check in some experimental X.509 parsing code

This commit is contained in:
leitner
2015-05-07 23:53:05 +00:00
parent ad390ee7ea
commit 2479167b37
11 changed files with 535 additions and 2 deletions

View File

@@ -3,7 +3,7 @@
all: t1 t2 parse dumpidx idx2ldif addindex bindrequest tinyldap \
tinyldap_standalone tinyldap_debug ldapclient ldapclient_str \
md5password mysql2ldif acl dumpacls ldapdelete asn1dump # t6 # t
md5password mysql2ldif acl dumpacls ldapdelete asn1dump tls.a x # t6 # t
asn1.a: fmt_asn1intpayload.o fmt_asn1length.o fmt_asn1tag.o \
fmt_asn1int.o fmt_asn1string.o fmt_asn1transparent.o scan_asn1tag.o \
@@ -37,6 +37,13 @@ mduptab_init.o mduptab_init_reuse.o mduptab_reset.o
auth.a: auth.o
tls.a: fmt_tls_clienthello.o init_tls_context.o \
fmt_tls_serverhello.o fmt_tls_alert.o fmt_tls_packet.o \
tls_cipherprio.o fmt_tls_alert_pkt.o fmt_tls_handshake_cert.o \
fmt_tls_handshake_certs_header.o fmt_tls_serverhellodone.o \
tls_accept.o tls_connect.o tls_doread.o tls_dowrite.o
DIET=/opt/diet/bin/diet -Os
CROSS=
#CROSS=i686-mingw32-
@@ -171,10 +178,31 @@ scan_asn1generic.o: scan_asn1generic.c asn1.h
asn1oid.o: asn1oid.c asn1.h
init_tls_context.o: init_tls_context.c tinytls.h
fmt_tls_clienthello.o: fmt_tls_clienthello.c tinytls.h
ldap_match_sre.o: ldap_match_sre.c ldap.h
x: tls.a
privatekey.pem:
openssl genrsa -out $@
windoze:
$(MAKE) DIET= CROSS=i686-mingw32- asn1dump
fmt_tls_alert.o: fmt_tls_alert.c tinytls.h asn1.h
fmt_tls_alert_pkt.o: fmt_tls_alert_pkt.c tinytls.h asn1.h
fmt_tls_clienthello.o: fmt_tls_clienthello.c tinytls.h asn1.h
fmt_tls_handshake_cert.o: fmt_tls_handshake_cert.c tinytls.h asn1.h
fmt_tls_handshake_certs_header.o: fmt_tls_handshake_certs_header.c \
tinytls.h asn1.h
fmt_tls_packet.o: fmt_tls_packet.c tinytls.h asn1.h
fmt_tls_serverhello.o: fmt_tls_serverhello.c tinytls.h asn1.h
fmt_tls_serverhellodone.o: fmt_tls_serverhellodone.c tinytls.h asn1.h
init_tls_context.o: init_tls_context.c tinytls.h asn1.h
tls_accept.o: tls_accept.c tinytls.h asn1.h
tls_cipherprio.o: tls_cipherprio.c
tls_connect.o: tls_connect.c tinytls.h asn1.h
tls_doread.o: tls_doread.c tinytls.h asn1.h
tls_dowrite.o: tls_dowrite.c tinytls.h asn1.h

10
fmt_tls_alert.c Normal file
View File

@@ -0,0 +1,10 @@
#include "tinytls.h"
size_t fmt_tls_alert(char* dest,enum alertlevel level,enum alerttype type) {
if (dest) {
dest[0]=level;
dest[1]=type;
}
return 2;
}

11
fmt_tls_alert_pkt.c Normal file
View File

@@ -0,0 +1,11 @@
#include <string.h>
#include "tinytls.h"
size_t fmt_tls_alert_pkt(char* dest,enum alertlevel level,enum alerttype type) {
if (dest) {
memcpy(dest,"\x15\x03\x03\x00\x02",5);
dest[5]=level;
dest[6]=type;
}
return 7;
}

