164 lines
5.3 KiB
C
164 lines
5.3 KiB
C
#include <libowfat/uint16.h>
|
|
#include <libowfat/uint32.h>
|
|
#include "tinytls.h"
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
#include <time.h>
|
|
|
|
/*
|
|
|
|
This function parses a client helo and writes the server helo into a
|
|
buffer. It returns the number of bytes in the server helo message,
|
|
so you can send it over the TCP connection. If you pass in NULL as
|
|
the destination buffer, the function tells you how much space it
|
|
would have needed. So the regular way to use it is to call it twice.
|
|
|
|
For efficiency: server helo is around 50 bytes of boilerplate plus
|
|
the session data (which comes from the SSL context you are passing
|
|
in, field session.l, and its length is limited to 255 bytes).
|
|
|
|
The function returns (size_t)-1 if the input buffer does not contain a
|
|
full client helo message (i.e. "read more data, then try again") and
|
|
it returns (size_t)-2 if the input buffer contained an invalid message
|
|
("somebody is trying to hack you; drop connection").
|
|
|
|
If we do not support any of the ciphers or compression methods the other
|
|
side wants, this function writes an alert message into the buffer
|
|
(length 7). You are then supposed to send that buffer and close the
|
|
connection.
|
|
|
|
Note that TLS has an encapsulation, so you get an outer message header
|
|
and an inner message header. For both input and output we handle both
|
|
headers.
|
|
|
|
*/
|
|
|
|
size_t fmt_tls_serverhello(char* dest,const char* clienthello,size_t len,struct ssl_context* sc) {
|
|
size_t l,i;
|
|
int compressionmethod=-1,hostlen=-1;
|
|
const char* host;
|
|
uint16_t best=0,bestprio=0x7fff;
|
|
|
|
/* first check if the clienthello is completely there */
|
|
if (len<5 || len<(l=5+uint16_read_big(clienthello+3)))
|
|
return (size_t)-1;
|
|
|
|
/* ok, it's complete, now check if it is valid. */
|
|
if (l < 49 || // Minimum length with one cipher suite
|
|
clienthello[0]!=22 || // Content Type: handshake
|
|
clienthello[1]!=3 || // at least SSL 3.0
|
|
clienthello[5]!=1 || // Handshake Type: Client Hello
|
|
clienthello[6]!=0 || // inner length is 3 bytes, outer length is 2 bytes, so first byte of inner length must be 0
|
|
uint16_read_big(clienthello+7)!=l-9) // inner length must fit into outer length
|
|
invalid:
|
|
return (size_t)-2;
|
|
|
|
i=43;
|
|
i+=(unsigned char)clienthello[i]+1; // session length
|
|
if (i+1>=l) goto invalid;
|
|
{
|
|
uint16 ciphers=uint16_read_big(clienthello+i);
|
|
uint16_t* c;
|
|
size_t j;
|
|
if (ciphers&1) goto invalid; // must be multiple of two
|
|
if (ciphers==0) goto invalid; // must support at least one cipher suite
|
|
c=(uint16_t*)(clienthello+i+2);
|
|
if (i+ciphers+2>=l) goto invalid; // do the ciphers fit in the packet?
|
|
for (j=0; j<ciphers; j+=2) {
|
|
uint16_t cur;
|
|
int p=tls_cipherprio((cur=uint16_read_big((char*)(c+j))));
|
|
// printf("peer supports tls cipher %x\n",cur);
|
|
if (p<0) continue;
|
|
if (p<bestprio) {
|
|
best=cur;
|
|
bestprio=p;
|
|
}
|
|
}
|
|
i+=ciphers+2; // skip cipher suites
|
|
}
|
|
{
|
|
size_t j,n=(unsigned char)clienthello[i];
|
|
const char* x=clienthello+i+1;
|
|
if (i+1+clienthello[i]+2>l) goto invalid; // do the compression methods fit in the packet?
|
|
for (j=0; j<n; ++j)
|
|
if (x[i]==0) compressionmethod=0; // for now only support method 0 (no compression)
|
|
}
|
|
i+=clienthello[i]+1; // compression methods
|
|
if (i+uint16_read_big(clienthello+i)+2 != l) // extensions
|
|
goto invalid;
|
|
i+=2;
|
|
while (i<l) {
|
|
if (i+4>l)
|
|
goto invalid;
|
|
if (clienthello[i]==0 && clienthello[i+1]==0) { /* server_name extension */
|
|
hostlen=uint16_read_big(clienthello+i+2);
|
|
host=clienthello+i+4;
|
|
}
|
|
if ((i+=4+uint16_read_big(clienthello+i+2))>l)
|
|
goto invalid;
|
|
}
|
|
/* The client hello validated OK; we can generate a reply now. */
|
|
|
|
/* do we support any of the ciphers and compression methods? */
|
|
if (bestprio==0x7fff || compressionmethod==-1) { /* nope */
|
|
return dest?
|
|
fmt_tls_alert_pkt(dest,FATAL,HANDSHAKE_FAILURE) :
|
|
7;
|
|
}
|
|
|
|
if (!sc->servername) {
|
|
/* We have not yet copied the data out of the client hello.
|
|
* Do so now. */
|
|
memcpy(sc->theirrandom,clienthello+15,sizeof(sc->theirrandom));
|
|
sc->cipher=best;
|
|
sc->compressionmethod=0;
|
|
if (hostlen!=-1) {
|
|
char* sn;
|
|
if ((sn=malloc(hostlen+1))) {
|
|
memcpy(sn,host,hostlen);
|
|
sn[hostlen]=0;
|
|
sc->servername=sn;
|
|
}
|
|
}
|
|
sc->timestamp=time(0);
|
|
}
|
|
|
|
if (sc->session.l>0xff) return 0;
|
|
|
|
if (dest) {
|
|
char* x;
|
|
fmt_tls_packet(dest,HANDSHAKE,1+3+2+4+28+1+sc->session.l+2+1+2+5);
|
|
dest+=5;
|
|
dest[0]=2; // type 2 = server hello
|
|
dest[1]=0;
|
|
uint16_pack_big(dest+2,2+4+28+1+sc->session.l+2+1+2+5);
|
|
uint16_pack_big(dest+4,0x0303); // version: TLS 1.2
|
|
uint32_pack_big(dest+6,sc->timestamp);
|
|
memcpy(dest+10,sc->myrandom,28);
|
|
x=dest+38;
|
|
*x=sc->session.l;
|
|
memcpy(x+1,sc->session.s,sc->session.l);
|
|
x+=sc->session.l+1;
|
|
uint16_pack_big(x,sc->cipher);
|
|
x[2]=sc->compressionmethod;
|
|
x+=3;
|
|
memcpy(x,"\x00\x05\xff\x01\x00\x01\x00",7); // this is the renegotiation extension
|
|
}
|
|
return 5+1+3+2+4+28+1+sc->session.l+2+1+2+5;
|
|
#if 0
|
|
char type = 2;
|
|
char length[3]; // big endian
|
|
char version[2]; // 0x03, 0x03
|
|
uint32_t gmt_unix_time; // big endian
|
|
char random[28];
|
|
char session_id_length;
|
|
char session_id[session_id_length];
|
|
char cipher_id[2];
|
|
char compression; // 0 - none, 1 - deflate
|
|
char extensions_length[2]; // \x00\x05
|
|
char renegotiation_extension[5]; // \xff\x01\x00\x01\x00
|
|
#endif
|
|
|
|
}
|