Plan 9 from Bell Labs’s /usr/web/sources/contrib/mospak/tls-modern-client/tls-ecdhe-sni-client.patch

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


tlshand: TLS 1.2 ECDHE + SNI client support

Adds client-side ECDHE (X25519) + AEAD cipher suites and SNI
to the TLS 1.2 handshake in sys/src/libsec/port/tlshand.c.
A new helper rsapkcs1verify lands in libsec
(sys/src/libsec/port/rsaverify.c) for verifying
ServerKeyExchange signatures.

Security invariant: the SKE signature is the sole
authentication binding the server's cert to this session's
ephemeral DH parameters.  Without verifying it, ECDHE degrades
to anonymous DH and is trivially MITM-able.  verifyDHparams()
is called unconditionally and a non-nil return fails the
handshake.

Hostname and certificate-chain validation are NOT in this
patch.  They land in libsec-x509-chain-hostname and are wired
into tlsClient by tls-ecdsa-and-chain-integration.  Callers
that rely on cert authenticity must use the existing
thumbprint path (unchanged) or, once the later patches are
applied, opt in via conn->rootCAchain.

Scope limits:
    - X25519 only (P-256/P-384 land in tls-nist-ecdhe-curves).
    - RSA certs only (ECDSA lands in
      libsec-ecc-ecdsa-primitives + tls-ecdsa-and-chain-
      integration).
    - rsa_pkcs1_sha256 (0x0401) sigalg only.
    - Server-side SNI in tlsServer2 is not wired; the
      findExtension helper is in place for it.

rsapkcs1verify is ported from 9front's X509rsaverifydigest /
pkcs1unpadbuf pair, simplified to a single sigalg.

RFC 5246 §7.4 (TLS 1.2 handshake), §5 (PRF with SHA-256);
5289 (TLS_ECDHE_RSA_* suites); 6066 §3 (server_name/SNI);
7748 §5 (X25519 as named_group 0x001d); 8017 §8.2
(RSASSA-PKCS1-v1_5).  TLS 1.2 only; no 1.0/1.1 fallback.

--- sys/include/libsec.h
+++ sys/include/libsec.h
@@ -351,6 +351,7 @@
 uchar*		X509req(RSApriv *priv, char *subj, int *certlen);
 char*		X509verify(uchar *cert, int ncert, RSApub *pk);
 void		X509dump(uchar *cert, int ncert);
+char*		rsapkcs1verify(RSApub *pk, int sigalg, uchar *msg, ulong msglen, uchar *sig, int siglen);
 
 /*
  * elgamal
@@ -454,6 +455,7 @@
 	uchar	*sessionKey;
 	int	sessionKeylen;
 	char	*sessionConst;
+	char	*serverName;	/* SNI: set by client before tlsClient; set by tlsServer to observed SNI */
 } TLSconn;
 
 /* tlshand.c */
--- sys/src/libsec/port/mkfile
+++ sys/src/libsec/port/mkfile
@@ -14,7 +14,7 @@
 	probably_prime.c smallprimetest.c genprime.c dsaprimes.c\
 	gensafeprime.c genstrongprime.c\
 	rsagen.c rsafill.c rsaencrypt.c rsadecrypt.c rsaalloc.c \
-	rsaprivtopub.c x509.c decodepem.c \
+	rsaprivtopub.c rsaverify.c x509.c decodepem.c \
 	eggen.c egencrypt.c egdecrypt.c egalloc.c egprivtopub.c \
 	egsign.c egverify.c \
 	dsagen.c dsaalloc.c dsaprivtopub.c dsasign.c dsaverify.c \
