Plan 9 from Bell Labs’s /usr/web/sources/contrib/mospak/tls-1.2/tls-extended-master-secret.diff

Copyright © 2021 Plan 9 Foundation.
Distributed under the MIT License.
Download the Plan 9 distribution.


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

Bell Labs OSI certified Powered by Plan 9

(Return to Plan 9 Home Page)

Copyright © 2021 Plan 9 Foundation. All Rights Reserved.
Comments to webmaster@9p.io.