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