--- sys/src/libsec/port/rsaverify.c
+++ sys/src/libsec/port/rsaverify.c
@@ -0,0 +1,89 @@
+#include <u.h>
+#include <libc.h>
+#include <mp.h>
+#include <libsec.h>
+
+/*
+ * RSA PKCS#1 v1.5 signature verification.
+ *
+ * Used by the TLS 1.2 client to verify ServerKeyExchange signatures,
+ * which is the sole authentication binding the server's cert to the
+ * ephemeral ECDHE parameters of the current session (without this check
+ * ECDHE degrades to anonymous DH, trivially MITM-able).
+ */
+
+/*
+ * DigestInfo prefix for SHA-256 per RFC 3447 §9.2:
+ *   SEQUENCE { SEQUENCE { OID sha256, NULL }, OCTET STRING [32] }
+ * Followed by the 32-byte SHA-256 digest.
+ */
+static uchar sha256DigestInfo[] = {
+	0x30, 0x31, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86,
+	0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, 0x05,
+	0x00, 0x04, 0x20,
+};
+
+/*
+ * Strip PKCS1 block-type-1 padding (00 01 FF..FF 00) from buf[0..len-1].
+ * Returns the length of the unpadded content, or -1 on malformed padding.
+ * Ported from 9front libsec/port/x509.c.
+ */
+static int
+pkcs1unpadbuf(uchar *buf, int len, mpint *modulus, int blocktype)
+{
+	uchar *p = buf + 1, *e = buf + len;
+
+	if(len < 1 || len != (mpsignif(modulus)-1)/8 || buf[0] != blocktype)
+		return -1;
+	while(p < e && *p == 0xFF)
+		p++;
+	if(p - buf <= 8 || p >= e || *p++ != 0x00)
+		return -1;
+	memmove(buf, p, len = e - p);
+	return len;
+}
+
+char*
+rsapkcs1verify(RSApub *pk, int sigalg, uchar *msg, ulong msglen, uchar *sig, int siglen)
+{
+	uchar digestinfo[sizeof(sha256DigestInfo) + SHA2_256dlen];
+	int dilen;
+	mpint *x, *y;
+	uchar *buf;
+	int len;
+	char *err;
+
+	/* TLS 1.2 SignatureAndHashAlgorithm: low byte = signature type (1 = RSA). */
+	if((sigalg & 0xff) != 0x01)
+		return "SKE signature not RSA";
+
+	/* BUG: only rsa_pkcs1_sha256 is advertised */
+	if(((sigalg >> 8) & 0xff) != 0x04){
+		werrstr("sigalg=%#x", sigalg);
+		return "unsupported SKE hash algorithm";
+	}
+
+	memmove(digestinfo, sha256DigestInfo, sizeof(sha256DigestInfo));
+	sha2_256(msg, msglen, digestinfo + sizeof(sha256DigestInfo), nil);
+	dilen = sizeof(sha256DigestInfo) + SHA2_256dlen;
+
+	/* RSA public operation on signature: s^e mod n */
+	x = betomp(sig, siglen, nil);
+	if(x == nil)
+		return "bad signature encoding";
+	y = rsaencrypt(pk, x, nil);
+	mpfree(x);
+	if(y == nil)
+		return "rsaencrypt failed";
+	len = mptobe(y, nil, 0, &buf);
+	mpfree(y);
+	if(buf == nil)
+		return "mptobe failed";
+
+	err = "bad signature";
+	len = pkcs1unpadbuf(buf, len, pk->n, 1);
+	if(len == dilen && tsmemcmp(buf, digestinfo, dilen) == 0)
+		err = nil;
+	free(buf);
+	return err;
+}
--- sys/src/libsec/port/tlshand.c
+++ sys/src/libsec/port/tlshand.c
@@ -66,6 +66,7 @@
 	int verset;		// version has been set
 	int ver2hi;		// server got a version 2 hello
 	int isClient;	// is this the client or server?
+	int cipher;	// negotiated cipher suite (for KE-type discrimination in msgSend)
 	Bytes *sid;		// SessionID
 	Bytes *cert;	// only last - no chain
 
@@ -97,7 +98,7 @@
 			Bytes*	sid;
 			Ints*	ciphers;
 			Bytes*	compressors;
-			Ints*	sigAlgs;
+			Bytes*	extensions;	/* raw extensions blob (inner, w/o outer 2-byte length) */
 		} clientHello;
 		struct {
 			int version;
@@ -105,6 +106,7 @@
 			Bytes*	sid;
 			int cipher;
 			int compressor;
+			Bytes*	extensions;
 		} serverHello;
 		struct {
 			int ncert;
@@ -116,6 +118,13 @@
 			Bytes **cas;
 		} certificateRequest;
 		struct {
+			int curve;		/* currently only X25519 */
+			Bytes *pubkey;		/* server's ephemeral DH pubkey (32 B for X25519) */
+			int sigalg;		/* TLS 1.2 SignatureAndHashAlgorithm uint16 */
+			Bytes *signature;	/* RSA PKCS1 signature over crandom|srandom|params */
+			Bytes *params;		/* raw signed params (curve_type|named_curve|publen|pub) */
+		} serverKeyExchange;
+		struct {
 			Bytes *key;
 		} clientKeyExchange;
 		Finished finished;
