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