diff --git a/sys/include/ape/libsec.h b/sys/include/ape/libsec.h index d7b3f9a39..954d3641d 100644 --- a/sys/include/ape/libsec.h +++ b/sys/include/ape/libsec.h @@ -461,7 +461,8 @@ DSApriv* asn1toDSApriv(uchar*, int); */ typedef struct Thumbprint{ struct Thumbprint *next; - uchar sha1[SHA1dlen]; + uchar hash[SHA2_256dlen]; + uchar len; } Thumbprint; typedef struct TLSconn{ @@ -487,9 +488,10 @@ int tlsClient(int fd, TLSconn *c); int tlsServer(int fd, TLSconn *c); /* thumb.c */ -Thumbprint* initThumbprints(char *ok, char *crl); +Thumbprint* initThumbprints(char *ok, char *crl, char *tag); void freeThumbprints(Thumbprint *ok); -int okThumbprint(uchar *sha1, Thumbprint *ok); +int okThumbprint(uchar *hash, int len, Thumbprint *ok); +int okCertificate(uchar *cert, int len, Thumbprint *ok); /* readcert.c */ uchar *readcert(char *filename, int *pcertlen); diff --git a/sys/include/libsec.h b/sys/include/libsec.h index 0067302e8..12f4d4be4 100644 --- a/sys/include/libsec.h +++ b/sys/include/libsec.h @@ -453,7 +453,8 @@ DSApriv* asn1toDSApriv(uchar*, int); */ typedef struct Thumbprint{ struct Thumbprint *next; - uchar sha1[SHA1dlen]; + uchar hash[SHA2_256dlen]; + uchar len; } Thumbprint; typedef struct TLSconn{ @@ -479,9 +480,10 @@ int tlsClient(int fd, TLSconn *c); int tlsServer(int fd, TLSconn *c); /* thumb.c */ -Thumbprint* initThumbprints(char *ok, char *crl); +Thumbprint* initThumbprints(char *ok, char *crl, char *tag); void freeThumbprints(Thumbprint *ok); -int okThumbprint(uchar *sha1, Thumbprint *ok); +int okThumbprint(uchar *hash, int len, Thumbprint *ok); +int okCertificate(uchar *cert, int len, Thumbprint *ok); /* readcert.c */ uchar *readcert(char *filename, int *pcertlen); diff --git a/sys/man/2/pushtls b/sys/man/2/pushtls index ed3f293dd..72a2605bf 100644 --- a/sys/man/2/pushtls +++ b/sys/man/2/pushtls @@ -1,6 +1,6 @@ .TH PUSHTLS 2 .SH NAME -pushtls, tlsClient, tlsServer, initThumbprints, freeThumbprints, okThumbprint, readcert, readcertchain \- attach TLS1 or SSL3 encryption to a communication channel +pushtls, tlsClient, tlsServer, initThumbprints, freeThumbprints, okThumbprint, okCertificate, readcert, readcertchain \- attach TLS1 or SSL3 encryption to a communication channel .SH SYNOPSIS .B #include .br @@ -29,13 +29,16 @@ uchar *readcert(char *filename, int *pcertlen) PEMchain *readcertchain(char *filename) .PP .B -Thumbprint *initThumbprints(char *ok, char *crl) +Thumbprint *initThumbprints(char *ok, char *crl, char *tag) .PP .B void freeThumbprints(Thumbprint *table) .PP .B -int okThumbprint(uchar *hash, Thumbprint *table) +int okThumbprint(uchar *hash, int len, Thumbprint *table) +.PP +.B +int okCertificate(uchar *cert, int len, Thumbprint *table) .SH DESCRIPTION Transport Layer Security (TLS) comprises a record layer protocol, doing message digesting and encrypting in the kernel, @@ -213,13 +216,10 @@ be freed by the application whenever convenient. Start the client half of TLS and check the remote certificate: .IP .EX -uchar hash[SHA1dlen]; - conn = (TLSconn*)mallocz(sizeof *conn, 1); fd = tlsClient(fd, conn); -sha1(conn->cert, conn->certlen, hash, nil); -if(!okThumbprint(hash,table)) - exits("suspect server"); +if(!okCertificate(conn->cert, conn->certlen, table)) + sysfatal("suspect server: %r"); \fI...application begins...\fP .EE .PP diff --git a/sys/man/6/thumbprint b/sys/man/6/thumbprint index 63be911af..196108e1f 100644 --- a/sys/man/6/thumbprint +++ b/sys/man/6/thumbprint @@ -8,6 +8,8 @@ for example by calling .B tlsClient and .B okThumbprint +or +.B okCertificate (see .IR pushtls (2)), check the remote side's public key by comparing against @@ -25,13 +27,21 @@ attribute/value pairs of the form .IB attr = value or .IR attr . -The first attribute must be +The first attribute must be the application tag: .B x509 -and the second must be -.BI sha1= {hex checksum of binary certificate}. +for tls applications or +.B ssh +for ssh server fingerprints. +The second attribute must be a hash type of +.B sha1= +or +.BI sha256= +followed by the hex or base64 encoded hash of binary certificate +or public key. All other attributes are treated as comments. The file may also contain lines of the form -.BI #include file +.B #include +.I file .PP For example, a web server might have thumbprint .EX diff --git a/sys/src/ape/lib/sec/mkfile b/sys/src/ape/lib/sec/mkfile index 89d464885..abd5813c9 100644 --- a/sys/src/ape/lib/sec/mkfile +++ b/sys/src/ape/lib/sec/mkfile @@ -12,6 +12,7 @@ install all:V: } clean:V: + rm -f [$OS].* *.[$OS] for(i in $DIRS)@{ echo $i cd $i @@ -34,5 +35,6 @@ everything:V: APE=/sys/src/ape <$APE/config + $O.tlsclient: tlsclient.c $CC -o $target $CFLAGS -D_POSIX_SOURCE -D_PLAN9_SOURCE -D_NET_EXTENSION tlsclient.c diff --git a/sys/src/ape/lib/sec/tlsclient.c b/sys/src/ape/lib/sec/tlsclient.c index 37095a6ab..3fd412486 100644 --- a/sys/src/ape/lib/sec/tlsclient.c +++ b/sys/src/ape/lib/sec/tlsclient.c @@ -107,7 +107,7 @@ main(int argc, char **argv) sysfatal("specifying -x without -t is useless"); if(file){ - thumb = initThumbprints(file, filex); + thumb = initThumbprints(file, filex, "x509"); if(thumb == nil) sysfatal("initThumbprints: %r"); } else @@ -144,13 +144,8 @@ main(int argc, char **argv) sysfatal("tlsclient: %r"); if(thumb){ - uchar digest[20]; - - if(conn->cert==nil || conn->certlen<=0) - sysfatal("server did not provide TLS certificate"); - sha1(conn->cert, conn->certlen, digest, nil); - if(!okThumbprint(digest, thumb)) - sysfatal("server certificate %.*H not recognized", SHA1dlen, digest); + if(!okCertificate(conn->cert, conn->certlen, thumb)) + sysfatal("cert for %s not recognized: %r", servername ? servername : addr); freeThumbprints(thumb); } diff --git a/sys/src/cmd/ssh.c b/sys/src/cmd/ssh.c index a635dd546..e4e1f3e89 100644 --- a/sys/src/cmd/ssh.c +++ b/sys/src/cmd/ssh.c @@ -4,7 +4,6 @@ #include #include #include -#include enum { MSG_DISCONNECT = 1, @@ -84,7 +83,6 @@ char *user, *service, *status, *host, *cmd; Oneway recv, send; void dispatch(void); -int checkthumb(char*, uchar*); void shutdown(void) @@ -580,22 +578,25 @@ Next1: switch(recvpkt()){ ds = hashstr(ys, 32, ds); if(thumb[0] == 0){ + Thumbprint *ok; + sha2_256(ks, nks, h, nil); - i = snprint(thumb, sizeof(thumb), "%.*[", sizeof(h), h); + i = enc64(thumb, sizeof(thumb), h, sizeof(h)); while(i > 0 && thumb[i-1] == '=') - thumb[--i] = '\0'; + i--; + thumb[i] = '\0'; + if(debug) fprint(2, "host fingerprint: %s\n", thumb); - switch(checkthumb(thumbfile, h)){ - case 0: - werrstr("host unknown"); - default: + + ok = initThumbprints(thumbfile, nil, "ssh"); + if(ok == nil || !okThumbprint(h, sizeof(h), ok)){ + if(ok != nil) werrstr("unknown host"); fprint(2, "%s: %r, to add after verification:\n", argv0); - fprint(2, "\techo 'ssh sha256=%s # %s' >> %q\n", thumb, host, thumbfile); + fprint(2, "\techo 'ssh sha256=%s server=%s' >> %q\n", thumb, host, thumbfile); sysfatal("checking hostkey failed: %r"); - case 1: - break; } + freeThumbprints(ok); } if((pub = ssh2rsapub(ks, nks)) == nil) @@ -1074,39 +1075,6 @@ rawon(void) } } -int -checkthumb(char *file, uchar hash[SHA2_256dlen]) -{ - uchar sum[SHA2_256dlen]; - char *line, *field[50]; - Biobuf *bin; - - if((bin = Bopen(file, OREAD)) == nil) - return -1; - for(; (line = Brdstr(bin, '\n', 1)) != nil; free(line)){ - if(tokenize(line, field, nelem(field)) < 2) - continue; - if(strcmp(field[0], "ssh") != 0 || strncmp(field[1], "sha256=", 7) != 0) - continue; - field[1] += 7; - if(dec64(sum, SHA2_256dlen, field[1], strlen(field[1])) != SHA2_256dlen){ - werrstr("malformed ssh entry in %s: %s", file, field[1]); - goto err; - } - if(memcmp(sum, hash, SHA2_256dlen) == 0){ - free(line); - Bterm(bin); - return 1; - } - } - Bterm(bin); - return 0; -err: - free(line); - Bterm(bin); - return -1; -} - void usage(void) { @@ -1124,7 +1092,6 @@ main(int argc, char *argv[]) quotefmtinstall(); fmtinstall('B', mpfmt); fmtinstall('H', encodefmt); - fmtinstall('[', encodefmt); s = getenv("TERM"); raw = s != nil && strcmp(s, "dumb") != 0; diff --git a/sys/src/cmd/tlsclient.c b/sys/src/cmd/tlsclient.c index a90eabf96..1b79fb739 100644 --- a/sys/src/cmd/tlsclient.c +++ b/sys/src/cmd/tlsclient.c @@ -87,7 +87,7 @@ main(int argc, char **argv) sysfatal("specifying -x without -t is useless"); if(file){ - thumb = initThumbprints(file, filex); + thumb = initThumbprints(file, filex, "x509"); if(thumb == nil) sysfatal("initThumbprints: %r"); } else @@ -123,13 +123,8 @@ main(int argc, char **argv) sysfatal("tlsclient: %r"); if(thumb){ - uchar digest[20]; - - if(conn->cert==nil || conn->certlen<=0) - sysfatal("server did not provide TLS certificate"); - sha1(conn->cert, conn->certlen, digest, nil); - if(!okThumbprint(digest, thumb)) - sysfatal("server certificate %.*H not recognized", SHA1dlen, digest); + if(!okCertificate(conn->cert, conn->certlen, thumb)) + sysfatal("cert for %s not recognized: %r", servername ? servername : addr); freeThumbprints(thumb); } diff --git a/sys/src/cmd/upas/fs/tls.c b/sys/src/cmd/upas/fs/tls.c index 07906ee38..dd1ca22cb 100644 --- a/sys/src/cmd/upas/fs/tls.c +++ b/sys/src/cmd/upas/fs/tls.c @@ -6,7 +6,6 @@ int wraptls(int ofd, char *host) { - uchar digest[SHA1dlen]; Thumbprint *thumb; TLSconn conn; int fd; @@ -18,16 +17,10 @@ wraptls(int ofd, char *host) close(ofd); return -1; } - thumb = initThumbprints("/sys/lib/tls/mail", "/sys/lib/tls/mail.exclude"); + thumb = initThumbprints("/sys/lib/tls/mail", "/sys/lib/tls/mail.exclude", "x509"); if(thumb != nil){ - if(conn.cert == nil || conn.certlen <= 0){ - werrstr("server did not provide TLS certificate"); - goto Err; - } - sha1(conn.cert, conn.certlen, digest, nil); - if(!okThumbprint(digest, thumb)){ - werrstr("server certificate %.*H not recognized", - SHA1dlen, digest); + if(!okCertificate(conn.cert, conn.certlen, thumb)){ + werrstr("cert for %s not recognized: %r", host); Err: close(fd); fd = -1; diff --git a/sys/src/cmd/upas/smtp/smtp.c b/sys/src/cmd/upas/smtp/smtp.c index b20d4df7b..4fb437992 100644 --- a/sys/src/cmd/upas/smtp/smtp.c +++ b/sys/src/cmd/upas/smtp/smtp.c @@ -388,9 +388,8 @@ wraptls(void) { TLSconn *c; Thumbprint *goodcerts; - char *h, *err; + char *err; int fd; - uchar hash[SHA1dlen]; goodcerts = nil; err = Giveup; @@ -412,29 +411,19 @@ wraptls(void) Bterm(&bin); Binit(&bin, fd, OREAD); - goodcerts = initThumbprints(smtpthumbs, smtpexclthumbs); + goodcerts = initThumbprints(smtpthumbs, smtpexclthumbs, "x509"); if (goodcerts == nil) { syslog(0, "smtp", "bad thumbprints in %s", smtpthumbs); goto Out; } - /* compute sha1 hash of remote's certificate, see if we know it */ - sha1(c->cert, c->certlen, hash, nil); - if (!okThumbprint(hash, goodcerts)) { - /* TODO? if not excluded, add hash to thumb list */ - h = malloc(2*sizeof hash + 1); - if (h == nil) - goto Out; - enc16(h, 2*sizeof hash + 1, hash, sizeof hash); - syslog(0, "smtp", "remote cert. has bad thumbprint: x509 sha1=%s server=%q", - h, ddomain); - free(h); + if (!okCertificate(c->cert, c->certlen, goodcerts)) { + syslog(0, "smtp", "cert for %s not recognized: %r", ddomain); goto Out; } syslog(0, "smtp", "started TLS to %q", ddomain); err = nil; Out: - if(goodcerts != nil) - freeThumbprints(goodcerts); + freeThumbprints(goodcerts); free(c->cert); free(c->sessionID); free(c); diff --git a/sys/src/libsec/port/thumb.c b/sys/src/libsec/port/thumb.c index d8ded8305..39757939b 100644 --- a/sys/src/libsec/port/thumb.c +++ b/sys/src/libsec/port/thumb.c @@ -5,9 +5,9 @@ enum{ ThumbTab = 1<<10 }; static Thumbprint* -tablehead(uchar *sum, Thumbprint *table) +tablehead(uchar *hash, Thumbprint *table) { - return &table[((sum[0]<<8) + sum[1]) & (ThumbTab-1)]; + return &table[((hash[0]<<8) + hash[1]) & (ThumbTab-1)]; } void @@ -27,15 +27,15 @@ freeThumbprints(Thumbprint *table) } int -okThumbprint(uchar *sum, Thumbprint *table) +okThumbprint(uchar *hash, int len, Thumbprint *table) { Thumbprint *hd, *p; if(table == nil) return 0; - hd = tablehead(sum, table); + hd = tablehead(hash, table); for(p = hd->next; p; p = p->next){ - if(memcmp(sum, p->sha1, SHA1dlen) == 0) + if(p->len == len && memcmp(hash, p->hash, len) == 0) return 1; if(p == hd) break; @@ -43,14 +43,51 @@ okThumbprint(uchar *sum, Thumbprint *table) return 0; } +int +okCertificate(uchar *cert, int len, Thumbprint *table) +{ + uchar hash[SHA2_256dlen]; + char thumb[2*SHA2_256dlen+1]; + + if(table == nil){ + werrstr("no thumbprints provided"); + return 0; + } + if(cert == nil || len <= 0){ + werrstr("no certificate provided"); + return 0; + } + + sha1(cert, len, hash, nil); + if(okThumbprint(hash, SHA1dlen, table)) + return 1; + + sha2_256(cert, len, hash, nil); + if(okThumbprint(hash, SHA2_256dlen, table)) + return 1; + + len = enc64(thumb, sizeof(thumb), hash, SHA2_256dlen); + while(len > 0 && thumb[len-1] == '=') + len--; + thumb[len] = '\0'; + werrstr("sha256=%s", thumb); + + return 0; +} + static int -loadThumbprints(char *file, Thumbprint *table, Thumbprint *crltab) +loadThumbprints(char *file, char *tag, Thumbprint *table, Thumbprint *crltab, int depth) { Thumbprint *hd, *entry; char *line, *field[50]; - uchar sum[SHA1dlen]; + uchar hash[SHA2_256dlen]; Biobuf *bin; + int len, n; + if(depth > 8){ + werrstr("too many includes, last file %s", file); + return -1; + } if(access(file, AEXIST) < 0) return 0; /* not an error */ if((bin = Bopen(file, OREAD)) == nil) @@ -59,20 +96,30 @@ loadThumbprints(char *file, Thumbprint *table, Thumbprint *crltab) if(tokenize(line, field, nelem(field)) < 2) continue; if(strcmp(field[0], "#include") == 0){ - if(loadThumbprints(field[1], table, crltab) < 0) + if(loadThumbprints(field[1], tag, table, crltab, depth+1) < 0) goto err; continue; } - if(strcmp(field[0], "x509") != 0 || strncmp(field[1], "sha1=", 5) != 0) + if(strcmp(field[0], tag) != 0) continue; - field[1] += 5; - if(dec16(sum, SHA1dlen, field[1], strlen(field[1])) != SHA1dlen){ - werrstr("malformed x509 entry in %s: %s", file, field[1]); + if(strncmp(field[1], "sha1=", 5) == 0){ + field[1] += 5; + len = SHA1dlen; + } else if(strncmp(field[1], "sha256=", 7) == 0){ + field[1] += 7; + len = SHA2_256dlen; + } else { + continue; + } + n = strlen(field[1]); + if((n != len*2 || dec16(hash, len, field[1], n) != len) + && dec64(hash, len, field[1], n) != len){ + werrstr("malformed %s entry in %s: %s", tag, file, field[1]); goto err; } - if(crltab && okThumbprint(sum, crltab)) + if(crltab && okThumbprint(hash, len, crltab)) continue; - hd = tablehead(sum, table); + hd = tablehead(hash, table); if(hd->next == nil) entry = hd; else { @@ -81,7 +128,8 @@ loadThumbprints(char *file, Thumbprint *table, Thumbprint *crltab) entry->next = hd->next; } hd->next = entry; - memcpy(entry->sha1, sum, SHA1dlen); + entry->len = len; + memcpy(entry->hash, hash, len); } Bterm(bin); return 0; @@ -92,7 +140,7 @@ err: } Thumbprint * -initThumbprints(char *ok, char *crl) +initThumbprints(char *ok, char *crl, char *tag) { Thumbprint *table, *crltab; @@ -101,13 +149,13 @@ initThumbprints(char *ok, char *crl) if((crltab = malloc(ThumbTab * sizeof(*crltab))) == nil) goto err; memset(crltab, 0, ThumbTab * sizeof(*crltab)); - if(loadThumbprints(crl, crltab, nil) < 0) + if(loadThumbprints(crl, tag, crltab, nil, 0) < 0) goto err; } if((table = malloc(ThumbTab * sizeof(*table))) == nil) goto err; memset(table, 0, ThumbTab * sizeof(*table)); - if(loadThumbprints(ok, table, crltab) < 0){ + if(loadThumbprints(ok, tag, table, crltab, 0) < 0){ freeThumbprints(table); table = nil; }