@@ -132,6 +141,9 @@
 	uchar srandom[RandomSize];	// server random
 	int clientVers;		// version in ClientHello
 	int vers;			// final version
+	/* X25519 ECDHE client ephemeral (only used when ECDHE suite selected) */
+	uchar ecdhe_priv[32];
+	uchar ecdhe_pub[32];
 	// byte generation and handshake checksum
 	void (*prf)(uchar*, int, uchar*, int, char*, uchar*, int, uchar*, int);
 	void (*setFinished)(TlsSec*, HandHash, uchar*, int);
@@ -238,20 +250,38 @@
 	TLS_DHE_RSA_WITH_AES_256_CBC_SHA	= 0X0039,
 	TLS_DH_anon_WITH_AES_256_CBC_SHA	= 0X003A,
 	TLS_EMPTY_RENEGOTIATION_INFO_SCSV	= 0x00FF,
-	CipherMax
+	CipherMax		/* upper bound of the weakCipher[] table; see below */
 };
 
+/*
+ * Modern cipher suites with TLS IDs outside the 0x0000..CipherMax range.
+ * Kept out of the enum above so weakCipher[] stays compactly sized.
+ * okCipher() treats c >= CipherMax as not-weak, which is correct here.
+ */
+enum {
+	TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256		= 0xC02F,
+	TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256	= 0xCCA8,
+};
+
 // compression methods
 enum {
 	CompressionNull = 0,
 	CompressionMax
 };
 
-// extensions
+// extensions (RFC 6066, 4492, 5246)
 enum {
-	ExtSigalgs = 0xd,
+	ExtSni			= 0x0000,
+	ExtSupportedGroups	= 0x000a,
+	ExtEcPointFormats	= 0x000b,
+	ExtSigalgs		= 0x000d,
 };
 
+// supported ECDHE named groups (we offer X25519 only)
+enum {
+	NamedGroupX25519 = 0x001d,
+};
+
 // signature algorithms
 enum {
 	RSA_PKCS1_SHA1   = 0x0201,
@@ -261,6 +291,10 @@
 };
 
 static Algs cipherAlgs[] = {
+	/* 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},
+	{"ccpoly96_aead", "clear", 2*(32+12), TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256},
+	/* legacy RSA-KE suites (no forward secrecy) */
 	{"rc4_128", "md5", 2*(16+MD5dlen), TLS_RSA_WITH_RC4_128_MD5},
 	{"rc4_128", "sha1", 2*(16+SHA1dlen), TLS_RSA_WITH_RC4_128_SHA},
 	{"3des_ede_cbc", "sha1", 2*(4*8+SHA1dlen), TLS_RSA_WITH_3DES_EDE_CBC_SHA},
@@ -272,13 +306,29 @@
 	CompressionNull,
 };
 
+/*
+ * 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) and modern servers don't pick it anyway.
+ */
 static int sigAlgs[] = {
 	RSA_PKCS1_SHA256,
-	RSA_PKCS1_SHA1,
 };
 
+/* supported groups (curves) we advertise in ClientHello */
+static uchar supportedGroups[] = {
+	NamedGroupX25519 >> 8, NamedGroupX25519 & 0xff,
+};
+
+/* EC point formats we support (only uncompressed) */
+static uchar ecPointFormats[] = {
+	0,	/* uncompressed */
+};
+
 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, char *serverName, int (*trace)(char*fmt, ...));
 
 static void	msgClear(Msg *m);
 static char* msgPrint(char *buf, int n, Msg *m);
@@ -336,6 +386,12 @@
 static Ints* makeints(int* buf, int len);
 static void freeints(Ints* b);
 
+static int	isECDHE(int tlsid);
+static Bytes*	findExtension(Bytes *ext, int type);
+static Bytes*	clientHelloExtensions(char *serverName);
+static uchar*	tlsSecECDHEc(TlsSec *sec, Bytes *Ys, int *npub);
+static char*	verifyDHparams(TlsSec *sec, Msg *m, Bytes *cert);
+
 //================= client/server ========================
 
 //	push TLS onto fd, returning new (application) file descriptor
