tlshand: NIST P-256 and P-384 as ECDHE named groups
Extends the client ECDHE handshake to advertise and negotiate
secp256r1 (0x0017) and secp384r1 (0x0018) alongside the
existing X25519 (0x001d). The ECC curve arithmetic for these
groups already lives in libsec-ecc-ecdsa-primitives for ECDSA
signature verification; this patch reuses it for ECDHE key
agreement.
Client side only. Server-side ECDHE negotiation in tlsServer
is out of scope.
New static helper tlsSecECDHEcNist() parallels tlsSecECDHEc
but uses libsec's generic short-Weierstrass ECC (ecdominit +
ecgen + ecencodepub + ecdecodepub + ecmul) with a curveinit
callback (secp256r1 or secp384r1). HServerKeyExchange
dispatches on the server-selected curve; unknown curves fail
with "unsupported ECDHE named curve".
Many servers advertise only P-256 or P-384 in
supported_groups, so offering only X25519 made the handshake
fail even when the server supported an ECDHE-RSA or
ECDHE-ECDSA suite we advertise. With this patch
ecc256.badssl.com and ecc384.badssl.com reach a complete
handshake.
RFC 8422 §5.1 (named_curve IDs), §5.4 (ECDHE over P-256/
P-384), §5.11 (shared secret is X coord, big-endian,
field-sized); 5480 (curve parameters); FIPS 186-4.
--- sys/src/libsec/port/tlshand.c
+++ sys/src/libsec/port/tlshand.c
@@ -285,9 +285,11 @@
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
@@ -334,9 +336,11 @@
RSA_PKCS1_SHA256,
};
-/* supported groups (curves) we advertise in ClientHello */
+/* supported groups (curves) we advertise in ClientHello, preference order */
static uchar supportedGroups[] = {
- NamedGroupX25519 >> 8, NamedGroupX25519 & 0xff,
+ NamedGroupX25519 >> 8, NamedGroupX25519 & 0xff,
+ NamedGroupSecp256r1 >> 8, NamedGroupSecp256r1 & 0xff,
+ NamedGroupSecp384r1 >> 8, NamedGroupSecp384r1 & 0xff,
};
/* EC point formats we support (only uncompressed) */
@@ -407,6 +411,7 @@
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 ========================
@@ -824,10 +829,6 @@
// 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);
@@ -839,7 +840,20 @@
tlsError(c, EIllegalParameter, "setVers failed: %r");
goto Err;
}
- epm = tlsSecECDHEc(c->sec, m.u.serverKeyExchange.pubkey, &nepm);
+ 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;
@@ -2398,6 +2412,91 @@
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 §5.11.
+ */
+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;
}
|