--- sys/include/libsec.h
+++ sys/include/libsec.h
@@ -510,6 +510,7 @@ typedef struct TLSconn{
char *sessionConst;
char *serverName; /* SNI: set by client before tlsClient; set by tlsServer to observed SNI */
PEMChain *rootCAchain; /* trust anchors for X509verifychain; nil = no chain validation */
+ int emsstrict; /* RFC 7627 Section 5.2: refuse handshake when peer lacks extended_master_secret */
} TLSconn;
/* tlshand.c */
--- sys/src/libsec/port/tlshand.c
+++ sys/src/libsec/port/tlshand.c
@@ -99,6 +99,9 @@ typedef struct TlsConnection{
int nClientOffered;
int clientOffered[16];
int extRenegInfoSeen; /* client signaled RFC 5746 (ext or SCSV) */
+ int extEmsSeen; /* peer signaled RFC 7627 extended_master_secret */
+ int emsStrict; /* RFC 7627 5.2 opt-in (TLSconn mirror) */
+ int emsActive; /* both sides agreed; gates EMS-PRF */
} TlsConnection;
typedef struct Msg{
@@ -160,6 +163,14 @@ typedef struct TlsSec{
void (*prf)(uchar*, int, uchar*, int, char*, uchar*, int, uchar*, int);
void (*setFinished)(TlsSec*, HandHash, uchar*, int);
int nfin;
+ /* RFC 7627 extended_master_secret state. When emsActive flips on,
+ * setMasterSecret stashes the pre-master in pms instead of running
+ * the PRF; the EMS finalize completes derivation against
+ * sessionHash once the snapshot is captured. */
+ int emsActive;
+ Bytes *pms;
+ uchar sessionHash[SHA2_256dlen];
+ int sessionHashLen;
} TlsSec;
@@ -369,9 +380,9 @@ static uchar ecPointFormats[] = {
0, /* uncompressed */
};
-static TlsConnection *tlsServer2(int ctl, int hand, uchar *cert, int ncert, int (*trace)(char*fmt, ...), PEMChain *chain);
+static TlsConnection *tlsServer2(int ctl, int hand, uchar *cert, int ncert, int emsstrict, int (*trace)(char*fmt, ...), PEMChain *chain);
static TlsConnection *tlsClient2(int ctl, int hand, uchar *csid, int ncsid,
- char *serverName, PEMChain *rootCA,
+ char *serverName, int emsstrict, PEMChain *rootCA,
int (*trace)(char*fmt, ...));
static void msgClear(Msg *m);
@@ -398,6 +409,8 @@ static void tlsSecClose(TlsSec *sec);
static void tlsSecOk(TlsSec *sec);
static void tlsSecKill(TlsSec *sec);
static void tlsSecClose(TlsSec *sec);
+static int tlsSecMasterSecretEms(TlsSec *sec);
+static int handshakeHashSnapshot(TlsConnection *c, uchar *out);
static void setMasterSecret(TlsSec *sec, Bytes *pm);
static void serverMasterSecret(TlsSec *sec, uchar *epm, int nepm);
static void setSecrets(TlsSec *sec, uchar *kd, int nkd);
@@ -504,7 +517,7 @@ tlsServer(int fd, TLSconn *conn)
return -1;
}
fprint(ctl, "fd %d 0x%x", fd, ProtocolVersion);
- tls = tlsServer2(ctl, hand, conn->cert, conn->certlen, conn->trace, conn->chain);
+ tls = tlsServer2(ctl, hand, conn->cert, conn->certlen, conn->emsstrict, conn->trace, conn->chain);
snprint(dname, sizeof dname, "#a/tls/%s/data", buf);
data = open(dname, ORDWR);
close(fd);
@@ -566,7 +579,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->serverName, conn->rootCAchain, conn->trace);
+ tls = tlsClient2(ctl, hand, conn->sessionID, conn->sessionIDlen, conn->serverName, conn->emsstrict, conn->rootCAchain, conn->trace);
close(fd);
close(hand);
close(ctl);
@@ -599,7 +612,8 @@ static TlsConnection *
}
static TlsConnection *
-tlsServer2(int ctl, int hand, uchar *cert, int ncert, int (*trace)(char*fmt, ...), PEMChain *chp)
+tlsServer2(int ctl, int hand, uchar *cert, int ncert,
+ int emsstrict, int (*trace)(char*fmt, ...), PEMChain *chp)
{
TlsConnection *c;
Msg m;
@@ -617,6 +631,7 @@ tlsServer2(int ctl, int hand, uchar *cert, int ncert,
c->hand = hand;
c->trace = trace;
c->version = ProtocolVersion;
+ c->emsStrict = emsstrict;
memset(&m, 0, sizeof(m));
if(!msgRecv(c, &m)){
@@ -750,6 +765,15 @@ tlsServer2(int ctl, int hand, uchar *cert, int ncert,
c->sec->rsapub = X509toRSApub(cert, ncert, nil, 0);
msgClear(&m);
+ c->emsActive = c->extEmsSeen;
+ c->sec->emsActive = c->emsActive;
+ if(!c->emsActive && c->emsStrict){
+ /* RFC 7627 Section 5.2: strict-mode opt-in demands EMS. */
+ tlsError(c, EHandshakeFailure,
+ "RFC 7627 strict mode: client lacks extended_master_secret");
+ goto Err;
+ }
+
m.tag = HServerHello;
m.u.serverHello.version = c->version;
memmove(m.u.serverHello.random, c->srandom, RandomSize);
@@ -838,6 +862,16 @@ tlsServer2(int ctl, int hand, uchar *cert, int ncert,
tlsError(c, EUnexpectedMessage, "expected a client key exchange");
goto Err;
}
+ /*
+ * RFC 7627 Section 4: session_hash = Hash(handshake_messages up
+ * to and including ClientKeyExchange). msgRecv has just absorbed
+ * CKE into c->hs; snapshot before the helpers' setMasterSecret
+ * stashes the pre-master and the EMS finalize that follows.
+ */
+ if(c->emsActive){
+ c->sec->sessionHashLen =
+ handshakeHashSnapshot(c, c->sec->sessionHash);
+ }
if(isECDHE(c->cipher)){
/* setVers selects the right PRF before setMasterSecret runs. */
if(setVers(c->sec, c->version) < 0){
@@ -856,6 +890,10 @@ tlsServer2(int ctl, int hand, uchar *cert, int ncert,
tlsError(c, EHandshakeFailure, "ECDHE finish: %r");
goto Err;
}
+ if(c->sec->emsActive && tlsSecMasterSecretEms(c->sec) < 0){
+ tlsError(c, EHandshakeFailure, "EMS finalize: %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){
@@ -923,7 +961,7 @@ static TlsConnection *
}
static TlsConnection *
-tlsClient2(int ctl, int hand, uchar *csid, int ncsid, char *serverName, PEMChain *rootCA, int (*trace)(char*fmt, ...))
+tlsClient2(int ctl, int hand, uchar *csid, int ncsid, char *serverName, int emsstrict, PEMChain *rootCA, int (*trace)(char*fmt, ...))
{
TlsConnection *c;
Msg m;
@@ -941,6 +979,7 @@ tlsClient2(int ctl, int hand, uchar *csid, int ncsid,
c->trace = trace;
c->isClient = 1;
c->clientVersion = c->version;
+ c->emsStrict = emsstrict;
c->sec = tlsSecInitc(c->clientVersion, c->crandom);
if(c->sec == nil)
@@ -998,6 +1037,14 @@ tlsClient2(int ctl, int hand, uchar *csid, int ncsid,
goto Err;
}
}
+ c->emsActive = c->extEmsSeen;
+ c->sec->emsActive = c->emsActive;
+ if(!c->emsActive && c->emsStrict){
+ /* RFC 7627 Section 5.2: strict-mode caller demanded EMS. */
+ tlsError(c, EHandshakeFailure,
+ "RFC 7627 strict mode: server lacks extended_master_secret");
+ goto Err;
+ }
dhx = isECDHE(cipher);
msgClear(&m);
@@ -1105,7 +1152,8 @@ tlsClient2(int ctl, int hand, uchar *csid, int ncsid,
tlsError(c, EHandshakeFailure, "ECDHE failed: %r");
goto Err;
}
- setSecrets(c->sec, kd, c->nsecret);
+ if(!c->sec->emsActive)
+ setSecrets(c->sec, kd, c->nsecret);
msgClear(&m);
if(!msgRecv(c, &m))
goto Err;
@@ -1142,15 +1190,22 @@ tlsClient2(int ctl, int hand, uchar *csid, int ncsid,
}
}
/* 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);
- memset(secrets, 0, 2*c->nsecret);
- free(secrets);
- memset(kd, 0, c->nsecret);
- if(rv < 0){
- tlsError(c, EHandshakeFailure, "can't set keys: %r");
- goto Err;
+ /*
+ * In the EMS path, master_secret + key block are derived after
+ * CKE has crossed (RFC 7627 Section 4); the emit here is gated
+ * on !emsActive and the EMS twin runs after msgSend(CKE) below.
+ */
+ if(!c->sec->emsActive){
+ 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);
+ memset(secrets, 0, 2*c->nsecret);
+ free(secrets);
+ memset(kd, 0, c->nsecret);
+ if(rv < 0){
+ tlsError(c, EHandshakeFailure, "can't set keys: %r");
+ goto Err;
+ }
}
if(creq) {
@@ -1174,6 +1229,31 @@ tlsClient2(int ctl, int hand, uchar *csid, int ncsid,
goto Err;
msgClear(&m);
+ /*
+ * RFC 7627 Section 4: with EMS active, derive master_secret now
+ * that c->hs covers ClientKeyExchange (msgSend just absorbed it),
+ * expand the key block, feed the record layer.
+ */
+ if(c->sec->emsActive){
+ c->sec->sessionHashLen =
+ handshakeHashSnapshot(c, c->sec->sessionHash);
+ if(tlsSecMasterSecretEms(c->sec) < 0){
+ tlsError(c, EHandshakeFailure, "EMS finalize: %r");
+ goto Err;
+ }
+ setSecrets(c->sec, kd, c->nsecret);
+ 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);
+ memset(secrets, 0, 2*c->nsecret);
+ free(secrets);
+ memset(kd, 0, c->nsecret);
+ if(rv < 0){
+ tlsError(c, EHandshakeFailure, "can't set keys: %r");
+ goto Err;
+ }
+ }
+
/* change cipher spec */
if(fprint(c->ctl, "changecipher") < 0){
tlsError(c, EInternalError, "can't enable cipher: %r");
@@ -2446,6 +2526,8 @@ tlsSecSecrets(TlsSec *sec, int vers, uchar *epm, int n
werrstr("mismatched session versions");
goto Err;
}
+ if(sec->emsActive && tlsSecMasterSecretEms(sec) < 0)
+ goto Err;
setSecrets(sec, kd, nkd);
return 0;
Err:
@@ -2487,7 +2569,8 @@ tlsSecSecretc(TlsSec *sec, uchar *sid, int nsid, uchar
if(clientMasterSecret(sec, pub, epm, nepm) < 0)
goto Err;
rsapubfree(pub);
- setSecrets(sec, kd, nkd);
+ if(!sec->emsActive)
+ setSecrets(sec, kd, nkd);
return 0;
Err:
@@ -2801,31 +2884,84 @@ buildRenegInfoServer(TlsConnection *c, ExtCtx *ctx, uc
return 1;
}
+/*
+ * RFC 7627 Section 5.1: client always advertises extended_master_secret
+ * (empty payload). Lenient default; the strict-mode opt-in
+ * (TLSconn.emsstrict) only fires if the server fails to echo back.
+ */
+static int
+buildEms(TlsConnection *c, ExtCtx *ctx, uchar *out, int outsize)
+{
+ USED(c); USED(ctx); USED(out); USED(outsize);
+ return 0; /* zero-length payload */
+}
+
+/*
+ * RFC 7627 Section 5.1 server side: client offered EMS, payload must
+ * be empty. Record on c->extEmsSeen so the ack builder echoes and the
+ * tlsServer2 path activates EMS finalize.
+ */
+static int
+parseEmsServer(TlsConnection *c, ExtCtx *ctx, uchar *in, int inlen)
+{
+ USED(ctx); USED(in);
+ if(inlen != 0)
+ return -1;
+ c->extEmsSeen = 1;
+ return 0;
+}
+
+/*
+ * RFC 7627 Section 5.1 client side: server confirmed EMS in ServerHello.
+ * Payload must be empty. Set c->extEmsSeen so tlsClient2 can flip
+ * emsActive on once the parse returns.
+ */
+static int
+parseEmsClient(TlsConnection *c, ExtCtx *ctx, uchar *in, int inlen)
+{
+ USED(ctx); USED(in);
+ if(inlen != 0)
+ return -1;
+ c->extEmsSeen = 1;
+ return 0;
+}
+
+/*
+ * RFC 7627 Section 5.1 server side: echo empty extended_master_secret
+ * iff client advertised it; declined otherwise.
+ */
+static int
+buildEmsAck(TlsConnection *c, ExtCtx *ctx, uchar *out, int outsize)
+{
+ USED(ctx); USED(out); USED(outsize);
+ if(!c->extEmsSeen)
+ return -1;
+ return 0; /* zero-length payload */
+}
+
static ExtAlg clientHelloBuilders[] = {
{ ExtSni, buildSni, nil },
{ ExtSupportedGroups, buildSupportedGroups, nil },
{ ExtEcPointFormats, buildEcPointFormats, nil },
{ ExtSigalgs, buildSigalgs, nil },
+ { ExtExtendedMasterSecret, buildEms, nil },
};
static ExtAlg clientHelloParsers[] = {
{ ExtSni, nil, parseSniServer },
{ ExtEcPointFormats, nil, parseEcPointFormatsServer },
{ ExtSigalgs, nil, parseSigalgsServer },
{ ExtRenegotiationInfo, nil, parseRenegInfoServer },
+ { ExtExtendedMasterSecret, nil, parseEmsServer },
};
static ExtAlg serverHelloBuilders[] = {
{ ExtSni, buildSniAck, nil },
{ ExtEcPointFormats, buildEcPointFormatsAck, nil },
{ ExtRenegotiationInfo, buildRenegInfoServer, nil },
+ { ExtExtendedMasterSecret, buildEmsAck, nil },
};
-/*
- * serverHelloParsers is empty in this baseline -- the client side has
- * no extensions that require state beyond what's already validated by
- * the framing code in parseExtensionBlock. When a future patch needs
- * to consume a ServerHello extension (e.g., extended_master_secret,
- * RFC 7627), it adds an entry here plus the matching parse function.
- */
-static ExtAlg serverHelloParsers[1]; /* unused; nServerHelloParsers == 0 */
+static ExtAlg serverHelloParsers[] = {
+ { ExtExtendedMasterSecret, nil, parseEmsClient },
+};
/*
* Walk the table; for each entry that wants to be sent emit
@@ -3482,6 +3618,7 @@ tlsSecClose(TlsSec *sec)
return;
factotum_rsa_close(sec->rpc);
free(sec->server);
+ freebytes(sec->pms);
free(sec);
}
@@ -3543,10 +3680,50 @@ setMasterSecret(TlsSec *sec, Bytes *pm)
static void
setMasterSecret(TlsSec *sec, Bytes *pm)
{
+ /*
+ * RFC 7627 Section 4: when EMS is active, master_secret is
+ * derived from session_hash, not the client+server randoms.
+ * session_hash is not yet available at every call site (the
+ * client computes a pre-master before CKE is on the wire); stash
+ * a clone of the pre-master so the EMS finalize can run the PRF
+ * once the snapshot exists.
+ */
+ if(sec->emsActive){
+ freebytes(sec->pms);
+ sec->pms = makebytes(pm->data, pm->len);
+ return;
+ }
(*sec->prf)(sec->sec, MasterSecretSize, pm->data, pm->len, "master secret",
sec->crandom, RandomSize, sec->srandom, RandomSize);
}
+/*
+ * RFC 7627 Section 4: extended master_secret = PRF(pre_master_secret,
+ * "extended master secret", session_hash). Caller has populated
+ * sec->pms via setMasterSecret while emsActive, and sec->sessionHash
+ * via handshakeHashSnapshot taken after ClientKeyExchange has been
+ * absorbed into the running handshake hash.
+ */
+static int
+tlsSecMasterSecretEms(TlsSec *sec)
+{
+ if(sec->prf == nil){
+ werrstr("nil sec->prf in tlsSecMasterSecretEms");
+ return -1;
+ }
+ if(sec->pms == nil || sec->sessionHashLen == 0){
+ werrstr("EMS finalize: missing pre-master or session_hash");
+ return -1;
+ }
+ (*sec->prf)(sec->sec, MasterSecretSize, sec->pms->data, sec->pms->len,
+ "extended master secret",
+ sec->sessionHash, sec->sessionHashLen, nil, 0);
+ memset(sec->pms->data, 0, sec->pms->len);
+ freebytes(sec->pms);
+ sec->pms = nil;
+ return 0;
+}
+
static void
serverMasterSecret(TlsSec *sec, uchar *epm, int nepm)
{
@@ -3674,6 +3851,27 @@ tls12SetFinished(TlsSec *sec, HandHash hs, uchar *fini
else
label = "server finished";
tlsPsha2_256(finished, TLSFinishedLen, sec->sec, MasterSecretSize, (uchar*)label, strlen(label), h, SHA2_256dlen);
+}
+
+/*
+ * Snapshot the running handshake hash without disturbing it: copy
+ * HandHash by value (mirror of tls12SetFinished's hs-by-value idiom),
+ * clear the malloced flags so the finalize cannot free embedded
+ * state, finalize SHA-256 into out. TLS 1.2 cipher suites we
+ * implement all use SHA-256 for the Finished MAC, which RFC 7627
+ * Section 3 ties session_hash to.
+ */
+static int
+handshakeHashSnapshot(TlsConnection *c, uchar *out)
+{
+ HandHash hs;
+
+ hs = c->hs;
+ hs.md5.malloced = 0;
+ hs.sha1.malloced = 0;
+ hs.sha2_256.malloced = 0;
+ sha2_256(nil, 0, out, &hs.sha2_256);
+ return SHA2_256dlen;
}
static void
|