@@ -429,7 +485,7 @@
 		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->serverName, conn->trace);
 	close(fd);
 	close(hand);
 	close(ctl);
@@ -633,13 +689,13 @@
 }
 
 static TlsConnection *
-tlsClient2(int ctl, int hand, uchar *csid, int ncsid, int (*trace)(char*fmt, ...))
+tlsClient2(int ctl, int hand, uchar *csid, int ncsid, char *serverName, int (*trace)(char*fmt, ...))
 {
 	TlsConnection *c;
 	Msg m;
 	uchar kd[MaxKeyData], *epm;
-	char *secrets;
-	int creq, nepm, rv;
+	char *secrets, *err;
+	int creq, nepm, rv, cipher, dhx;
 
 	if(!initCiphers())
 		return nil;
@@ -665,7 +721,7 @@
 	m.u.clientHello.ciphers = makeciphers();
 	m.u.clientHello.compressors = makebytes(compressors,sizeof(compressors));
 	if(c->clientVersion >= TLS12Version)
-		m.u.clientHello.sigAlgs = makeints(sigAlgs, nelem(sigAlgs));
+		m.u.clientHello.extensions = clientHelloExtensions(serverName);
 	if(!msgSend(c, &m, AFlush))
 		goto Err;
 	msgClear(&m);
@@ -687,7 +743,8 @@
 		tlsError(c, EIllegalParameter, "invalid server session identifier");
 		goto Err;
 	}
-	if(!setAlgs(c, m.u.serverHello.cipher)) {
+	cipher = m.u.serverHello.cipher;
+	if(!setAlgs(c, cipher)) {
 		tlsError(c, EIllegalParameter, "invalid cipher suite");
 		goto Err;
 	}
@@ -695,6 +752,7 @@
 		tlsError(c, EIllegalParameter, "invalid compression");
 		goto Err;
 	}
+	dhx = isECDHE(cipher);
 	msgClear(&m);
 
 	/* certificate */
@@ -709,14 +767,43 @@
 	c->cert = makebytes(m.u.certificate.certs[0]->data, m.u.certificate.certs[0]->len);
 	msgClear(&m);
 
-	/* server key exchange (optional) */
+	/* server key exchange (mandatory for ECDHE, forbidden for RSA-KE) */
 	if(!msgRecv(c, &m))
 		goto Err;
 	if(m.tag == HServerKeyExchange) {
-		tlsError(c, EUnexpectedMessage, "got an server key exchange");
+		if(!dhx){
+			tlsError(c, EUnexpectedMessage, "unexpected server key exchange");
+			goto Err;
+			// If enabling SKE for RSA-KE later, watch out for rollback
+			// attack described in Wagner Schneier 1996, section 4.4.
+		}
+		if(m.u.serverKeyExchange.curve != NamedGroupX25519) {
+			tlsError(c, EHandshakeFailure, "unsupported ECDHE named curve");
+			goto Err;
+		}
+		/* mirror crandom/srandom into TlsSec for signature hashing */
+		memmove(c->sec->srandom, c->srandom, RandomSize);
+		err = verifyDHparams(c->sec, &m, c->cert);
+		if(err != nil){
+			tlsError(c, EBadCertificate, "SKE signature: %s", err);
+			goto Err;
+		}
+		if(setVers(c->sec, c->version) < 0){
+			tlsError(c, EIllegalParameter, "setVers failed: %r");
+			goto Err;
+		}
+		epm = tlsSecECDHEc(c->sec, m.u.serverKeyExchange.pubkey, &nepm);
+		if(epm == nil){
+			tlsError(c, EHandshakeFailure, "ECDHE failed: %r");
+			goto Err;
+		}
+		setSecrets(c->sec, kd, c->nsecret);
+		msgClear(&m);
+		if(!msgRecv(c, &m))
+			goto Err;
+	} else if(dhx){
+		tlsError(c, EUnexpectedMessage, "expected server key exchange for ECDHE");
 		goto Err;
-		// If implementing this later, watch out for rollback attack
-		// described in Wagner Schneier 1996, section 4.4.
 	}
 
 	/* certificate request (optional) */
