This patch adds client TLS authentication to libsec in compliance with rfc 4346. A new -c flag has been introduced for tlsclient allowing the user to specify a certificate in pem(8) format which will be provided to the server upon request. A -D debug flag has been introduced to enable debugging output. The patch has been tested against OpenSSL 0.9.7j 04 May 2006. It exists today because of the great (debugging) help and insight provided by Matthias Bauer. TODOs: - specification of a certain client key in factotum is not possible at the moment - tlssrv should support this too These will get added in another patch. The first try to submit this patch failed due to a network error. Sorry for the duplication! Kind regards, Christian Reference: /n/sources/patch/maybe/tls-client-auth Date: Mon Jun 23 05:23:28 CES 2008 Signed-off-by: ckeen@pestilenz.org --- /sys/src/libsec/port/x509.c Thu Apr 24 21:55:47 2008 +++ /sys/src/libsec/port/x509.c Thu Apr 24 21:55:36 2008 @@ -19,7 +19,6 @@ typedef struct Elem Elem; typedef struct Tag Tag; typedef struct Value Value; -typedef struct Bytes Bytes; typedef struct Ints Ints; typedef struct Bits Bits; typedef struct Elist Elist; @@ -56,10 +55,6 @@ #define UniversalString 28 #define BMPString 30 -struct Bytes { - int len; - uchar data[1]; -}; struct Ints { int len; @@ -2027,8 +2022,7 @@ } return nil; } - -static mpint* +mpint* pkcs1pad(Bytes *b, mpint *modulus) { int n = (mpsignif(modulus)+7)/8; --- /sys/src/libsec/port/tlshand.c Thu Apr 24 21:56:07 2008 +++ /sys/src/libsec/port/tlshand.c Thu Apr 24 21:55:54 2008 @@ -28,11 +28,6 @@ typedef struct TlsSec TlsSec; -typedef struct Bytes{ - int len; - uchar data[1]; // [len] -} Bytes; - typedef struct Ints{ int len; int data[1]; // [len] @@ -112,6 +107,9 @@ struct { Bytes *key; } clientKeyExchange; + struct { + Bytes *signature; + } certificateVerify; Finished finished; } u; } Msg; @@ -249,8 +247,7 @@ }; static TlsConnection *tlsServer2(int ctl, int hand, uchar *cert, int ncert, int (*trace)(char*fmt, ...), PEMChain *chain); -static TlsConnection *tlsClient2(int ctl, int hand, uchar *csid, int ncsid, int (*trace)(char*fmt, ...)); - +static TlsConnection *tlsClient2(int ctl, int hand, uchar *csid, int ncsid, uchar *cert, int certlen, int (*trace)(char*fmt, ...)); static void msgClear(Msg *m); static char* msgPrint(char *buf, int n, Msg *m); static int msgRecv(TlsConnection *c, Msg *m); @@ -301,6 +298,7 @@ static int get16(uchar *p); static Bytes* newbytes(int len); static Bytes* makebytes(uchar* buf, int len); +static Bytes* mptobytes(mpint* big); static void freebytes(Bytes* b); static Ints* newints(int len); static Ints* makeints(int* buf, int len); @@ -396,7 +394,7 @@ if(data < 0) return -1; fprint(ctl, "fd %d 0x%x", fd, ProtocolVersion); - tls = tlsClient2(ctl, hand, conn->sessionID, conn->sessionIDlen, conn->trace); + tls = tlsClient2(ctl, hand, conn->sessionID, conn->sessionIDlen, conn->cert, conn->certlen, conn->trace); close(fd); close(hand); close(ctl); @@ -600,13 +598,15 @@ } static TlsConnection * -tlsClient2(int ctl, int hand, uchar *csid, int ncsid, int (*trace)(char*fmt, ...)) +tlsClient2(int ctl, int hand, uchar *csid, int ncsid, uchar *cert, int certlen, int (*trace)(char*fmt, ...)) { TlsConnection *c; Msg m; uchar kd[MaxKeyData], *epm; char *secrets; int creq, nepm, rv; + Bytes *modbytes,*tmpBytes; + mpint *signatureMP, *decryptMP, *signedMP, *paddedHashes; if(!initCiphers()) return nil; @@ -717,7 +717,9 @@ } if(creq) { - /* send a zero length certificate */ + m.u.certificate.ncert = 1; + m.u.certificate.certs = emalloc(m.u.certificate.ncert * sizeof(Bytes)); + m.u.certificate.certs[0] = makebytes(cert, certlen); m.tag = HCertificate; if(!msgSend(c, &m, AFlush)) goto Err; @@ -733,10 +735,57 @@ tlsError(c, EHandshakeFailure, "can't set secret: %r"); goto Err; } + if(!msgSend(c, &m, AFlush)) goto Err; msgClear(&m); + + + /* CertificateVerify */ + /*XXX I should only send this when it is not DH right? + Also we need to know which TLS key + we have to use in case there are more than one*/ + if (cert){ + m.tag = HCertificateVerify; + uchar hshashes[MD5dlen+SHA1dlen]; /* content of signature */ + MD5state hsmd5_save; + SHAstate hssha1_save; + + /* save the state for the Finish message */ + + hsmd5_save = c->hsmd5; + hssha1_save = c->hssha1; + md5(nil, 0, hshashes, &c->hsmd5); + sha1(nil, 0, hshashes+MD5dlen, &c->hssha1); + + c->hsmd5 = hsmd5_save; + c->hssha1 = hssha1_save; + + c->sec->rpc = factotum_rsa_open(cert, certlen); + if(c->sec->rpc == nil){ + tlsError(c, EHandshakeFailure, "factotum_rsa_open: %r"); + goto Err; + } + c->sec->rsapub = X509toRSApub(cert, certlen, nil, 0); + + paddedHashes = pkcs1pad(makebytes(hshashes, 36), c->sec->rsapub->n); + signedMP = factotum_rsa_decrypt(c->sec->rpc, paddedHashes); + m.u.certificateVerify.signature = mptobytes(signedMP); + free(signedMP); + + if (m.u.certificateVerify.signature == nil){ + msgClear(&m); + goto Err; + } + + if(!msgSend(c, &m, AFlush)){ + msgClear(&m); + goto Err; + } + msgClear(&m); + } + /* change cipher spec */ if(fprint(c->ctl, "changecipher") < 0){ tlsError(c, EInternalError, "can't enable cipher: %r"); @@ -890,6 +939,12 @@ p += m->u.certificate.certs[i]->len; } break; + case HCertificateVerify: + put16(p, m->u.certificateVerify.signature->len); + p += 2; + memmove(p, m->u.certificateVerify.signature->data, m->u.certificateVerify.signature->len); + p += m->u.certificateVerify.signature->len; + break; case HClientKeyExchange: n = m->u.clientKeyExchange.key->len; if(c->version != SSL3Version){ @@ -1114,7 +1169,7 @@ nn = get24(p); p += 3; n -= 3; - if(n != nn) + if(nn == 0 && n > 0) goto Short; /* certs */ i = 0; @@ -1140,8 +1195,7 @@ nn = p[0]; p += 1; n -= 1; - if(nn < 1 || nn > n) - goto Short; + m->u.certificateRequest.types = makebytes(p, nn); p += nn; n -= nn; @@ -1150,7 +1204,7 @@ nn = get16(p); p += 2; n -= 2; - if(nn == 0 || n != nn) + if(nn == 0 && n > 0) goto Short; /* cas */ i = 0; @@ -1246,6 +1300,9 @@ freebytes(m->u.certificateRequest.cas[i]); free(m->u.certificateRequest.cas); break; + case HCertificateVerify: + freebytes(m->u.certificateVerify.signature); + break; case HServerHelloDone: break; case HClientKeyExchange: @@ -1339,6 +1396,10 @@ for(i=0; iu.certificateRequest.nca; i++) bs = bytesPrint(bs, be, "\t\t", m->u.certificateRequest.cas[i], "\n"); break; + case HCertificateVerify: + bs = seprint(bs, be, "HCertificateVerify\n"); + bs = bytesPrint(bs, be, "\tsignature: ", m->u.certificateVerify.signature,"\n"); + break; case HServerHelloDone: bs = seprint(bs, be, "ServerHelloDone\n"); break; --- /sys/include/libsec.h Thu Apr 24 21:56:17 2008 +++ /sys/include/libsec.h Thu Apr 24 21:56:11 2008 @@ -372,3 +372,13 @@ /* readcert.c */ uchar *readcert(char *filename, int *pcertlen); PEMChain*readcertchain(char *filename); + +/* X509.c */ +typedef struct Bytes { + int len; + uchar data[1]; +} Bytes; +mpint *pkcs1pad(Bytes *b, mpint *modulus); + + + --- /sys/src/cmd/tlsclient.c Thu Apr 24 21:56:22 2008 +++ /sys/src/cmd/tlsclient.c Thu Apr 24 21:56:19 2008 @@ -6,7 +6,7 @@ void usage(void) { - fprint(2, "usage: tlsclient [-t /sys/lib/tls/xxx] [-x /sys/lib/tls/xxx.exclude] dialstring\n"); + fprint(2, "usage: tlsclient [-c lib/tls/clientcert] [-t /sys/lib/tls/xxx] [-x /sys/lib/tls/xxx.exclude] dialstring\n"); exits("usage"); } @@ -20,19 +20,34 @@ if(write(to, buf, n) < 0) break; } - + static int + reporter(char *fmt, ...) + { + va_list ap; + + va_start(ap, fmt); + fprint(2, "%s: tls reports ", argv0); + vfprint(2, fmt, ap); + fprint(2, "\n"); + + va_end(ap); + return 0; + } + void main(int argc, char **argv) { - int fd, netfd; + int fd, netfd, debug; uchar digest[20]; - TLSconn conn; - char *addr, *file, *filex; + TLSconn *conn; + char *addr, *file, *filex, *ccert; Thumbprint *thumb; file = nil; filex = nil; thumb = nil; + ccert=nil; + debug=0; ARGBEGIN{ case 't': file = EARGF(usage()); @@ -40,6 +55,12 @@ case 'x': filex = EARGF(usage()); break; + case 'D': + debug++; + break; + case 'c': + ccert = EARGF(usage()); + break; default: usage(); }ARGEND @@ -59,20 +80,24 @@ if((netfd = dial(addr, 0, 0, 0)) < 0) sysfatal("dial %s: %r", addr); - memset(&conn, 0, sizeof conn); - fd = tlsClient(netfd, &conn); + conn = (TLSconn*)mallocz(sizeof *conn, 1); + if (ccert) + conn->cert = readcert(ccert, &conn->certlen); + if(debug) + conn->trace = reporter; + fd = tlsClient(netfd, conn); if(fd < 0) sysfatal("tlsclient: %r"); if(thumb){ - if(conn.cert==nil || conn.certlen<=0) + if(conn->cert==nil || conn->certlen<=0) sysfatal("server did not provide TLS certificate"); - sha1(conn.cert, conn.certlen, digest, nil); + sha1(conn->cert, conn->certlen, digest, nil); if(!okThumbprint(digest, thumb)){ fmtinstall('H', encodefmt); sysfatal("server certificate %.*H not recognized", SHA1dlen, digest); } } - free(conn.cert); + free(conn->cert); close(netfd); rfork(RFNOTEG); --- /sys/man/8/tlssrv Thu Apr 24 21:56:30 2008 +++ /sys/man/8/tlssrv Thu Apr 24 21:56:27 2008 @@ -24,6 +24,13 @@ .PP .B tlsclient [ +.B -D +] +[ +.B -c +.I cert.pem +] +[ .B -t .I trustedkeys ] @@ -69,6 +76,14 @@ and then relays between the network connection and standard input and output. +The +.B -D +flag enables some debug output. +Specifying a certificate in pem(8) format with the +.B -c +flag, causes the client to submit this certificate upon +server's request. A corresponding key has to be present in +.IR factotum(4). If the .B -t flag