--- sys/include/libsec.h
+++ sys/include/libsec.h
@@ -351,6 +351,7 @@ void X509dump(uchar *cert, int ncert);
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 @@ typedef struct TLSconn{
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 @@ CFILES = des.c desmodes.c desECB.c desCBC.c des3ECB.c
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,119 @@
+#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 prefixes per RFC 3447 Section 9.2:
+ * SEQUENCE { SEQUENCE { OID hash, NULL }, OCTET STRING [hashlen] }
+ * Followed by the hash digest.
+ */
+static uchar sha256DigestInfo[] = {
+ 0x30, 0x31, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86,
+ 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, 0x05,
+ 0x00, 0x04, 0x20,
+};
+static uchar sha384DigestInfo[] = {
+ 0x30, 0x41, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86,
+ 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x02, 0x05,
+ 0x00, 0x04, 0x30,
+};
+static uchar sha512DigestInfo[] = {
+ 0x30, 0x51, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86,
+ 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x03, 0x05,
+ 0x00, 0x04, 0x40,
+};
+
+/*
+ * 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(sha512DigestInfo) + SHA2_512dlen];
+ uchar *diPrefix;
+ int dilen, dipfxlen, dgstlen;
+ 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";
+
+ /* RFC 5246 Section 7.4.1.4.1: high byte = hash algorithm.
+ * 4 = SHA-256, 5 = SHA-384, 6 = SHA-512. */
+ switch((sigalg >> 8) & 0xff){
+ case 0x04:
+ diPrefix = sha256DigestInfo;
+ dipfxlen = sizeof(sha256DigestInfo);
+ dgstlen = SHA2_256dlen;
+ sha2_256(msg, msglen, digestinfo + dipfxlen, nil);
+ break;
+ case 0x05:
+ diPrefix = sha384DigestInfo;
+ dipfxlen = sizeof(sha384DigestInfo);
+ dgstlen = SHA2_384dlen;
+ sha2_384(msg, msglen, digestinfo + dipfxlen, nil);
+ break;
+ case 0x06:
+ diPrefix = sha512DigestInfo;
+ dipfxlen = sizeof(sha512DigestInfo);
+ dgstlen = SHA2_512dlen;
+ sha2_512(msg, msglen, digestinfo + dipfxlen, nil);
+ break;
+ default:
+ werrstr("sigalg=%#x", sigalg);
+ return "unsupported SKE hash algorithm";
+ }
+
+ memmove(digestinfo, diPrefix, dipfxlen);
+ dilen = dipfxlen + dgstlen;
+
+ /* 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
@@ -43,6 +43,7 @@ typedef struct Algs{
char *digest;
int nsecret;
int tlsid;
+ int flags; /* AlgRsaSign | AlgEcSign; 0 = no cert-key gating */
int ok;
} Algs;
@@ -66,6 +67,7 @@ typedef struct TlsConnection{
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 (KE-type discrimination) */
Bytes *sid; // SessionID
Bytes *cert; // only last - no chain
@@ -97,7 +99,7 @@ typedef struct Msg{
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 +107,7 @@ typedef struct Msg{
Bytes* sid;
int cipher;
int compressor;
+ Bytes* extensions;
} serverHello;
struct {
int ncert;
@@ -116,6 +119,13 @@ typedef struct Msg{
Bytes **cas;
} certificateRequest;
struct {
+ int curve; /* X25519 / secp256r1 / secp384r1 named-curve tlsid */
+ Bytes *pubkey; /* peer's ephemeral DH pubkey (X25519: 32 B; P-256: 65 B; P-384: 97 B) */
+ 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 +142,9 @@ typedef struct TlsSec{
uchar srandom[RandomSize]; // server random
int clientVers; // version in ClientHello
int vers; // final version
+ /* X25519 ECDHE ephemeral keypair (both client and server roles) */
+ 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 +251,38 @@ enum {
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 */
};
+/*
+ * AEAD and ECDHE 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,
@@ -260,25 +291,53 @@ enum {
RSA_PKCS1_SHA512 = 0x0601
};
+/* Algs.flags: cert-key-type gate used by okCipher (RFC 4492 Section 2.2). */
+enum {
+ AlgRsaSign = 1<<0,
+ AlgEcSign = 1<<1,
+};
+
static Algs cipherAlgs[] = {
- {"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},
- {"aes_128_cbc", "sha1", 2*(16+16+SHA1dlen), TLS_RSA_WITH_AES_128_CBC_SHA},
- {"aes_256_cbc", "sha1", 2*(32+16+SHA1dlen), TLS_RSA_WITH_AES_256_CBC_SHA}
+ /* 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, AlgRsaSign},
+ {"ccpoly96_aead", "clear", 2*(32+12), TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256, AlgRsaSign},
+ /* legacy RSA-KE suites (no forward secrecy) */
+ {"rc4_128", "md5", 2*(16+MD5dlen), TLS_RSA_WITH_RC4_128_MD5, AlgRsaSign},
+ {"rc4_128", "sha1", 2*(16+SHA1dlen), TLS_RSA_WITH_RC4_128_SHA, AlgRsaSign},
+ {"3des_ede_cbc", "sha1", 2*(4*8+SHA1dlen), TLS_RSA_WITH_3DES_EDE_CBC_SHA, AlgRsaSign},
+ {"aes_128_cbc", "sha1", 2*(16+16+SHA1dlen), TLS_RSA_WITH_AES_128_CBC_SHA, AlgRsaSign},
+ {"aes_256_cbc", "sha1", 2*(32+16+SHA1dlen), TLS_RSA_WITH_AES_256_CBC_SHA, AlgRsaSign}
};
static uchar compressors[] = {
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); 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);
@@ -322,6 +381,12 @@ static void* emalloc(int);
static void factotum_rsa_close(AuthRpc*rpc);
static void* emalloc(int);
+
+/* server-side ECDHE-RSA helpers (defined later in this file) */
+static Bytes* tlsSecECDHEs1(TlsSec *sec);
+static int tlsSecECDHEs2(TlsSec *sec, Bytes *Yc);
+static Bytes* signDHparams(TlsSec *sec, Bytes *params, int sigalg);
+static Bytes* mptobytes(mpint *big);
static void* erealloc(void*, int);
static void put32(uchar *p, u32int);
static void put24(uchar *p, int);
@@ -336,6 +401,12 @@ static void freeints(Ints* b);
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 +500,7 @@ tlsClient(int fd, TLSconn *conn)
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);
@@ -557,6 +628,43 @@ tlsServer2(int ctl, int hand, uchar *cert, int ncert,
goto Err;
msgClear(&m);
+ /*
+ * RFC 4492 Section 5.4: when ECDHE is negotiated, send
+ * ServerKeyExchange with ECParams (curve_type=named_curve,
+ * named_curve, pubkey) and an RSA-PKCS1-SHA256 signature over
+ * crandom||srandom||ECParams binding the cert to this session's
+ * ephemeral key. X25519 only at this point in the series.
+ */
+ if(isECDHE(cipher)){
+ Bytes *epub, *ecparams;
+
+ epub = tlsSecECDHEs1(c->sec);
+ if(epub == nil){
+ tlsError(c, EInternalError, "ECDHE keygen failed");
+ goto Err;
+ }
+
+ ecparams = newbytes(1 + 2 + 1 + epub->len);
+ ecparams->data[0] = 3; /* named_curve */
+ put16(ecparams->data + 1, NamedGroupX25519);
+ ecparams->data[3] = epub->len;
+ memmove(ecparams->data + 4, epub->data, epub->len);
+
+ m.tag = HServerKeyExchange;
+ m.u.serverKeyExchange.curve = NamedGroupX25519;
+ m.u.serverKeyExchange.pubkey = epub;
+ m.u.serverKeyExchange.params = ecparams;
+ m.u.serverKeyExchange.sigalg = RSA_PKCS1_SHA256;
+ m.u.serverKeyExchange.signature = signDHparams(c->sec, ecparams, RSA_PKCS1_SHA256);
+ if(m.u.serverKeyExchange.signature == nil){
+ tlsError(c, EHandshakeFailure, "SKE sign failed: %r");
+ goto Err;
+ }
+ if(!msgSend(c, &m, AQueue))
+ goto Err;
+ msgClear(&m);
+ }
+
m.tag = HServerHelloDone;
if(!msgSend(c, &m, AFlush))
goto Err;
@@ -568,6 +676,18 @@ tlsServer2(int ctl, int hand, uchar *cert, int ncert,
tlsError(c, EUnexpectedMessage, "expected a client key exchange");
goto Err;
}
+ if(isECDHE(c->cipher)){
+ /* setVers selects the right PRF before setMasterSecret runs. */
+ if(setVers(c->sec, c->version) < 0){
+ tlsError(c, EInternalError, "setVers: %r");
+ goto Err;
+ }
+ if(tlsSecECDHEs2(c->sec, m.u.clientKeyExchange.key) < 0){
+ tlsError(c, EHandshakeFailure, "ECDHE finish: %r");
+ goto Err;
+ }
+ setSecrets(c->sec, kd, c->nsecret);
+ }else
if(tlsSecSecrets(c->sec, c->version, m.u.clientKeyExchange.key->data, m.u.clientKeyExchange.key->len, kd, c->nsecret) < 0){
tlsError(c, EHandshakeFailure, "couldn't set secrets: %r");
goto Err;
@@ -633,13 +753,13 @@ static TlsConnection *
}
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 +785,7 @@ tlsClient2(int ctl, int hand, uchar *csid, int ncsid,
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 +807,8 @@ tlsClient2(int ctl, int hand, uchar *csid, int ncsid,
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 +816,7 @@ tlsClient2(int ctl, int hand, uchar *csid, int ncsid,
tlsError(c, EIllegalParameter, "invalid compression");
goto Err;
}
+ dhx = isECDHE(cipher);
msgClear(&m);
/* certificate */
@@ -709,14 +831,43 @@ tlsClient2(int ctl, int hand, uchar *csid, int ncsid,
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 (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 +885,19 @@ tlsClient2(int ctl, int hand, uchar *csid, int ncsid,
}
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 +1055,13 @@ msgSend(TlsConnection *c, Msg *m, int act)
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 +1083,15 @@ msgSend(TlsConnection *c, Msg *m, int act)
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;
@@ -949,9 +1112,41 @@ msgSend(TlsConnection *c, Msg *m, int act)
p += m->u.certificate.certs[i]->len;
}
break;
+ case HServerKeyExchange:
+ /*
+ * RFC 4492 Section 5.4 ServerKeyExchange wire form:
+ * curve_type(1) named_curve(2) pubkey_len(1) pubkey
+ * sig_alg(2) sig_len(2) sig
+ * params already contains the curve_type..pubkey block; emit
+ * it verbatim so the client signs/verifies the same bytes.
+ */
+ {
+ Bytes *params = m->u.serverKeyExchange.params;
+ Bytes *sig = m->u.serverKeyExchange.signature;
+
+ memmove(p, params->data, params->len);
+ p += params->len;
+ put16(p, m->u.serverKeyExchange.sigalg);
+ p += 2;
+ put16(p, sig->len);
+ p += 2;
+ memmove(p, sig->data, sig->len);
+ p += sig->len;
+ }
+ 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 +1334,7 @@ msgRecv(TlsConnection *c, Msg *m)
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 +1342,11 @@ msgRecv(TlsConnection *c, Msg *m)
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 +1371,61 @@ msgRecv(TlsConnection *c, Msg *m)
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 +1552,11 @@ msgClear(Msg *m)
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 +1571,11 @@ msgClear(Msg *m)
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 +1643,8 @@ msgPrint(char *buf, int n, Msg *m)
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 +1656,16 @@ msgPrint(char *buf, int n, Msg *m)
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 +1804,7 @@ setAlgs(TlsConnection *c, int a)
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 +2217,365 @@ static int
}
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 Section 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;
+}
+
+/*
+ * X25519 ECDHE server side, step 1: generate ephemeral keypair.
+ * Returns the public key as a malloc'd Bytes for the SKE pubkey field.
+ * Mirrors tlsSecECDHEc but split into two calls because the server
+ * emits its pubkey in SKE before receiving the client's in CKE.
+ */
+static Bytes*
+tlsSecECDHEs1(TlsSec *sec)
+{
+ Bytes *pub;
+
+ curve25519_dh_new(sec->ecdhe_priv, sec->ecdhe_pub);
+ pub = newbytes(32);
+ memmove(pub->data, sec->ecdhe_pub, 32);
+ return pub;
+}
+
+/*
+ * X25519 ECDHE server side, step 2: compute shared secret using the
+ * client's pubkey Yc from CKE, install as pre-master, derive master via
+ * setMasterSecret. Returns 0 on success, -1 on degenerate (all-zero)
+ * shared secret per RFC 7748 Section 6.1.
+ */
+static int
+tlsSecECDHEs2(TlsSec *sec, Bytes *Yc)
+{
+ Bytes *pm;
+ uchar Ypeer[32], Z[32];
+
+ if(Yc == nil || Yc->len != 32){
+ werrstr("unexpected X25519 pubkey length");
+ return -1;
+ }
+ memmove(Ypeer, Yc->data, 32);
+ if(!curve25519_dh_finish(sec->ecdhe_priv, Ypeer, Z)){
+ memset(Ypeer, 0, sizeof(Ypeer));
+ memset(Z, 0, sizeof(Z));
+ werrstr("degenerate DH shared secret");
+ return -1;
+ }
+ pm = newbytes(32);
+ memmove(pm->data, Z, 32);
+ memset(Z, 0, sizeof(Z));
+ setMasterSecret(sec, pm);
+ memset(pm->data, 0, pm->len);
+ freebytes(pm);
+ return 0;
+}
+
+/*
+ * Build the bytes the SKE signature covers per RFC 5246 Section 7.4.3:
+ * client_random (32) || server_random (32) || ServerKeyExchange params.
+ * Caller-allocated *blob receives the bytes; *bloblen returns total.
+ * Returns 0 on success, -1 if the supplied blob buffer would overflow.
+ */
+static int
+buildSkeSigBlob(TlsSec *sec, Bytes *params, uchar *blob, int blobsize, int *bloblen)
+{
+ int n;
+
+ n = 2*RandomSize + params->len;
+ if(n > blobsize)
+ return -1;
+ memmove(blob, sec->crandom, RandomSize);
+ memmove(blob + RandomSize, sec->srandom, RandomSize);
+ memmove(blob + 2*RandomSize, params->data, params->len);
+ *bloblen = n;
+ return 0;
+}
+
+/*
+ * PKCS1 v1.5 type-1 (signature) padding per RFC 3447 Section 9.2:
+ * 00 01 FF...FF 00 || data
+ * pad_len = mod_len - 3 - data_len; minimum 8 FFs
+ * (RFC 3447 Section 9.2 step 2).
+ * Caller-allocated out[] receives the padded result of exactly mod_len bytes.
+ * Returns mod_len on success, -1 if mod_len is too small for data + 11.
+ */
+static int
+pkcs1padBlockType1(uchar *data, int datalen, int modlen, uchar *out)
+{
+ int padlen;
+
+ if(modlen < datalen + 11)
+ return -1;
+ padlen = modlen - 3 - datalen;
+ out[0] = 0x00;
+ out[1] = 0x01;
+ memset(out + 2, 0xFF, padlen);
+ out[2 + padlen] = 0x00;
+ memmove(out + 3 + padlen, data, datalen);
+ return modlen;
+}
+
+/*
+ * Sign ECDHE SKE params with the server's RSA cert key (RFC 5246
+ * Section 7.4.3 + RFC 3447 Section 9.2). The signed blob is
+ * crandom || srandom || ECParams; SHA-256, then PKCS1 v1.5 type-1
+ * padded, then RSA private op via factotum. Currently only
+ * rsa_pkcs1_sha256 is supported. Returns malloc'd Bytes or nil.
+ */
+static Bytes*
+signDHparams(TlsSec *sec, Bytes *params, int sigalg)
+{
+ /* DigestInfo prefix for SHA-256, RFC 3447 Section 9.2 */
+ static uchar diPrefix[] = {
+ 0x30, 0x31, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86,
+ 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, 0x05,
+ 0x00, 0x04, 0x20,
+ };
+ uchar blob[2*RandomSize + 256];
+ uchar *eb, di[sizeof(diPrefix) + SHA2_256dlen];
+ int bloblen, modlen, dilen;
+ mpint *x, *y;
+ Bytes *sig, *padded;
+
+ if(sigalg != RSA_PKCS1_SHA256){
+ werrstr("unsupported SKE sigalg %#x", sigalg);
+ return nil;
+ }
+ if(sec->rsapub == nil){
+ werrstr("no RSA public key");
+ return nil;
+ }
+
+ if(buildSkeSigBlob(sec, params, blob, sizeof blob, &bloblen) < 0){
+ werrstr("SKE blob too large");
+ return nil;
+ }
+
+ dilen = sizeof(diPrefix) + SHA2_256dlen;
+ modlen = (mpsignif(sec->rsapub->n)+7)/8;
+
+ /* DigestInfo || digest, then PKCS1 v1.5 type-1 pad to modlen */
+ memmove(di, diPrefix, sizeof(diPrefix));
+ sha2_256(blob, bloblen, di + sizeof(diPrefix), nil);
+ eb = emalloc(modlen);
+ if(pkcs1padBlockType1(di, dilen, modlen, eb) < 0){
+ free(eb);
+ werrstr("RSA modulus too small for PKCS1 v1.5");
+ return nil;
+ }
+
+ x = betomp(eb, modlen, nil);
+ free(eb);
+ if(x == nil){
+ werrstr("betomp failed");
+ return nil;
+ }
+ y = factotum_rsa_decrypt(sec->rpc, x);
+ if(y == nil){
+ werrstr("factotum_rsa_decrypt: %r");
+ return nil;
+ }
+ sig = mptobytes(y);
+ mpfree(y);
+ if(sig == nil){
+ werrstr("mptobytes failed");
+ return nil;
+ }
+ if(sig->len < modlen){
+ padded = newbytes(modlen);
+ memset(padded->data, 0, modlen - sig->len);
+ memmove(padded->data + modlen - sig->len, sig->data, sig->len);
+ freebytes(sig);
+ sig = padded;
+ } else if(sig->len > modlen){
+ /* RSA op MUST NOT produce a value > modulus. If it does,
+ * the modulus or factotum_rsa_decrypt is corrupt -- a hard
+ * failure surfaces it rather than silently truncating to
+ * an invalid signature. */
+ sysfatal("signDHparams: RSA op produced sig > modlen, invariant violated");
+ }
+ return sig;
+}
+
+static int
tlsSecFinished(TlsSec *sec, HandHash hs, uchar *fin, int nfin, int isclient)
{
if(sec->nfin != nfin){
@@ -2065,11 +2670,15 @@ setSecrets(TlsSec *sec, uchar *kd, int nkd)
/*
* 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);
}
|