Plan 9 from Bell Labs’s /usr/web/sources/contrib/mospak/tls-1.2/tls-ecdhe-ecdsa-and-chain.diff

Copyright © 2021 Plan 9 Foundation.
Distributed under the MIT License.
Download the Plan 9 distribution.


--- 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;
 }
 

Bell Labs OSI certified Powered by Plan 9

(Return to Plan 9 Home Page)

Copyright © 2021 Plan 9 Foundation. All Rights Reserved.
Comments to webmaster@9p.io.