--- sys/include/libsec.h
+++ sys/include/libsec.h
@@ -509,6 +509,7 @@ typedef struct TLSconn{
int sessionKeylen;
char *sessionConst;
char *serverName; /* SNI: set by client before tlsClient; set by tlsServer to observed SNI */
+ PEMChain *rootCAchain; /* trust anchors for X509verifychain; nil = no chain validation */
} TLSconn;
/* tlshand.c */
--- sys/src/9/port/devtls.c
+++ sys/src/9/port/devtls.c
@@ -1729,8 +1729,11 @@ tlswrite(Chan *c, void *a, long n, vlong off)
(*ea->initkey)(ea, tos, &x[2 * ha->maclen], &x[2 * ha->maclen + 2 * ea->keylen]);
(*ea->initkey)(ea, toc, &x[2 * ha->maclen + ea->keylen], &x[2 * ha->maclen + 2 * ea->keylen + ea->ivlen]);
- if(!tos->mac || !tos->enc || !tos->dec
- || !toc->mac || !toc->enc || !toc->dec)
+ /* Either legacy (mac+enc+dec) or AEAD (aead_enc+aead_dec)
+ * pathways must be fully wired on both directions. */
+ if(!tos->mac || !toc->mac
+ || ((!tos->enc || !tos->dec || !toc->enc || !toc->dec)
+ && (!tos->aead_enc || !tos->aead_dec || !toc->aead_enc || !toc->aead_dec)))
error("missing algorithm implementations");
if(strtol(cb->f[3], nil, 0) == 0){
tr->in.new = tos;
--- sys/src/9k/port/devtls.c
+++ sys/src/9k/port/devtls.c
@@ -1729,8 +1729,11 @@ tlswrite(Chan *c, void *a, long n, vlong off)
(*ea->initkey)(ea, tos, &x[2 * ha->maclen], &x[2 * ha->maclen + 2 * ea->keylen]);
(*ea->initkey)(ea, toc, &x[2 * ha->maclen + ea->keylen], &x[2 * ha->maclen + 2 * ea->keylen + ea->ivlen]);
- if(!tos->mac || !tos->enc || !tos->dec
- || !toc->mac || !toc->enc || !toc->dec)
+ /* Either legacy (mac+enc+dec) or AEAD (aead_enc+aead_dec)
+ * pathways must be fully wired on both directions. */
+ if(!tos->mac || !toc->mac
+ || ((!tos->enc || !tos->dec || !toc->enc || !toc->dec)
+ && (!tos->aead_enc || !tos->aead_dec || !toc->aead_enc || !toc->aead_dec)))
error("missing algorithm implementations");
if(strtol(cb->f[3], nil, 0) == 0){
tr->in.new = tos;
--- sys/src/cmd/hget.c
+++ sys/src/cmd/hget.c
@@ -353,6 +353,7 @@ dohttp(URL *u, URL *px, Range *r, Out *out, long mtime
TLSconn conn;
memset(&conn, 0, sizeof conn);
+ conn.serverName = u->host; /* SNI: server requires it */
tfd = tlsClient(fd, &conn);
if(tfd < 0){
fprint(2, "tlsClient: %r\n");
--- sys/src/cmd/webfs/fns.h
+++ sys/src/cmd/webfs/fns.h
@@ -34,7 +34,7 @@ void httpclose(Client*);
void httpclose(Client*);
/* io.c */
-int iotlsdial(Ioproc*, char*, char*, char*, int*, int);
+int iotlsdial(Ioproc*, char*, char*, char*, int*, int, char*);
int ioprint(Ioproc*, int, char*, ...);
#pragma varargck argpos ioprint 3
--- sys/src/cmd/webfs/http.c
+++ sys/src/cmd/webfs/http.c
@@ -286,7 +286,7 @@ httpopen(Client *c, Url *url)
fprint(2, "dial %s\n", hs->netaddr);
fprint(2, "dial port: %s\n", url->port);
}
- fd = iotlsdial(io, hs->netaddr, 0, 0, 0, url->ischeme==UShttps);
+ fd = iotlsdial(io, hs->netaddr, 0, 0, 0, url->ischeme==UShttps, url->host);
if(fd < 0){
Error:
if(httpdebug)
--- sys/src/cmd/webfs/io.c
+++ sys/src/cmd/webfs/io.c
@@ -45,7 +45,7 @@ _iotlsdial(va_list *arg)
static long
_iotlsdial(va_list *arg)
{
- char *addr, *local, *dir;
+ char *addr, *local, *dir, *host;
int *cfdp, fd, tfd, usetls;
TLSconn conn;
@@ -54,6 +54,7 @@ _iotlsdial(va_list *arg)
dir = va_arg(*arg, char*);
cfdp = va_arg(*arg, int*);
usetls = va_arg(*arg, int);
+ host = va_arg(*arg, char*);
fd = dial(addr, local, dir, cfdp);
if(fd < 0)
@@ -62,8 +63,7 @@ _iotlsdial(va_list *arg)
return fd;
memset(&conn, 0, sizeof conn);
- /* does no good, so far anyway */
- // conn.chain = readcertchain("/sys/lib/ssl/vsignss.pem");
+ conn.serverName = host; /* SNI: server picks cert by it */
tfd = tlsClient(fd, &conn);
close(fd);
@@ -78,7 +78,7 @@ int
}
int
-iotlsdial(Ioproc *io, char *addr, char *local, char *dir, int *cfdp, int usetls)
+iotlsdial(Ioproc *io, char *addr, char *local, char *dir, int *cfdp, int usetls, char *host)
{
- return iocall(io, _iotlsdial, addr, local, dir, cfdp, usetls);
+ return iocall(io, _iotlsdial, addr, local, dir, cfdp, usetls, host);
}
--- sys/src/libsec/port/tlshand.c
+++ sys/src/libsec/port/tlshand.c
@@ -260,7 +260,9 @@ enum {
* okCipher() treats c >= CipherMax as not-weak, which is correct here.
*/
enum {
+ TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 = 0xC02B,
TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 = 0xC02F,
+ TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256 = 0xCCA9,
TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256 = 0xCCA8,
};
@@ -270,12 +272,18 @@ enum {
CompressionMax
};
-// extensions (RFC 6066, 4492, 5246)
+// extensions (RFC 6066, 4492, 5246, 7301, 7627, 5746, 8446)
enum {
ExtSni = 0x0000,
+ ExtStatusRequest = 0x0005,
ExtSupportedGroups = 0x000a,
ExtEcPointFormats = 0x000b,
ExtSigalgs = 0x000d,
+ ExtAlpn = 0x0010,
+ ExtExtendedMasterSecret = 0x0017,
+ ExtSessionTicket = 0x0023,
+ ExtSupportedVersions = 0x002b,
+ ExtRenegotiationInfo = 0xff01,
};
// supported ECDHE named groups (we offer X25519 only)
@@ -288,7 +296,9 @@ enum {
RSA_PKCS1_SHA1 = 0x0201,
RSA_PKCS1_SHA256 = 0x0401,
RSA_PKCS1_SHA384 = 0x0501,
- RSA_PKCS1_SHA512 = 0x0601
+ RSA_PKCS1_SHA512 = 0x0601,
+ ECDSA_SECP256R1_SHA256 = 0x0403,
+ ECDSA_SECP384R1_SHA384 = 0x0503,
};
/* Algs.flags: cert-key-type gate used by okCipher (RFC 4492 Section 2.2). */
@@ -298,6 +308,9 @@ static Algs cipherAlgs[] = {
};
static Algs cipherAlgs[] = {
+ /* ECDHE_ECDSA + AEAD -- preferred when server has an ECDSA cert */
+ {"aes_128_gcm_aead", "clear", 2*(16+4), TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, AlgEcSign},
+ {"ccpoly96_aead", "clear", 2*(32+12), TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256, AlgEcSign},
/* ECDHE_RSA + AEAD: preferred, safe against MITM
* given SKE-sig verification */
{"aes_128_gcm_aead", "clear", 2*(16+4), TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, AlgRsaSign},
@@ -315,14 +328,19 @@ static uchar compressors[] = {
};
/*
- * Advertised signature_algorithms. Keep in sync with rsapkcs1verify
- * (which only handles SHA-256 today); advertising a hash we don't
- * verify creates a failure mode where the server picks it and we
- * then reject the SKE signature. SHA-1 for TLS 1.2 signatures is
- * deprecated (RFC 9155); servers don't pick it anyway.
+ * Advertised signature_algorithms. Keep in sync with verifyDHparams
+ * (which dispatches RSA via rsapkcs1verify and ECDSA via
+ * X509ecdsaverifydigest); advertising a hash we don't verify creates
+ * a failure mode where the server picks it and we then reject the
+ * SKE signature. SHA-1 for TLS 1.2 signatures is deprecated
+ * (RFC 9155); servers don't pick it anyway.
*/
static int sigAlgs[] = {
+ ECDSA_SECP256R1_SHA256,
+ ECDSA_SECP384R1_SHA384,
RSA_PKCS1_SHA256,
+ RSA_PKCS1_SHA384,
+ RSA_PKCS1_SHA512,
};
/* supported groups (curves) we advertise in ClientHello */
@@ -337,7 +355,8 @@ static TlsConnection *tlsClient2(int ctl, int hand, uc
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,
- char *serverName, int (*trace)(char*fmt, ...));
+ char *serverName, PEMChain *rootCA,
+ int (*trace)(char*fmt, ...));
static void msgClear(Msg *m);
static char* msgPrint(char *buf, int n, Msg *m);
@@ -350,7 +369,7 @@ static int setAlgs(TlsConnection *c, int a);
static void tlsConnectionFree(TlsConnection *c);
static int setAlgs(TlsConnection *c, int a);
-static int okCipher(Ints *cv);
+static int okCipher(TlsConnection *c, Ints *cv);
static int okCompression(Bytes *cv);
static int initCiphers(void);
static Ints* makeciphers(void);
@@ -500,7 +519,7 @@ tlsClient(int fd, TLSconn *conn)
return -1;
}
fprint(ctl, "fd %d 0x%x", fd, ProtocolVersion);
- tls = tlsClient2(ctl, hand, conn->sessionID, conn->sessionIDlen, conn->serverName, conn->trace);
+ tls = tlsClient2(ctl, hand, conn->sessionID, conn->sessionIDlen, conn->serverName, conn->rootCAchain, conn->trace);
close(fd);
close(hand);
close(ctl);
@@ -571,7 +590,7 @@ tlsServer2(int ctl, int hand, uchar *cert, int ncert,
}
memmove(c->crandom, m.u.clientHello.random, RandomSize);
- cipher = okCipher(m.u.clientHello.ciphers);
+ cipher = okCipher(c, m.u.clientHello.ciphers);
if(cipher < 0) {
// reply with EInsufficientSecurity if we know that's the case
if(cipher == -2)
@@ -753,7 +772,7 @@ static TlsConnection *
}
static TlsConnection *
-tlsClient2(int ctl, int hand, uchar *csid, int ncsid, char *serverName, int (*trace)(char*fmt, ...))
+tlsClient2(int ctl, int hand, uchar *csid, int ncsid, char *serverName, PEMChain *rootCA, int (*trace)(char*fmt, ...))
{
TlsConnection *c;
Msg m;
@@ -829,6 +848,36 @@ tlsClient2(int ctl, int hand, uchar *csid, int ncsid,
goto Err;
}
c->cert = makebytes(m.u.certificate.certs[0]->data, m.u.certificate.certs[0]->len);
+
+ /*
+ * Full X.509 chain and hostname verification (RFC 5280 + RFC 6125).
+ * Skipped when rootCA is nil to preserve the thumbprint-only trust
+ * model for callers that haven't yet provided trust anchors.
+ */
+ if(rootCA != nil){
+ PEMChain *ch, *tc;
+ char *verr;
+ int i;
+
+ ch = nil;
+ for(i = m.u.certificate.ncert - 1; i >= 0; i--){
+ tc = emalloc(sizeof(PEMChain));
+ tc->next = ch;
+ tc->pem = m.u.certificate.certs[i]->data;
+ tc->pemlen = m.u.certificate.certs[i]->len;
+ ch = tc;
+ }
+ verr = X509verifychain(ch, rootCA, serverName);
+ while(ch != nil){
+ tc = ch->next;
+ free(ch); /* frees only the list nodes; pem pointers belong to m */
+ ch = tc;
+ }
+ if(verr != nil){
+ tlsError(c, EBadCertificate, "cert verify: %s", verr);
+ goto Err;
+ }
+ }
msgClear(&m);
/* server key exchange (mandatory for ECDHE, forbidden for RSA-KE) */
@@ -1814,20 +1863,20 @@ static int
}
static int
-okCipher(Ints *cv)
+okCipher(TlsConnection *c, Ints *cv)
{
- int weak, i, j, c;
+ int weak, i, j, id;
weak = 1;
for(i = 0; i < cv->len; i++) {
- c = cv->data[i];
- if(c >= CipherMax)
+ id = cv->data[i];
+ if(id >= CipherMax)
weak = 0;
else
- weak &= weakCipher[c];
+ weak &= weakCipher[id];
for(j = 0; j < nelem(cipherAlgs); j++)
- if(cipherAlgs[j].ok && cipherAlgs[j].tlsid == c)
- return c;
+ if(cipherAlgs[j].ok && cipherAlgs[j].tlsid == id)
+ return id;
}
if(weak)
return -2;
@@ -2222,6 +2271,8 @@ isECDHE(int tlsid)
switch(tlsid){
case TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256:
case TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256:
+ case TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256:
+ case TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256:
return 1;
}
return 0;
@@ -2306,18 +2357,91 @@ clientHelloExtensions(char *serverName)
}
/*
+ * Strict DER-form check on an ECDSA-Sig-Value (RFC 5480 + X9.62):
+ * SEQUENCE { INTEGER r, INTEGER s }
+ * Definite short or long-form length, INTEGER tags, no leading zero
+ * unless required for sign-bit clear, no trailing bytes. Defends
+ * against signature-malleability vectors. Returns 0 on conforming
+ * to this shape, -1 otherwise.
+ */
+static int
+derStrictCheck(uchar *sig, int siglen)
+{
+ uchar *p, *e, *seqEnd;
+ int seqLen, lenBytes, intLen, i;
+
+ if(siglen < 8)
+ return -1;
+ p = sig;
+ e = sig + siglen;
+ if(*p++ != 0x30) /* SEQUENCE tag */
+ return -1;
+ /* SEQUENCE length: short form < 0x80, long form 0x81/0x82 only. */
+ if((*p & 0x80) == 0){
+ seqLen = *p++;
+ }else{
+ lenBytes = *p++ & 0x7f;
+ if(lenBytes < 1 || lenBytes > 2 || p + lenBytes > e)
+ return -1;
+ seqLen = 0;
+ for(i = 0; i < lenBytes; i++)
+ seqLen = (seqLen << 8) | *p++;
+ /* minimum-length encoding: a one-byte form is required when len<128. */
+ if(lenBytes == 1 && seqLen < 0x80)
+ return -1;
+ }
+ if(p + seqLen != e) /* no trailing bytes after SEQUENCE */
+ return -1;
+ seqEnd = e;
+ /* r INTEGER */
+ if(p + 2 > seqEnd || *p++ != 0x02)
+ return -1;
+ if((*p & 0x80) != 0) /* indefinite-length forbidden */
+ return -1;
+ intLen = *p++;
+ if(intLen < 1 || p + intLen > seqEnd)
+ return -1;
+ /* Minimum-length: leading 0x00 only if next byte has high bit set. */
+ if(intLen >= 2 && p[0] == 0x00 && (p[1] & 0x80) == 0)
+ return -1;
+ if(p[0] & 0x80) /* MSB set -> negative; ECDSA r,s must be positive */
+ return -1;
+ p += intLen;
+ /* s INTEGER */
+ if(p + 2 > seqEnd || *p++ != 0x02)
+ return -1;
+ if((*p & 0x80) != 0)
+ return -1;
+ intLen = *p++;
+ if(intLen < 1 || p + intLen != seqEnd)
+ return -1;
+ if(intLen >= 2 && p[0] == 0x00 && (p[1] & 0x80) == 0)
+ return -1;
+ if(p[0] & 0x80)
+ return -1;
+ return 0;
+}
+
+/*
* Verify the ServerKeyExchange signature, binding the server's cert to
* the ephemeral DH parameters for this session. Returns nil on success,
* error string otherwise. The caller MUST treat any non-nil return as
* a handshake failure -- otherwise ECDHE degenerates into anonymous DH
* and is trivially MITM-able.
+ *
+ * RFC 5246 Section 7.4.1.4.1 / RFC 4492 Section 5.4: the SKE signature
+ * is over crandom || srandom || ServerECDHParams. The sigalg's low
+ * byte selects the signature scheme (1 = RSA, 3 = ECDSA); the high
+ * byte selects the hash (4 = SHA-256, 5 = SHA-384, 6 = SHA-512).
*/
static char*
verifyDHparams(TlsSec *sec, Msg *m, Bytes *cert)
{
- uchar *blob;
- int bloblen;
- RSApub *pk;
+ uchar *blob, digest[SHA2_384dlen];
+ int bloblen, sigalg, digestlen;
+ RSApub *rpk;
+ ECpub *epk;
+ ECdomain dom;
char *err;
if(cert == nil || cert->len == 0)
@@ -2326,10 +2450,7 @@ verifyDHparams(TlsSec *sec, Msg *m, Bytes *cert)
m->u.serverKeyExchange.params == nil)
return "no SKE signature or params";
- pk = X509toRSApub(cert->data, cert->len, nil, 0);
- if(pk == nil)
- return "bad certificate";
-
+ sigalg = m->u.serverKeyExchange.sigalg;
bloblen = 2*RandomSize + m->u.serverKeyExchange.params->len;
blob = emalloc(bloblen);
memmove(blob, sec->crandom, RandomSize);
@@ -2337,13 +2458,56 @@ verifyDHparams(TlsSec *sec, Msg *m, Bytes *cert)
memmove(blob + 2*RandomSize, m->u.serverKeyExchange.params->data,
m->u.serverKeyExchange.params->len);
- err = rsapkcs1verify(pk, m->u.serverKeyExchange.sigalg,
- blob, bloblen,
- m->u.serverKeyExchange.signature->data,
- m->u.serverKeyExchange.signature->len);
+ switch(sigalg & 0xff){
+ case 0x01: /* RSA PKCS#1 v1.5 */
+ rpk = X509toRSApub(cert->data, cert->len, nil, 0);
+ if(rpk == nil){
+ free(blob);
+ return "bad certificate";
+ }
+ err = rsapkcs1verify(rpk, sigalg, blob, bloblen,
+ m->u.serverKeyExchange.signature->data,
+ m->u.serverKeyExchange.signature->len);
+ rsapubfree(rpk);
+ break;
+ case 0x03: /* ECDSA */
+ if(derStrictCheck(m->u.serverKeyExchange.signature->data,
+ m->u.serverKeyExchange.signature->len) < 0){
+ free(blob);
+ return "ECDSA signature not strict-DER";
+ }
+ switch((sigalg >> 8) & 0xff){
+ case 0x04: /* SHA-256 */
+ sha2_256(blob, bloblen, digest, nil);
+ digestlen = SHA2_256dlen;
+ break;
+ case 0x05: /* SHA-384 */
+ sha2_384(blob, bloblen, digest, nil);
+ digestlen = SHA2_384dlen;
+ break;
+ default:
+ free(blob);
+ werrstr("sigalg=%#x", sigalg);
+ return "unsupported ECDSA hash";
+ }
+ epk = X509toECpub(cert->data, cert->len, nil, 0, &dom);
+ if(epk == nil){
+ free(blob);
+ return "bad certificate";
+ }
+ err = X509ecdsaverifydigest(
+ m->u.serverKeyExchange.signature->data,
+ m->u.serverKeyExchange.signature->len,
+ digest, digestlen, &dom, epk);
+ ecpubfree(epk);
+ ecdomfree(&dom);
+ break;
+ default:
+ err = "SKE signature not RSA or ECDSA";
+ break;
+ }
free(blob);
- rsapubfree(pk);
return err;
}
|