51
fmt_tls_clienthello.c Normal file
View File

@@ -0,0 +1,51 @@
#include "tinytls.h"
#include "uint16.h"
#include "uint32.h"
#include <time.h>
size_t fmt_tls_clienthello(char* dest, struct ssl_context* sc) {
size_t hnextlen=sc->servername?strlen(sc->servername)+9:0;
if (hnextlen>0x1000) return 0;
if (sc->session.l>0xff) return 0;
if (dest) {
char* x;
dest[0]=22; // content type: handshake
uint16_pack_big(dest+1,0x303); // tls 1.2
// uint16_pack_big(dest+3,length);
dest[5]=0x01; // handshake type: client hello
// uint16_pack_big(dest+6,length);
uint16_pack_big(dest+9,0x0303); // tls 1.2
uint32_pack_big(dest+11,time(0));
memcpy(dest+15,sc->myrandom,sizeof(sc->myrandom));
if ((dest[43]=sc->session.l))
memcpy(dest+44,sc->session.s,sc->session.l);
x=dest+44+sc->session.l;
uint16_pack_big(x,6);
uint16_pack_big(x+2,0x3d); // TLS_RSA_WITH_AES_256_CBC_SHA256
uint16_pack_big(x+4,0x35); // TLS_RSA_WITH_AES_256_CBC_SHA
uint16_pack_big(x+6,0xff); // "we support renegotiation"
x+=8;
#if 0
memcpy(x,"\x02\x01\x00",3); // 2 compression methods, deflate and null
x+=3;
#else
memcpy(x,"\x01\x00",2); // only support null compression
x+=2;
#endif
uint16_pack_big(x,hnextlen);
x+=2;
if (hnextlen) {
uint16_pack_big(x,0); // extension id 0 = server_name
uint16_pack_big(x+2,hnextlen-4); // length
uint16_pack_big(x+4,hnextlen-6); // another length
x[6]=0; // hostname type: DNS
uint16_pack_big(x+7,hnextlen-9); // yet another length
memcpy(x+9,sc->servername,hnextlen-9);
x+=hnextlen;
}
uint16_pack_big(dest+3,x-dest-5);
uint16_pack_big(dest+7,x-dest-9);
return x-dest;
} else
return 44+sc->session.l+8+2+2+hnextlen;
}

13
fmt_tls_packet.c Normal file
View File

@@ -0,0 +1,13 @@
#include "tinytls.h"
#include "uint16.h"
size_t fmt_tls_packet(char* dest,enum contenttype ct, size_t len) {
if (len>0xffff) return 0;
if (dest) {
dest[0]=ct;
dest[1]=0x03; dest[2]=0x03; // version: TLS 1.2
dest[3]=len>>8;
dest[4]=len&0xff;
}
return 5;
}

163
fmt_tls_serverhello.c Normal file
View File

@@ -0,0 +1,163 @@
#include "uint16.h"
#include "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
}

19
init_tls_context.c Normal file
View File

@@ -0,0 +1,19 @@
#include "tinytls.h"
#include "open.h"
#include <unistd.h>
void init_tls_context_norandom(struct ssl_context* sc, const char* servername) {
memset(sc,0,sizeof *sc);
sc->servername=servername;
}
int init_tls_context(struct ssl_context* sc, const char* servername) {
int fd=open_read("/dev/urandom");
int r;
if (fd==-1) return -1;
init_tls_context_norandom(sc,servername);
r=read(fd,sc->myrandom,sizeof(sc->myrandom));
close(fd);
if (r!=sizeof(sc->myrandom)) return -1;
return 0;
}

View File

