Plan 9 from Bell Labs’s /usr/web/sources/contrib/mospak/tls-1.2/tls-nist-ecdhe-curves.diff

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


--- sys/src/libsec/port/tlshand.c
+++ sys/src/libsec/port/tlshand.c
@@ -88,6 +88,7 @@ typedef struct TlsConnection{
 	// for finished messages
 	HandHash	hs;	// handshake hash
 	Finished	finished;
+	int	serverCurve;	/* negotiated ECDHE group, 0 = none */
 } TlsConnection;
 
 typedef struct Msg{
@@ -286,9 +287,11 @@ enum {
 	ExtRenegotiationInfo	= 0xff01,
 };
 
-// supported ECDHE named groups (we offer X25519 only)
+// supported ECDHE named groups
 enum {
-	NamedGroupX25519 = 0x001d,
+	NamedGroupX25519	= 0x001d,
+	NamedGroupSecp256r1	= 0x0017,
+	NamedGroupSecp384r1	= 0x0018,
 };
 
 // signature algorithms
@@ -346,6 +349,8 @@ static uchar supportedGroups[] = {
 /* supported groups (curves) we advertise in ClientHello */
 static uchar supportedGroups[] = {
 	NamedGroupX25519 >> 8, NamedGroupX25519 & 0xff,
+	NamedGroupSecp256r1 >> 8, NamedGroupSecp256r1 & 0xff,
+	NamedGroupSecp384r1 >> 8, NamedGroupSecp384r1 & 0xff,
 };
 
 /* EC point formats we support (only uncompressed) */
@@ -424,6 +429,7 @@ static uchar*	tlsSecECDHEc(TlsSec *sec, Bytes *Ys, int
 static Bytes*	findExtension(Bytes *ext, int type);
 static Bytes*	clientHelloExtensions(char *serverName);
 static uchar*	tlsSecECDHEc(TlsSec *sec, Bytes *Ys, int *npub);
+static uchar*	tlsSecECDHEcNist(TlsSec *sec, void (*curveinit)(mpint*,mpint*,mpint*,mpint*,mpint*,mpint*,mpint*), Bytes *Ys, int *npub);
 static char*	verifyDHparams(TlsSec *sec, Msg *m, Bytes *cert);
 
 //================= client/server ========================
@@ -590,6 +596,36 @@ tlsServer2(int ctl, int hand, uchar *cert, int ncert, 
 	}
 
 	memmove(c->crandom, m.u.clientHello.random, RandomSize);
+	/*
+	 * Walk client's supported_groups in preference order and pick the first
+	 * curve we also implement (RFC 8422 Section 5.1.1, RFC 7919 Section 4).
+	 * Inline parse rather than going through the extension framework so this
+	 * patch is self-contained.  c->serverCurve == 0 after this loop means
+	 * no overlap, and okCipher will then refuse to pick any ECDHE suite.
+	 */
+	c->serverCurve = 0;
+	if(m.u.clientHello.extensions != nil){
+		Bytes *sg;
+
+		sg = findExtension(m.u.clientHello.extensions, ExtSupportedGroups);
+		if(sg != nil){
+			int listlen, i, g;
+
+			if(sg->len >= 2){
+				listlen = get16(sg->data);
+				if(listlen + 2 <= sg->len && (listlen & 1) == 0){
+					for(i = 0; i < listlen; i += 2){
+						g = get16(sg->data + 2 + i);
+						if(g == NamedGroupX25519){
+							c->serverCurve = g;
+							break;
+						}
+					}
+				}
+			}
+			freebytes(sg);
+		}
+	}
 	cipher = okCipher(c, m.u.clientHello.ciphers);
 	if(cipher < 0) {
 		// reply with EInsufficientSecurity if we know that's the case
@@ -657,7 +693,15 @@ tlsServer2(int ctl, int hand, uchar *cert, int ncert, 
 	if(isECDHE(cipher)){
 		Bytes *epub, *ecparams;
 
-		epub = tlsSecECDHEs1(c->sec);
+		switch(c->serverCurve){
+		case NamedGroupX25519:
+			epub = tlsSecECDHEs1(c->sec);
+			break;
+		default:
+			epub = nil;
+			tlsError(c, EInternalError, "no ECDHE curve negotiated");
+			goto Err;
+		}
 		if(epub == nil){
 			tlsError(c, EInternalError, "ECDHE keygen failed");
 			goto Err;
@@ -665,12 +709,12 @@ tlsServer2(int ctl, int hand, uchar *cert, int ncert, 
 
 		ecparams = newbytes(1 + 2 + 1 + epub->len);
 		ecparams->data[0] = 3;	/* named_curve */
-		put16(ecparams->data + 1, NamedGroupX25519);
+		put16(ecparams->data + 1, c->serverCurve);
 		ecparams->data[3] = epub->len;
 		memmove(ecparams->data + 4, epub->data, epub->len);
 
 		m.tag = HServerKeyExchange;
-		m.u.serverKeyExchange.curve = NamedGroupX25519;
+		m.u.serverKeyExchange.curve = c->serverCurve;
 		m.u.serverKeyExchange.pubkey = epub;
 		m.u.serverKeyExchange.params = ecparams;
 		m.u.serverKeyExchange.sigalg = RSA_PKCS1_SHA256;
@@ -701,7 +745,15 @@ tlsServer2(int ctl, int hand, uchar *cert, int ncert, 
 			tlsError(c, EInternalError, "setVers: %r");
 			goto Err;
 		}
-		if(tlsSecECDHEs2(c->sec, m.u.clientKeyExchange.key) < 0){
+		switch(c->serverCurve){
+		case NamedGroupX25519:
+			rv = tlsSecECDHEs2(c->sec, m.u.clientKeyExchange.key);
+			break;
+		default:
+			rv = -1;
+			break;
+		}
+		if(rv < 0){
 			tlsError(c, EHandshakeFailure, "ECDHE finish: %r");
 			goto Err;
 		}
@@ -890,10 +942,6 @@ tlsClient2(int ctl, int hand, uchar *csid, int ncsid, 
 			/* If enabling SKE for RSA-KE later, watch out for
 			 * rollback attack (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);
@@ -905,7 +953,43 @@ tlsClient2(int ctl, int hand, uchar *csid, int ncsid, 
 			tlsError(c, EIllegalParameter, "setVers failed: %r");
 			goto Err;
 		}
-		epm = tlsSecECDHEc(c->sec, m.u.serverKeyExchange.pubkey, &nepm);
+		/*
+		 * RFC 8446 Section 4.2.7 / RFC 8422 Section 5.4: server MUST
+		 * pick a curve from the client's supportedGroups list.
+		 * Reject any curve we didn't advertise as a downgrade
+		 * attempt.
+		 */
+		{
+			int i, off, found;
+
+			found = 0;
+			for(i = 0; i + 1 < (int)sizeof(supportedGroups); i += 2){
+				off = (supportedGroups[i] << 8) | supportedGroups[i+1];
+				if(off == m.u.serverKeyExchange.curve){
+					found = 1;
+					break;
+				}
+			}
+			if(!found){
+				tlsError(c, EIllegalParameter,
+					"server picked unoffered ECDHE curve");
+				goto Err;
+			}
+		}
+		switch(m.u.serverKeyExchange.curve){
+		case NamedGroupX25519:
+			epm = tlsSecECDHEc(c->sec, m.u.serverKeyExchange.pubkey, &nepm);
+			break;
+		case NamedGroupSecp256r1:
+			epm = tlsSecECDHEcNist(c->sec, secp256r1, m.u.serverKeyExchange.pubkey, &nepm);
+			break;
+		case NamedGroupSecp384r1:
+			epm = tlsSecECDHEcNist(c->sec, secp384r1, m.u.serverKeyExchange.pubkey, &nepm);
+			break;
+		default:
+			tlsError(c, EHandshakeFailure, "unsupported ECDHE named curve");
+			goto Err;
+		}
 		if(epm == nil){
 			tlsError(c, EHandshakeFailure, "ECDHE failed: %r");
 			goto Err;
@@ -1543,11 +1627,25 @@ msgRecv(TlsConnection *c, Msg *m)
 		break;
 	case HClientKeyExchange:
 		/*
-		 * this message depends upon the encryption selected
-		 * assume rsa.
+		 * length-prefix encoding differs by key-exchange algorithm
+		 * (mirror of msgSend HClientKeyExchange):
+		 *   RSA-KE   EncryptedPreMasterSecret:
+		 *     opaque<0..2^16-1>
+		 *     (2-byte, TLS; none in SSL3)
+		 *   ECDHE    ClientECDiffieHellmanPublic: opaque<1..255>  (1-byte)
 		 */
 		if(c->version == SSL3Version)
 			nn = n;
+		/* mirror of msgSend's HClientKeyExchange ECDHE branch (added by
+		 * tls-ecdhe-sni-client patch); fixes latent CKE-recv parsing bug
+		 * that surfaces only when tlsServer2 actually drives ECDHE. */
+		else if(isECDHE(c->cipher)){
+			if(n < 1)
+				goto Short;
+			nn = p[0];
+			p += 1;
+			n -= 1;
+		}
 		else{
 			if(n < 2)
 				goto Short;
@@ -2560,6 +2658,95 @@ tlsSecECDHEc(TlsSec *sec, Bytes *Ys, int *npub)
 	freebytes(pm);
 
 	*npub = 32;
+	return pub;
+}
+
+/*
+ * NIST ECDHE (P-256, P-384).  Same shape as tlsSecECDHEc above but
+ * uses libsec's generic short-Weierstrass ECC (ecgen + ecmul) rather
+ * than the curve25519_dh_* specialisation.  curveinit selects the
+ * curve (secp256r1 or secp384r1).  Shared secret is the X coordinate
+ * of the Diffie-Hellman point, big-endian, exactly plen bytes wide
+ * (32 for P-256, 48 for P-384) -- RFC 8422 Section 5.11.
+ *
+ * BUG: ecmul is not constant-time; the ephemeral private key d is
+ * exposed to timing-side-channel recovery on shared hosts.  A
+ * constant-time scalar multiply is needed in libsec.
+ */
+static uchar*
+tlsSecECDHEcNist(TlsSec *sec,
+	void (*curveinit)(mpint*,mpint*,mpint*,mpint*,mpint*,mpint*,mpint*),
+	Bytes *Ys, int *npub)
+{
+	ECdomain dom;
+	ECpriv *priv;
+	ECpub *peerpub;
+	ECpoint shared;
+	Bytes *pm;
+	uchar *pub;
+	int plen, eclen;
+
+	priv = nil;
+	peerpub = nil;
+	pm = nil;
+	pub = nil;
+	memset(&shared, 0, sizeof shared);
+
+	ecdominit(&dom, curveinit);
+	plen = (mpsignif(dom.p) + 7) / 8;
+	eclen = 1 + 2*plen;
+
+	if(Ys == nil || Ys->len != eclen){
+		werrstr("unexpected NIST ECDHE pubkey length");
+		goto Err;
+	}
+
+	priv = ecgen(&dom, nil);
+	if(priv == nil)
+		goto Err;
+
+	pub = emalloc(eclen);
+	if(ecencodepub(&dom, (ECpub*)priv, pub, eclen) == 0){
+		werrstr("ecencodepub");
+		goto Err;
+	}
+
+	/* ecdecodepub calls ecpubverify */
+	peerpub = ecdecodepub(&dom, Ys->data, Ys->len);
+	if(peerpub == nil){
+		werrstr("bad server ECDHE pubkey");
+		goto Err;
+	}
+
+	shared.x = mpnew(0);
+	shared.y = mpnew(0);
+	ecmul(&dom, peerpub, priv->d, &shared);
+
+	pm = newbytes(plen);
+	mptober(shared.x, pm->data, plen);
+	setMasterSecret(sec, pm);
+	memset(pm->data, 0, pm->len);
+
+	*npub = eclen;
+	goto Out;
+
+Err:
+	if(pub != nil){
+		free(pub);
+		pub = nil;
+	}
+
+Out:
+	if(pm != nil) freebytes(pm);
+	if(shared.x != nil) mpfree(shared.x);
+	if(shared.y != nil) mpfree(shared.y);
+	if(shared.z != nil) mpfree(shared.z);
+	if(peerpub != nil) ecpubfree(peerpub);
+	if(priv != nil){
+		mpfree(priv->d);
+		ecpubfree((ECpub*)priv);
+	}
+	ecdomfree(&dom);
 	return pub;
 }
 

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.