@@ -734,12 +821,16 @@
 	}
 	msgClear(&m);
 
-	if(tlsSecSecretc(c->sec, c->sid->data, c->sid->len, c->srandom,
-			c->cert->data, c->cert->len, c->version, &epm, &nepm,
-			kd, c->nsecret) < 0){
-		tlsError(c, EBadCertificate, "invalid x509/rsa certificate");
-		goto Err;
+	if(!dhx){
+		/* RSA key exchange: derive the pre-master, encrypt with cert pubkey, derive keys */
+		if(tlsSecSecretc(c->sec, c->sid->data, c->sid->len, c->srandom,
+				c->cert->data, c->cert->len, c->version, &epm, &nepm,
+				kd, c->nsecret) < 0){
+			tlsError(c, EBadCertificate, "invalid x509/rsa certificate");
+			goto Err;
+		}
 	}
+	/* for ECDHE: epm, nepm, kd, master secret were all set during SKE handling */
 	secrets = (char*)emalloc(2*c->nsecret);
 	enc64(secrets, 2*c->nsecret, kd, c->nsecret);
 	rv = fprint(c->ctl, "secret %s %s 1 %s", c->digest, c->enc, secrets);
@@ -897,17 +988,12 @@
 		memmove(p+1, m->u.clientHello.compressors->data, n);
 		p += n+1;
 
-		if(m->u.clientHello.sigAlgs != nil) {
-			n = m->u.clientHello.sigAlgs->len;
-			put16(p, 6 + 2*n);   /* length of extensions */
-			put16(p+2, ExtSigalgs);
-			put16(p+4, 2 + 2*n); /* length of extension content */
-			put16(p+6, 2*n);     /* length of algorithm list */
-			p += 8;
-			for(i = 0; i < n; i++) {
-				put16(p, m->u.clientHello.sigAlgs->data[i]);
-				p += 2;
-			}
+		if(m->u.clientHello.extensions != nil && m->u.clientHello.extensions->len > 0) {
+			n = m->u.clientHello.extensions->len;
+			put16(p, n);	// outer length of extensions vector
+			p += 2;
+			memmove(p, m->u.clientHello.extensions->data, n);
+			p += n;
 		}
 		break;
 	case HServerHello:
@@ -929,6 +1015,14 @@
 		p += 2;
 		p[0] = m->u.serverHello.compressor;
 		p += 1;
+
+		if(m->u.serverHello.extensions != nil && m->u.serverHello.extensions->len > 0) {
+			n = m->u.serverHello.extensions->len;
+			put16(p, n);
+			p += 2;
+			memmove(p, m->u.serverHello.extensions->data, n);
+			p += n;
+		}
 		break;
 	case HServerHelloDone:
 		break;
@@ -951,7 +1045,15 @@
 		break;
 	case HClientKeyExchange:
 		n = m->u.clientKeyExchange.key->len;
-		if(c->version != SSL3Version){
+		/*
+		 * length-prefix encoding differs by key-exchange algorithm:
+		 *   RSA-KE   EncryptedPreMasterSecret: opaque<0..2^16-1> (2-byte len, TLS; no prefix in SSL3)
+		 *   ECDHE    ClientECDiffieHellmanPublic: opaque<1..255>  (1-byte len)
+		 */
+		if(isECDHE(c->cipher)){
+			p[0] = n;
+			p += 1;
+		}else if(c->version != SSL3Version){
 			put16(p, n);
 			p += 2;
 		}
@@ -1139,7 +1241,7 @@
 		p += nn + 1;
 		n -= nn + 1;
 
-		/* extensions */
+		/* extensions: stored as raw blob (without the outer 2-byte length) */
 		if(n == 0)
 			break;
 		if(n < 2)
@@ -1147,29 +1249,11 @@
 		nx = get16(p);
 		p += 2;
 		n -= 2;
-		while(nx > 0){
-			if(n < nx || nx < 4)
-				goto Short;
-			i = get16(p);
-			nn = get16(p+2);
-			if(nx < nn+4)
-				goto Short;
-			nx -= nn+4;
-			p += 4;
-			n -= 4;
-			if(i == ExtSigalgs){
-				if(get16(p) != nn-2)
-					goto Short;
-				p += 2;
-				n -= 2;
-				nn -= 2;
-				m->u.clientHello.sigAlgs = newints(nn/2);
-				for(i = 0; i < nn; i += 2)
-					m->u.clientHello.sigAlgs->data[i >> 1] = get16(&p[i]);
-			}
-			p += nn;
-			n -= nn;
-		}
+		if(nx > n)
+			goto Short;
+		m->u.clientHello.extensions = makebytes(p, nx);
+		p += nx;
+		n -= nx;
 		break;
 	case HServerHello:
 		if(n < 2)
@@ -1194,8 +1278,61 @@
 			goto Short;
 		m->u.serverHello.cipher = get16(p);
 		m->u.serverHello.compressor = p[2];
-		n = 0;	/* skip extensions */
+		p += 3;
+		n -= 3;
+
+		/* extensions (optional, may be absent) */
+		if(n >= 2){
+			nx = get16(p);
+			p += 2;
+			n -= 2;
+			if(nx > n)
+				goto Short;
+			m->u.serverHello.extensions = makebytes(p, nx);
+			p += nx;
+			n -= nx;
+		}
 		break;
+	case HServerKeyExchange:
+		/*
+		 * ECDHE ServerKeyExchange wire format: curve_type (1) must be
+		 * named_curve (3), followed by named_curve (2), pubkey_len (1),
+		 * pubkey, sig_alg (2), sig_len (2), sig.  Earlier versions without
+		 * explicit sig_alg are not supported — we negotiate TLS 1.2.
+		 */
+		{
+			uchar *start = p;
+
+			if(n < 1 || *p != 3)
+				goto Short;	/* curve type must be named_curve */
+			p++, n--;
+			if(n < 2)
+				goto Short;
+			m->u.serverKeyExchange.curve = get16(p);
+			p += 2, n -= 2;
+			if(n < 1 || p[0] > n-1)
+				goto Short;
+			nn = p[0];
+			p++, n--;
+			m->u.serverKeyExchange.pubkey = makebytes(p, nn);
+			p += nn, n -= nn;
+			/* the signed params are exactly the bytes we just consumed */
+			m->u.serverKeyExchange.params = makebytes(start, p - start);
+
+			if(n < 2)
+				goto Short;
+			m->u.serverKeyExchange.sigalg = get16(p);
+			p += 2, n -= 2;
+			if(n < 2)
+				goto Short;
+			nn = get16(p);
+			p += 2, n -= 2;
+			if(nn > n)
+				goto Short;
+			m->u.serverKeyExchange.signature = makebytes(p, nn);
+			p += nn, n -= nn;
+		}
+		break;
 	case HCertificate:
 		if(n < 3)
 			goto Short;
@@ -1322,10 +1459,11 @@
 		freeints(m->u.clientHello.ciphers);
 		m->u.clientHello.ciphers = nil;
 		freebytes(m->u.clientHello.compressors);
-		freeints(m->u.clientHello.sigAlgs);
+		freebytes(m->u.clientHello.extensions);
 		break;
 	case HServerHello:
-		freebytes(m->u.clientHello.sid);
+		freebytes(m->u.serverHello.sid);
+		freebytes(m->u.serverHello.extensions);
 		break;
 	case HCertificate:
 		for(i=0; i<m->u.certificate.ncert; i++)
@@ -1340,6 +1478,11 @@
 		break;
 	case HServerHelloDone:
 		break;
+	case HServerKeyExchange:
+		freebytes(m->u.serverKeyExchange.pubkey);
+		freebytes(m->u.serverKeyExchange.signature);
+		freebytes(m->u.serverKeyExchange.params);
+		break;
 	case HClientKeyExchange:
 		freebytes(m->u.clientKeyExchange.key);
 		break;
@@ -1407,8 +1550,8 @@
 		bs = bytesPrint(bs, be, "\tsid: ", m->u.clientHello.sid, "\n");
 		bs = intsPrint(bs, be, "\tciphers: ", m->u.clientHello.ciphers, "\n");
 		bs = bytesPrint(bs, be, "\tcompressors: ", m->u.clientHello.compressors, "\n");
-		if(m->u.clientHello.sigAlgs != nil)
-			bs = intsPrint(bs, be, "\tsigAlgs: ", m->u.clientHello.sigAlgs, "\n");
+		if(m->u.clientHello.extensions != nil)
+			bs = bytesPrint(bs, be, "\textensions: ", m->u.clientHello.extensions, "\n");
 		break;
 	case HServerHello:
 		bs = seprint(bs, be, "ServerHello\n");
@@ -1420,7 +1563,16 @@
 		bs = bytesPrint(bs, be, "\tsid: ", m->u.serverHello.sid, "\n");
 		bs = seprint(bs, be, "\tcipher: %.4x\n", m->u.serverHello.cipher);
 		bs = seprint(bs, be, "\tcompressor: %.2x\n", m->u.serverHello.compressor);
+		if(m->u.serverHello.extensions != nil)
+			bs = bytesPrint(bs, be, "\textensions: ", m->u.serverHello.extensions, "\n");
 		break;
+	case HServerKeyExchange:
+		bs = seprint(bs, be, "ServerKeyExchange\n");
+		bs = seprint(bs, be, "\tcurve: %.4x  sigalg: %.4x\n",
+			m->u.serverKeyExchange.curve, m->u.serverKeyExchange.sigalg);
+		bs = bytesPrint(bs, be, "\tpubkey: ", m->u.serverKeyExchange.pubkey, "\n");
+		bs = bytesPrint(bs, be, "\tsignature: ", m->u.serverKeyExchange.signature, "\n");
+		break;
 	case HCertificate:
 		bs = seprint(bs, be, "Certificate\n");
 		for(i=0; i<m->u.certificate.ncert; i++)
@@ -1559,6 +1711,7 @@
 			c->enc = cipherAlgs[i].enc;
 			c->digest = cipherAlgs[i].digest;
 			c->nsecret = cipherAlgs[i].nsecret;
+			c->cipher = a;
 			if(c->nsecret > MaxKeyData)
 				return 0;
 			return 1;
@@ -1971,6 +2124,189 @@
 }
 
 static int
+isECDHE(int tlsid)
+{
+	switch(tlsid){
+	case TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256:
+	case TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256:
+		return 1;
+	}
+	return 0;
+}
+
+/*
+ * Scan a raw extensions blob (as stored in Msg.u.*.extensions — without
+ * the outer 2-byte length) for the given extension type.  Returns a new
+ * Bytes holding the extension's inner content, or nil if not found.
+ */
+static Bytes*
+findExtension(Bytes *ext, int type)
+{
+	uchar *p, *e;
+	int t, nn;
+
+	if(ext == nil || ext->len == 0)
+		return nil;
+	p = ext->data;
+	e = p + ext->len;
+	while(p + 4 <= e){
+		t = get16(p);
+		nn = get16(p+2);
+		p += 4;
+		if(p + nn > e)
+			return nil;
+		if(t == type)
+			return makebytes(p, nn);
+		p += nn;
+	}
+	return nil;
+}
+
+/*
+ * Build the ClientHello extensions blob (inner form, without the outer
+ * 2-byte length).  Contains: server_name (when given, RFC 6066 §3),
+ * supported_groups = [X25519], ec_point_formats = [uncompressed],
+ * signature_algorithms.
+ */
+static Bytes*
+clientHelloExtensions(char *serverName)
+{
+	uchar buf[512], *p;
+	int n, snlen, i;
+
+	p = buf;
+
+	if(serverName != nil && *serverName != 0){
+		snlen = strlen(serverName);
+		put16(p, ExtSni);
+		put16(p+2, snlen + 5);		/* ext content = list_len(2) + entry(1+2+snlen) */
+		put16(p+4, snlen + 3);		/* server_name_list length */
+		p[6] = 0;			/* NameType = host_name */
+		put16(p+7, snlen);
+		memmove(p+9, serverName, snlen);
+		p += 9 + snlen;
+	}
+
+	put16(p, ExtSupportedGroups);
+	put16(p+2, sizeof(supportedGroups) + 2);
+	put16(p+4, sizeof(supportedGroups));
+	memmove(p+6, supportedGroups, sizeof(supportedGroups));
+	p += 6 + sizeof(supportedGroups);
+
+	put16(p, ExtEcPointFormats);
+	put16(p+2, sizeof(ecPointFormats) + 1);
+	p[4] = sizeof(ecPointFormats);
+	memmove(p+5, ecPointFormats, sizeof(ecPointFormats));
+	p += 5 + sizeof(ecPointFormats);
+
+	n = nelem(sigAlgs);
+	put16(p, ExtSigalgs);
+	put16(p+2, 2*n + 2);
+	put16(p+4, 2*n);
+	p += 6;
+	for(i = 0; i < n; i++){
+		put16(p, sigAlgs[i]);
+		p += 2;
+	}
+
+	return makebytes(buf, p - buf);
+}
+
+/*
+ * 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.
+ */
+static char*
+verifyDHparams(TlsSec *sec, Msg *m, Bytes *cert)
+{
+	uchar *blob;
+	int bloblen;
+	RSApub *pk;
+	char *err;
+
+	if(cert == nil || cert->len == 0)
+		return "no server certificate";
+	if(m->u.serverKeyExchange.signature == nil ||
+	   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";
+
+	bloblen = 2*RandomSize + m->u.serverKeyExchange.params->len;
+	blob = emalloc(bloblen);
+	memmove(blob, sec->crandom, RandomSize);
+	memmove(blob + RandomSize, sec->srandom, RandomSize);
+	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);
+
+	free(blob);
+	rsapubfree(pk);
+	return err;
+}
+
+/*
+ * X25519 ECDHE client side.  Generates our ephemeral keypair, computes
+ * the shared secret using the server's public key Ys, installs the
+ * shared secret as the pre-master for setMasterSecret.  Returns a new
+ * malloc'd buffer with our public key to send in ClientKeyExchange;
+ * *npub is set to its length.  Caller must free on success.
+ */
+static uchar*
+tlsSecECDHEc(TlsSec *sec, Bytes *Ys, int *npub)
+{
+	Bytes *pm;
+	uchar Ypeer[32], Z[32];
+	uchar *pub;
+
+	if(Ys == nil || Ys->len != 32){
+		werrstr("unexpected X25519 pubkey length");
+		return nil;
+	}
+
+	curve25519_dh_new(sec->ecdhe_priv, sec->ecdhe_pub);
+	pub = emalloc(32);
+	memmove(pub, sec->ecdhe_pub, 32);
+
+	/*
+	 * curve25519_dh_finish(x, y, z) zeros both x (priv) and y (peer pubkey)
+	 * after computing the shared secret into z.  If y and z alias the same
+	 * buffer, the final memset(y) wipes the shared secret we just computed.
+	 * Use distinct buffers: Ypeer holds the server's pubkey input, Z receives
+	 * the shared secret output.
+	 */
+	memmove(Ypeer, Ys->data, 32);
+	if(!curve25519_dh_finish(sec->ecdhe_priv, Ypeer, Z)){
+		memset(pub, 0, 32);
+		free(pub);
+		memset(Ypeer, 0, sizeof(Ypeer));
+		memset(Z, 0, sizeof(Z));
+		werrstr("degenerate DH shared secret");
+		return nil;
+	}
+
+	pm = newbytes(32);
+	memmove(pm->data, Z, 32);
+	memset(Z, 0, sizeof(Z));
+
+	setMasterSecret(sec, pm);
+	memset(pm->data, 0, pm->len);
+	freebytes(pm);
+
+	*npub = 32;
+	return pub;
+}
+
+static int
 tlsSecFinished(TlsSec *sec, HandHash hs, uchar *fin, int nfin, int isclient)
 {
 	if(sec->nfin != nfin){
@@ -2065,11 +2401,15 @@
 
 /*
  * set the master secret from the pre-master secret.
+ * pm->len differs by key-exchange algorithm: RSA-KE uses a 48-byte
+ * pre-master, ECDHE X25519 produces a 32-byte shared secret, ECDHE P-256
+ * produces 32 bytes, etc.  Hard-coding MasterSecretSize (48) here would
+ * read past the end of pm->data on any non-RSA-KE path.
  */
 static void
 setMasterSecret(TlsSec *sec, Bytes *pm)
 {
-	(*sec->prf)(sec->sec, MasterSecretSize, pm->data, MasterSecretSize, "master secret",
+	(*sec->prf)(sec->sec, MasterSecretSize, pm->data, pm->len, "master secret",
 			sec->crandom, RandomSize, sec->srandom, RandomSize);
 }
 

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.