@@ -32,6 +32,9 @@ struct rsaprivatekey {
size_t* freewhendone;
};
struct dsaprivatekey {
};
void printasn1(const char* buf,const char* max);
static int findindn(struct string* dn,enum x509_oid id,struct string* dest) {
@@ -379,7 +382,7 @@ int main(int argc,char* argv[]) {
printf("failed to parse certificate\n");
free(freewhendone);
buf=mmap_read(argc>1?argv[1]:"privatekey.pem",&l);
buf=mmap_read(argc>2?argv[2]:"privatekey.pem",&l);
if (!buf) { puts("privatekey.pem not found"); return 1; }
n=scan_rsaprivatekey(buf,l,&k,&freewhendone);

166
tinytls.h Normal file
View File

@@ -0,0 +1,166 @@
#ifndef _TINYTLS_H
#define _TINYTLS_H
#include <stdint.h>
#include <sys/types.h>
/* for struct string: */
#include "asn1.h"
typedef enum {
NONE,
FAIL, // protocol failure, refuse all operations
READ_CLIENTHELLO, // tls_accept called, trying to read client hello
WRITE_ALERTFAIL, // got something bad, write alert and fail
WRITE_SERVERHELLO, // got client hello, trying to write server hello
WRITE_CLIENTHELLO, // tls_connect called, trying to write client hello
READ_SERVERHELLO, // trying to read server hello
READ_CERT, // read server hello, trying to read cert
READ_SERVERHELLODONE, // read server hello + cert, trying to read server hello done
} tls_state;
enum { MAXCERT=4 };
struct ssl_context {
tls_state state;
char myrandom[28];
char theirrandom[28];
time_t timestamp; /* so we can age out old sessions from the session id cache */
const char* servername;
struct string session; // a cookie sent during the handshake, so if a client comes back later, we can save work
struct string mycert[MAXCERT]; // my own cert, maybe an intermediate cert, and a ca cert; sent during handshake
struct string theircert[MAXCERT];
struct string message; // the packet we are currently trying to read or write
size_t ofsinmessage;
char scratch[2048];
ssize_t (*_read)(uintptr_t handle,char* buf,size_t len);
ssize_t (*_write)(uintptr_t handle,const char* buf,size_t len);
int (*_close)(uintptr_t handle);
int (*readcert)(struct ssl_context* sc); // use sc->servername to read cert into sc->mycert
// return 0 for success, enum alerttype otherwise
uint16_t cipher,compressionmethod;
};
/* Put servername into ssl_context, set empty session. */
void init_tls_context_norandom(struct ssl_context* sc, const char* servername);
/* Put servername into ssl_context, and fill random bytes from
* /dev/urandom. Return 0 if OK, -1 if /dev/urandom failed */
int init_tls_context(struct ssl_context* sc, const char* servername);
/* Generate a client hello inside a handshake packet using the
* servername and session data from the context, return number of bytes
* written to dest. Call with dest=NULL to get needed buffer size. */
size_t fmt_tls_clienthello(char* dest, struct ssl_context* sc);
/* The response to a client hello consists of several packets:
1. server hello
2. certificate
[3. server key exchange for a DHE cipher suite]
4. server hello done */
/* Generate a server hello from a client hello that came in, return
* number of bytes written to dest.
* Call with dest=NULL to get needed buffer size.
* Returns (size_t)-1 if the client hello is not complete,
* (size_t)-2 if the client hello is invalid
* if the client helo is valid but has no common ciphers, write an alert
* and return length of alert (7) */
size_t fmt_tls_serverhello(char* dest,const char* clienthelo,size_t len,struct ssl_context* sc);
/*
* Allocate a buffer that can hold all the X.509 certificates plus 3
* bytes per certificate plus 12 bytes for the headers. Write the
* certificates to buf+12 using fmt_tls_handshake_cert. Then write
* the header to buf using fmt_tls_handshake_certs_header; give it the
* sum of the return values of fmt_tls_handshake_cert as len.
*/
size_t fmt_tls_handshake_cert(char* dest,const char* cert,size_t len);
size_t fmt_tls_handshake_certs_header(char* dest,size_t len_of_certs);
size_t fmt_tls_serverhellodone(char* dest);
size_t scan_tls_serverhello(const char* buf,size_t len,struct ssl_context* sc);
enum alertlevel {
WARNING=1,
FATAL=2
};
enum alerttype {
CLOSE_NOTIFY=0,
UNEXPECTED_MESSAGE=10,
BAD_RECORD_MAC=20,
DECRYPTION_FAILED=21,
RECORD_OVERFLOW=22,
DECOMPRESSION_FAILURE=30,
HANDSHAKE_FAILURE=40,
NO_CERT=41,
BAD_CERT=42,
UNSUPPORTED_CERT=43,
CERT_REVOKED=44,
CERT_EXPIRED=45,
CERT_UNKNOWN=46,
ILLEGAL_PARAMETER=47,
UNKNOWN_CA=48,
ACCESS_DENIED=49,
DECODE_ERROR=50,
DECRYPT_ERROR=51,
EXPORT_RESTRICTION=60,
PROTOCOL_VERSION=70,
INSUFFICIENT_SECURITY=71,
INTERNAL_ERROR=80,
USER_CANCELED=90,
NO_RENEGOTIATION=100,
UNSUPPORTED_EXT=110
};
/* Generate a TLS alert (only the alert!) */
size_t fmt_tls_alert(char* dest,enum alertlevel level,enum alerttype type);
/* Generate a TLS alert with outer header */
size_t fmt_tls_alert_pkt(char* dest,enum alertlevel level,enum alerttype type);
enum contenttype {
CHANGE_CIPHER_SPEC=20,
ALERT=21,
HANDSHAKE=22,
APPLICATION_DATA=23
};
size_t fmt_tls_packet(char* dest,enum contenttype ct, size_t len);
enum ciphers {
TLS_RSA_WITH_AES_256_CBC_SHA256=0x3d,
TLS_RSA_WITH_AES_256_CBC_SHA=0x35,
};
/* return the desirability of a cipher (number >= 0)
* or -1 if the cipher is not supported
* the higher the number, the less desirable is the cipher */
int tls_cipherprio(uint16_t cipher);
typedef enum {
OK=0,
IOFAIL=-1, // I/O error
WANTREAD=-2, // socket was non-blocking; wait for read event
WANTWRITE=-3, // socket was non-blocking; wait for write event
OOM=-4, // out of memory
PROTOCOLFAIL=-5, // received invalid packets
NEGOTIATIONFAIL=-6, // no common ciphers / compression methods
CRYPTOFAIL=-7, // cryptographic failure (weak key detected or so)
CERTFAIL=-8, // certificate validation failed
YOUSUCK=-42, // user-supplied callbacks violated protocol
} tls_error_code;
tls_error_code tls_connect(uintptr_t fd,struct ssl_context* sc);
tls_error_code tls_accept(uintptr_t fd,struct ssl_context* sc);
/* these are internal helpers */
tls_error_code tls_dowrite(uintptr_t fd,struct ssl_context* sc);
tls_error_code tls_doread(uintptr_t fd,struct ssl_context* sc);
tls_error_code tls_checkalert(struct ssl_context* sc);
#endif

13
tls_cipherprio.c Normal file
View File

@@ -0,0 +1,13 @@
#include <tinytls.h>
static uint16_t ciphers[] = {
TLS_RSA_WITH_AES_256_CBC_SHA256,
TLS_RSA_WITH_AES_256_CBC_SHA,
};
int tls_cipherprio(uint16_t cipher) {
size_t i;
for (i=0; i<sizeof(ciphers)/sizeof(ciphers[0]); ++i)
if (ciphers[i]==cipher) return i;
return -1;
}

56
x.c Normal file
View File

@@ -0,0 +1,56 @@
#include "tinytls.h"
#include "errmsg.h"
#include "ip4.h"
#include "socket.h"
#include <stdio.h>
char pkt[]=
"\x16\x03\x01\x01\x4e\x01\x00\x01\x4a\x03\x03\x4f\x93\x81\xfb\x57"
"\x72\x4b\x79\x31\x35\x0f\x4e\xb6\xd5\x47\xb7\x0d\xb5\x54\x0e\xd1"
"\x71\xc1\xb6\x9b\x9f\xdb\xa5\xf0\xe4\x43\xac\x00\x00\xa0\xc0\x30"
"\xc0\x2c\xc0\x28\xc0\x24\xc0\x14\xc0\x0a\xc0\x22\xc0\x21\x00\xa3"
"\x00\x9f\x00\x6b\x00\x6a\x00\x39\x00\x38\x00\x88\x00\x87\xc0\x32"
"\xc0\x2e\xc0\x2a\xc0\x26\xc0\x0f\xc0\x05\x00\x9d\x00\x3d\x00\x35"
"\x00\x84\xc0\x12\xc0\x08\xc0\x1c\xc0\x1b\x00\x16\x00\x13\xc0\x0d"
"\xc0\x03\x00\x0a\xc0\x2f\xc0\x2b\xc0\x27\xc0\x23\xc0\x13\xc0\x09"
"\xc0\x1f\xc0\x1e\x00\xa2\x00\x9e\x00\x67\x00\x40\x00\x33\x00\x32"
"\x00\x9a\x00\x99\x00\x45\x00\x44\xc0\x31\xc0\x2d\xc0\x29\xc0\x25"
"\xc0\x0e\xc0\x04\x00\x9c\x00\x3c\x00\x2f\x00\x96\x00\x41\x00\x07"
"\xc0\x11\xc0\x07\xc0\x0c\xc0\x02\x00\x05\x00\x04\x00\x15\x00\x12"
"\x00\x09\x00\x14\x00\x11\x00\x08\x00\x06\x00\x03\x00\xff\x02\x01"
"\x00\x00\x80\x00\x00\x00\x11\x00\x0f\x00\x00\x0c\x62\x6c\x6f\x67"
"\x2e\x66\x65\x66\x65\x2e\x64\x65\x00\x0b\x00\x04\x03\x00\x01\x02"
"\x00\x0a\x00\x34\x00\x32\x00\x0e\x00\x0d\x00\x19\x00\x0b\x00\x0c"
"\x00\x18\x00\x09\x00\x0a\x00\x16\x00\x17\x00\x08\x00\x06\x00\x07"
"\x00\x14\x00\x15\x00\x04\x00\x05\x00\x12\x00\x13\x00\x01\x00\x02"
"\x00\x03\x00\x0f\x00\x10\x00\x11\x00\x0d\x00\x22\x00\x20\x06\x01"
"\x06\x02\x06\x03\x05\x01\x05\x02\x05\x03\x04\x01\x04\x02\x04\x03"
"\x03\x01\x03\x02\x03\x03\x02\x01\x02\x02\x02\x03\x01\x01\x00\x0f"
"\x00\x01\x01\x15\x03\x03\x00\x02\x02\x30";
int main() {
char buf[200];
size_t l;
struct ssl_context sc;
int fd;
fd=socket_tcp4b();
if (fd==-1)
diesys(1,"socket");
if (socket_connect4(fd,ip4loopback,443)==-1)
if (socket_connect4(fd,ip4loopback,4433)==-1)
diesys(1,"connect");
#if 0
init_tls_context_norandom(&sc,NULL);
printf("%zu\n",fmt_tls_serverhello(NULL,pkt,sizeof(pkt),&sc));
init_tls_context_norandom(&sc,"blog.fefe.de");
l=fmt_tls_clienthello(buf,&sc);
printf("%zu\n",fmt_tls_serverhello(NULL,buf,l,&sc));
#endif
init_tls_context(&sc,"localhost");
tls_connect(fd,&sc);
return 0;
}