diff options
Diffstat (limited to 'lib/ssl')
-rw-r--r-- | lib/ssl/ssl3con.c | 163 | ||||
-rw-r--r-- | lib/ssl/ssl3ext.c | 10 | ||||
-rw-r--r-- | lib/ssl/ssl3ext.h | 10 | ||||
-rw-r--r-- | lib/ssl/sslexp.h | 8 | ||||
-rw-r--r-- | lib/ssl/sslimpl.h | 11 | ||||
-rw-r--r-- | lib/ssl/sslsecur.c | 1 | ||||
-rw-r--r-- | lib/ssl/sslsock.c | 15 | ||||
-rw-r--r-- | lib/ssl/sslt.h | 3 | ||||
-rw-r--r-- | lib/ssl/tls13con.c | 84 | ||||
-rw-r--r-- | lib/ssl/tls13con.h | 2 | ||||
-rw-r--r-- | lib/ssl/tls13ech.c | 785 | ||||
-rw-r--r-- | lib/ssl/tls13ech.h | 25 | ||||
-rw-r--r-- | lib/ssl/tls13exthandle.c | 102 | ||||
-rw-r--r-- | lib/ssl/tls13exthandle.h | 3 | ||||
-rw-r--r-- | lib/ssl/tls13hashstate.c | 241 | ||||
-rw-r--r-- | lib/ssl/tls13hashstate.h | 15 |
16 files changed, 883 insertions, 595 deletions
diff --git a/lib/ssl/ssl3con.c b/lib/ssl/ssl3con.c index cd36b80a5..fd491f896 100644 --- a/lib/ssl/ssl3con.c +++ b/lib/ssl/ssl3con.c @@ -70,6 +70,9 @@ PRBool ssl_IsRsaPssSignatureScheme(SSLSignatureScheme scheme); PRBool ssl_IsRsaeSignatureScheme(SSLSignatureScheme scheme); PRBool ssl_IsRsaPkcs1SignatureScheme(SSLSignatureScheme scheme); PRBool ssl_IsDsaSignatureScheme(SSLSignatureScheme scheme); +static SECStatus ssl3_UpdateDefaultHandshakeHashes(sslSocket *ss, + const unsigned char *b, + unsigned int l); const PRUint8 ssl_hello_retry_random[] = { 0xCF, 0x21, 0xAD, 0x74, 0xE5, 0x9A, 0x61, 0x11, @@ -3848,11 +3851,18 @@ ssl3_InitHandshakeHashes(sslSocket *ss) if (ss->ssl3.hs.hashType != handshake_hash_record && ss->ssl3.hs.messages.len > 0) { - if (ssl3_UpdateHandshakeHashes(ss, ss->ssl3.hs.messages.buf, - ss->ssl3.hs.messages.len) != SECSuccess) { + /* When doing ECH, ssl3_UpdateHandshakeHashes will store outer messages into + * the both the outer and inner transcripts. ssl3_UpdateDefaultHandshakeHashes + * uses only the default context (which is the outer when doing ECH). */ + if (ssl3_UpdateDefaultHandshakeHashes(ss, ss->ssl3.hs.messages.buf, + ss->ssl3.hs.messages.len) != SECSuccess) { return SECFailure; } - sslBuffer_Clear(&ss->ssl3.hs.messages); + /* When doing ECH, deriving accept_confirmation requires all messages + * up to SH, then a synthetic SH. Don't free the buffers just yet. */ + if (!ss->ssl3.hs.echHpkeCtx) { + sslBuffer_Clear(&ss->ssl3.hs.messages); + } } if (ss->ssl3.hs.shaEchInner && ss->ssl3.hs.echInnerMessages.len > 0) { @@ -3861,7 +3871,9 @@ ssl3_InitHandshakeHashes(sslSocket *ss) ssl_MapLowLevelError(SSL_ERROR_DIGEST_FAILURE); return SECFailure; } - sslBuffer_Clear(&ss->ssl3.hs.echInnerMessages); + if (!ss->ssl3.hs.echHpkeCtx) { + sslBuffer_Clear(&ss->ssl3.hs.echInnerMessages); + } } return SECSuccess; @@ -3893,52 +3905,28 @@ ssl3_RestartHandshakeHashes(sslSocket *ss) } } -/* For TLS 1.3 EncryptedClientHello, add the provided buffer to the - * given hash context. This is only needed for the initial CH, - * after which ssl3_UpdateHandshakeHashes will update both contexts - * until ssl3_CoalesceEchHandshakeHashes. */ +/* Add the provided bytes to the handshake hash context. When doing + * TLS 1.3 ECH, |target| may be provided to specify only the inner/outer + * transcript, else the input is added to both contexts. This happens + * only on the client. On the server, only the default context is used. */ SECStatus -ssl3_UpdateExplicitHandshakeTranscript(sslSocket *ss, const unsigned char *b, - unsigned int l, sslBuffer *target) -{ - PORT_Assert(ss->opt.noLocks || ssl_HaveSSL3HandshakeLock(ss)); - PORT_Assert(ss->vrange.max >= SSL_LIBRARY_VERSION_TLS_1_3); - if (ss->sec.isServer) { - /* Only the client maintains two states at the outset. */ - PORT_Assert(target != &ss->ssl3.hs.echInnerMessages); - } - return sslBuffer_Append(target, b, l); -} -static SECStatus -ssl3_UpdateOuterHandshakeHashes(sslSocket *ss, const unsigned char *b, - unsigned int l) +ssl3_UpdateHandshakeHashesInt(sslSocket *ss, const unsigned char *b, + unsigned int l, sslBuffer *target) { - return ssl3_UpdateExplicitHandshakeTranscript(ss, b, l, - &ss->ssl3.hs.messages); -} -static SECStatus -ssl3_UpdateInnerHandshakeHashes(sslSocket *ss, const unsigned char *b, - unsigned int l) -{ - return ssl3_UpdateExplicitHandshakeTranscript(ss, b, l, - &ss->ssl3.hs.echInnerMessages); -} -/* - * Handshake messages - */ -/* Called from ssl3_InitHandshakeHashes() -** ssl3_AppendHandshake() -** ssl3_HandleV2ClientHello() -** ssl3_HandleHandshakeMessage() -** Caller must hold the ssl3Handshake lock. -*/ -SECStatus -ssl3_UpdateHandshakeHashes(sslSocket *ss, const unsigned char *b, unsigned int l) -{ - SECStatus rv = SECSuccess; + SECStatus rv = SECSuccess; + PRBool explicit = (target != NULL); + PRBool appendToEchInner = !ss->sec.isServer && + ss->ssl3.hs.echHpkeCtx && + !explicit; PORT_Assert(ss->opt.noLocks || ssl_HaveSSL3HandshakeLock(ss)); + PORT_Assert(target != &ss->ssl3.hs.echInnerMessages || + !ss->sec.isServer); + if (target == NULL) { + /* Default context. */ + target = &ss->ssl3.hs.messages; + } /* With TLS 1.3, and versions TLS.1.1 and older, we keep the hash(es) * always up to date. However, we must initially buffer the handshake * messages, until we know what to do. @@ -3950,15 +3938,14 @@ ssl3_UpdateHandshakeHashes(sslSocket *ss, const unsigned char *b, unsigned int l * and never update the hash, because the hash function we must use for * certificate_verify might be different from the hash function we use * when signing other handshake hashes. */ - if (ss->ssl3.hs.hashType == handshake_hash_unknown || ss->ssl3.hs.hashType == handshake_hash_record) { - rv = sslBuffer_Append(&ss->ssl3.hs.messages, b, l); + rv = sslBuffer_Append(target, b, l); if (rv != SECSuccess) { return SECFailure; } - if (!ss->sec.isServer && ss->ssl3.hs.echHpkeCtx) { - return ssl3_UpdateInnerHandshakeHashes(ss, b, l); + if (appendToEchInner) { + return sslBuffer_Append(&ss->ssl3.hs.echInnerMessages, b, l); } return SECSuccess; } @@ -3967,10 +3954,20 @@ ssl3_UpdateHandshakeHashes(sslSocket *ss, const unsigned char *b, unsigned int l if (ss->ssl3.hs.hashType == handshake_hash_single) { PORT_Assert(ss->version >= SSL_LIBRARY_VERSION_TLS_1_3); - rv = PK11_DigestOp(ss->ssl3.hs.sha, b, l); - if (rv != SECSuccess) { - ssl_MapLowLevelError(SSL_ERROR_DIGEST_FAILURE); - return rv; + if (target == &ss->ssl3.hs.messages) { + rv = PK11_DigestOp(ss->ssl3.hs.sha, b, l); + if (rv != SECSuccess) { + ssl_MapLowLevelError(SSL_ERROR_DIGEST_FAILURE); + return rv; + } + } + if (ss->ssl3.hs.shaEchInner && + (target == &ss->ssl3.hs.echInnerMessages || !explicit)) { + rv = PK11_DigestOp(ss->ssl3.hs.shaEchInner, b, l); + if (rv != SECSuccess) { + ssl_MapLowLevelError(SSL_ERROR_DIGEST_FAILURE); + return rv; + } } } else if (ss->ssl3.hs.hashType == handshake_hash_combo) { rv = PK11_DigestOp(ss->ssl3.hs.md5, b, l); @@ -3987,6 +3984,37 @@ ssl3_UpdateHandshakeHashes(sslSocket *ss, const unsigned char *b, unsigned int l return rv; } +static SECStatus +ssl3_UpdateDefaultHandshakeHashes(sslSocket *ss, const unsigned char *b, + unsigned int l) +{ + return ssl3_UpdateHandshakeHashesInt(ss, b, l, + &ss->ssl3.hs.messages); +} + +static SECStatus +ssl3_UpdateInnerHandshakeHashes(sslSocket *ss, const unsigned char *b, + unsigned int l) +{ + return ssl3_UpdateHandshakeHashesInt(ss, b, l, + &ss->ssl3.hs.echInnerMessages); +} + +/* + * Handshake messages + */ +/* Called from ssl3_InitHandshakeHashes() +** ssl3_AppendHandshake() +** ssl3_HandleV2ClientHello() +** ssl3_HandleHandshakeMessage() +** Caller must hold the ssl3Handshake lock. +*/ +SECStatus +ssl3_UpdateHandshakeHashes(sslSocket *ss, const unsigned char *b, unsigned int l) +{ + return ssl3_UpdateHandshakeHashesInt(ss, b, l, NULL); +} + SECStatus ssl3_UpdatePostHandshakeHashes(sslSocket *ss, const unsigned char *b, unsigned int l) { @@ -5513,7 +5541,7 @@ ssl3_SendClientHello(sslSocket *ss, sslClientHelloType type) if (rv != SECSuccess) { goto loser; /* code set */ } - rv = ssl3_UpdateOuterHandshakeHashes(ss, chBuf.buf, chBuf.len); + rv = ssl3_UpdateDefaultHandshakeHashes(ss, chBuf.buf, chBuf.len); if (rv != SECSuccess) { goto loser; /* code set */ } @@ -7064,11 +7092,6 @@ ssl3_HandleServerHello(sslSocket *ss, PRUint8 *b, PRUint32 length) return SECSuccess; } - rv = tls13_MaybeHandleEchSignal(ss); - if (rv != SECSuccess) { - goto alert_loser; - } - rv = ssl3_HandleParsedExtensions(ss, ssl_hs_server_hello); ssl3_DestroyRemoteExtensions(&ss->ssl3.hs.remoteExtensions); if (rv != SECSuccess) { @@ -7082,7 +7105,7 @@ ssl3_HandleServerHello(sslSocket *ss, PRUint8 *b, PRUint32 length) } if (ss->version >= SSL_LIBRARY_VERSION_TLS_1_3) { - rv = tls13_HandleServerHelloPart2(ss); + rv = tls13_HandleServerHelloPart2(ss, savedMsg, savedLength); if (rv != SECSuccess) { errCode = PORT_GetError(); goto loser; @@ -7093,6 +7116,7 @@ ssl3_HandleServerHello(sslSocket *ss, PRUint8 *b, PRUint32 length) goto loser; } + ss->ssl3.hs.preliminaryInfo |= ssl_preinfo_ech; return SECSuccess; alert_loser: @@ -8628,13 +8652,6 @@ ssl_GenerateServerRandom(sslSocket *ss) return SECFailure; } - if (ss->ssl3.hs.echAccepted) { - rv = tls13_WriteServerEchSignal(ss); - if (rv != SECSuccess) { - return SECFailure; - } - } - if (ss->version == ss->vrange.max) { return SECSuccess; } @@ -9775,6 +9792,15 @@ ssl_ConstructServerHello(sslSocket *ss, PRBool helloRetry, } } + if (!helloRetry && ssl3_ExtensionNegotiated(ss, ssl_tls13_ech_is_inner_xtn)) { + /* Signal ECH acceptance if we handled handled both CHOuter/CHInner (i.e. + * in shared mode), or if we received a CHInner in split/backend mode. */ + if (ss->ssl3.hs.echAccepted || ss->opt.enableTls13BackendEch) { + return tls13_WriteServerEchSignal(ss, SSL_BUFFER_BASE(messageBuf), + SSL_BUFFER_LEN(messageBuf)); + } + } + return SECSuccess; } @@ -12284,7 +12310,7 @@ ssl_HashHandshakeMessageDefault(sslSocket *ss, SSLHandshakeType ct, const PRUint8 *b, PRUint32 length) { return ssl_HashHandshakeMessageInt(ss, ct, ss->ssl3.hs.recvMessageSeq, - b, length, ssl3_UpdateOuterHandshakeHashes); + b, length, ssl3_UpdateDefaultHandshakeHashes); } SECStatus ssl_HashHandshakeMessageEchInner(sslSocket *ss, SSLHandshakeType ct, @@ -13847,6 +13873,7 @@ ssl3_DestroySSL3Info(sslSocket *ss) /* TLS 1.3 ECH state. */ PK11_HPKE_DestroyContext(ss->ssl3.hs.echHpkeCtx, PR_TRUE); PORT_Free((void *)ss->ssl3.hs.echPublicName); /* CONST */ + sslBuffer_Clear(&ss->ssl3.hs.greaseEchBuf); } /* diff --git a/lib/ssl/ssl3ext.c b/lib/ssl/ssl3ext.c index 78c2b901a..199cf459a 100644 --- a/lib/ssl/ssl3ext.c +++ b/lib/ssl/ssl3ext.c @@ -15,6 +15,7 @@ #include "sslimpl.h" #include "sslproto.h" #include "ssl3exthandle.h" +#include "tls13ech.h" #include "tls13err.h" #include "tls13exthandle.h" #include "tls13subcerts.h" @@ -54,6 +55,7 @@ static const ssl3ExtensionHandler clientHelloHandlers[] = { { ssl_tls13_psk_key_exchange_modes_xtn, &tls13_ServerHandlePskModesXtn }, { ssl_tls13_cookie_xtn, &tls13_ServerHandleCookieXtn }, { ssl_tls13_post_handshake_auth_xtn, &tls13_ServerHandlePostHandshakeAuthXtn }, + { ssl_tls13_ech_is_inner_xtn, &tls13_ServerHandleEchIsInnerXtn }, { ssl_record_size_limit_xtn, &ssl_HandleRecordSizeLimitXtn }, { 0, NULL } }; @@ -1020,12 +1022,8 @@ ssl3_DestroyExtensionData(TLSExtensionData *xtnData) PORT_Free(xtnData->advertised); tls13_DestroyDelegatedCredential(xtnData->peerDelegCred); - /* ECH State */ - SECITEM_FreeItem(&xtnData->innerCh, PR_FALSE); - SECITEM_FreeItem(&xtnData->echSenderPubKey, PR_FALSE); - SECITEM_FreeItem(&xtnData->echConfigId, PR_FALSE); - SECITEM_FreeItem(&xtnData->echRetryConfigs, PR_FALSE); - xtnData->echRetryConfigsValid = PR_FALSE; + tls13_DestroyEchXtnState(xtnData->ech); + xtnData->ech = NULL; } /* Free everything that has been allocated and then reset back to diff --git a/lib/ssl/ssl3ext.h b/lib/ssl/ssl3ext.h index 45510041e..685a7a99c 100644 --- a/lib/ssl/ssl3ext.h +++ b/lib/ssl/ssl3ext.h @@ -131,13 +131,9 @@ struct TLSExtensionDataStr { * rather through tls13_DestoryPskList(). */ sslPsk *selectedPsk; - /* ECH working state. */ - SECItem innerCh; /* Server: "payload value of ClientECH. */ - SECItem echSenderPubKey; /* Server: "enc value of ClientECH, required for CHInner decryption. */ - SECItem echConfigId; /* Server: "config_id" value of ClientECH. */ - PRUint32 echCipherSuite; /* Server: "cipher_suite" value of ClientECH. */ - SECItem echRetryConfigs; /* Client: Retry_configs from ServerEncryptedCH. */ - PRBool echRetryConfigsValid; /* Client: Permits retry_configs to be extracted. */ + /* ECH working state. Non-null when a valid Encrypted Client Hello extension + * was received. */ + sslEchXtnState *ech; }; typedef struct TLSExtensionStr { diff --git a/lib/ssl/sslexp.h b/lib/ssl/sslexp.h index a02f0f351..8bacc6b42 100644 --- a/lib/ssl/sslexp.h +++ b/lib/ssl/sslexp.h @@ -509,6 +509,14 @@ typedef SECStatus(PR_CALLBACK *SSLResumptionTokenCallback)( SSL_EXPERIMENTAL_API("SSL_EnableTls13GreaseEch", \ (PRFileDesc * _fd, PRBool _enabled), (fd, enabled)) +/* If |enabled|, a server receiving a Client Hello containing the ech_is_inner + * (and not encrypted_client_hello) extension will respond with the ECH + * acceptance signal. This signals the client to continue with the inner + * transcript rather than outer. */ +#define SSL_EnableTls13BackendEch(fd, enabled) \ + SSL_EXPERIMENTAL_API("SSL_EnableTls13BackendEch", \ + (PRFileDesc * _fd, PRBool _enabled), (fd, enabled)) + /* Called by the client after an initial ECH connection fails with * SSL_ERROR_ECH_RETRY_WITH_ECH. Returns compatible ECHConfigs, which * are configured via SetClientEchConfigs for an ECH retry attempt. diff --git a/lib/ssl/sslimpl.h b/lib/ssl/sslimpl.h index a126cb8c3..1b7dfb107 100644 --- a/lib/ssl/sslimpl.h +++ b/lib/ssl/sslimpl.h @@ -36,9 +36,9 @@ typedef struct sslSocketStr sslSocket; typedef struct sslNamedGroupDefStr sslNamedGroupDef; -typedef struct sslEsniKeysStr sslEsniKeys; typedef struct sslEchConfigStr sslEchConfig; typedef struct sslEchConfigContentsStr sslEchConfigContents; +typedef struct sslEchXtnStateStr sslEchXtnState; typedef struct sslPskStr sslPsk; typedef struct sslDelegatedCredentialStr sslDelegatedCredential; typedef struct sslEphemeralKeyPairStr sslEphemeralKeyPair; @@ -287,6 +287,7 @@ typedef struct sslOptionsStr { unsigned int enableDtls13VersionCompat : 1; unsigned int suppressEndOfEarlyData : 1; unsigned int enableTls13GreaseEch : 1; + unsigned int enableTls13BackendEch : 1; } sslOptions; typedef enum { sslHandshakingUndetermined = 0, @@ -748,6 +749,7 @@ typedef struct SSL3HandshakeStateStr { HpkeContext *echHpkeCtx; /* Client/Server: HPKE context for ECH. */ const char *echPublicName; /* Client: If rejected, the ECHConfig.publicName to * use for certificate verification. */ + sslBuffer greaseEchBuf; /* Client: Remember GREASE ECH, as advertised, for CH2 (HRR case). */ } SSL3HandshakeState; @@ -1122,8 +1124,9 @@ struct sslSocketStr { SSLProtocolVariant protocolVariant; /* TLS 1.3 Encrypted Client Hello. */ - PRCList echConfigs; /* Client/server: Must not change while hs is in-progress. */ - SECKEYPublicKey *echPubKey; /* Server: The ECH keypair used in HPKE setup */ + PRCList echConfigs; /* Client/server: Must not change while hs + * is in-progress. */ + SECKEYPublicKey *echPubKey; /* Server: The ECH keypair used in HPKE. */ SECKEYPrivateKey *echPrivKey; /* As above. */ /* Anti-replay for TLS 1.3 0-RTT. */ @@ -1948,6 +1951,8 @@ SECStatus SSLExp_DestroyMaskingContext(SSLMaskingContext *ctx); SECStatus SSLExp_EnableTls13GreaseEch(PRFileDesc *fd, PRBool enabled); +SECStatus SSLExp_EnableTls13BackendEch(PRFileDesc *fd, PRBool enabled); + SEC_END_PROTOS #if defined(XP_UNIX) || defined(XP_OS2) || defined(XP_BEOS) diff --git a/lib/ssl/sslsecur.c b/lib/ssl/sslsecur.c index 162fc66d0..2c9a4dbf9 100644 --- a/lib/ssl/sslsecur.c +++ b/lib/ssl/sslsecur.c @@ -183,6 +183,7 @@ SSL_ResetHandshake(PRFileDesc *s, PRBool asServer) PORT_Assert(ss->ssl3.hs.echPublicName); PORT_Free((void *)ss->ssl3.hs.echPublicName); /* CONST */ ss->ssl3.hs.echPublicName = NULL; + sslBuffer_Clear(&ss->ssl3.hs.greaseEchBuf); } if (!ss->TCPconnected) diff --git a/lib/ssl/sslsock.c b/lib/ssl/sslsock.c index e075e23c8..b698d8f43 100644 --- a/lib/ssl/sslsock.c +++ b/lib/ssl/sslsock.c @@ -93,7 +93,8 @@ static sslOptions ssl_defaults = { .enableV2CompatibleHello = PR_FALSE, .enablePostHandshakeAuth = PR_FALSE, .suppressEndOfEarlyData = PR_FALSE, - .enableTls13GreaseEch = PR_FALSE + .enableTls13GreaseEch = PR_FALSE, + .enableTls13BackendEch = PR_FALSE }; /* @@ -4293,6 +4294,7 @@ struct { EXP(DestroyAead), EXP(DestroyMaskingContext), EXP(DestroyResumptionTokenInfo), + EXP(EnableTls13BackendEch), EXP(EnableTls13GreaseEch), EXP(EncodeEchConfig), EXP(GetCurrentEpoch), @@ -4372,6 +4374,17 @@ SSLExp_EnableTls13GreaseEch(PRFileDesc *fd, PRBool enabled) } SECStatus +SSLExp_EnableTls13BackendEch(PRFileDesc *fd, PRBool enabled) +{ + sslSocket *ss = ssl_FindSocket(fd); + if (!ss) { + return SECFailure; + } + ss->opt.enableTls13BackendEch = enabled; + return SECSuccess; +} + +SECStatus SSLExp_SetDtls13VersionWorkaround(PRFileDesc *fd, PRBool enabled) { sslSocket *ss = ssl_FindSocket(fd); diff --git a/lib/ssl/sslt.h b/lib/ssl/sslt.h index f1713069b..1d5c4d179 100644 --- a/lib/ssl/sslt.h +++ b/lib/ssl/sslt.h @@ -545,8 +545,9 @@ typedef enum { ssl_next_proto_nego_xtn = 13172, /* Deprecated. */ ssl_renegotiation_info_xtn = 0xff01, ssl_tls13_short_header_xtn = 0xff03, /* Deprecated. */ + ssl_tls13_ech_is_inner_xtn = 0xda09, ssl_tls13_outer_extensions_xtn = 0xfd00, - ssl_tls13_encrypted_client_hello_xtn = 0xfe08, + ssl_tls13_encrypted_client_hello_xtn = 0xfe09, ssl_tls13_encrypted_sni_xtn = 0xffce, /* Deprecated. */ } SSLExtensionType; diff --git a/lib/ssl/tls13con.c b/lib/ssl/tls13con.c index a10962981..1347f3fe2 100644 --- a/lib/ssl/tls13con.c +++ b/lib/ssl/tls13con.c @@ -61,7 +61,7 @@ tls13_DeriveSecretWrap(sslSocket *ss, PK11SymKey *key, const char *suffix, const char *keylogLabel, PK11SymKey **dest); -static SECStatus +SECStatus tls13_DeriveSecret(sslSocket *ss, PK11SymKey *key, const char *label, unsigned int labelLen, @@ -1184,13 +1184,12 @@ tls13_DeriveEarlySecrets(sslSocket *ss) } static SECStatus -tls13_ComputeHandshakeSecrets(sslSocket *ss) +tls13_ComputeHandshakeSecret(sslSocket *ss) { SECStatus rv; PK11SymKey *derivedSecret = NULL; PK11SymKey *newSecret = NULL; - - SSL_TRC(5, ("%d: TLS13[%d]: compute handshake secrets (%s)", + SSL_TRC(5, ("%d: TLS13[%d]: compute handshake secret (%s)", SSL_GETPID(), ss->fd, SSL_ROLE(ss))); /* If no PSK, generate the default early secret. */ @@ -1205,7 +1204,7 @@ tls13_ComputeHandshakeSecrets(sslSocket *ss) PORT_Assert(ss->ssl3.hs.currentSecret); PORT_Assert(ss->ssl3.hs.dheSecret); - /* Expand before we extract. */ + /* Derive-Secret(., "derived", "") */ rv = tls13_DeriveSecretNullHash(ss, ss->ssl3.hs.currentSecret, kHkdfLabelDerivedSecret, strlen(kHkdfLabelDerivedSecret), @@ -1215,18 +1214,32 @@ tls13_ComputeHandshakeSecrets(sslSocket *ss) return rv; } + /* HKDF-Extract(ECDHE, .) = Handshake Secret */ rv = tls13_HkdfExtract(derivedSecret, ss->ssl3.hs.dheSecret, tls13_GetHash(ss), &newSecret); PK11_FreeSymKey(derivedSecret); - if (rv != SECSuccess) { LOG_ERROR(ss, SEC_ERROR_LIBRARY_FAILURE); return rv; } - PK11_FreeSymKey(ss->ssl3.hs.dheSecret); - ss->ssl3.hs.dheSecret = NULL; + PK11_FreeSymKey(ss->ssl3.hs.currentSecret); ss->ssl3.hs.currentSecret = newSecret; + return SECSuccess; +} + +static SECStatus +tls13_ComputeHandshakeSecrets(sslSocket *ss) +{ + SECStatus rv; + PK11SymKey *derivedSecret = NULL; + PK11SymKey *newSecret = NULL; + + PK11_FreeSymKey(ss->ssl3.hs.dheSecret); + ss->ssl3.hs.dheSecret = NULL; + + SSL_TRC(5, ("%d: TLS13[%d]: compute handshake secrets (%s)", + SSL_GETPID(), ss->fd, SSL_ROLE(ss))); /* Now compute |*HsTrafficSecret| */ rv = tls13_DeriveSecretWrap(ss, ss->ssl3.hs.currentSecret, @@ -1865,22 +1878,16 @@ tls13_HandleClientHelloPart2(sslSocket *ss, PRINT_BUF(50, (ss, "Client sent cookie", ss->xtnData.cookie.data, ss->xtnData.cookie.len)); - rv = tls13_RecoverHashState(ss, ss->xtnData.cookie.data, - ss->xtnData.cookie.len, - &previousCipherSuite, - &previousGroup, - &previousEchOffered); + rv = tls13_HandleHrrCookie(ss, ss->xtnData.cookie.data, + ss->xtnData.cookie.len, + &previousCipherSuite, + &previousGroup, + &previousEchOffered, + NULL, NULL, NULL, NULL, PR_TRUE); if (rv != SECSuccess) { FATAL_ERROR(ss, SSL_ERROR_BAD_2ND_CLIENT_HELLO, illegal_parameter); goto loser; } - - /* CH1/CH2 must either both include ECH, or both exclude it. */ - if ((ss->xtnData.echConfigId.len > 0) != previousEchOffered) { - FATAL_ERROR(ss, SSL_ERROR_BAD_2ND_CLIENT_HELLO, - illegal_parameter); - goto loser; - } } /* Now merge the ClientHello into the hash state. */ @@ -1936,6 +1943,13 @@ tls13_HandleClientHelloPart2(sslSocket *ss, goto loser; } + /* CH1/CH2 must either both include ECH, or both exclude it. */ + if (previousEchOffered != (ss->xtnData.ech != NULL)) { + FATAL_ERROR(ss, SSL_ERROR_BAD_2ND_CLIENT_HELLO, + previousEchOffered ? missing_extension : illegal_parameter); + goto loser; + } + /* If we requested a new key share, check that the client provided just * one of the right type. */ if (previousGroup) { @@ -2825,6 +2839,11 @@ tls13_SendServerHelloSequence(sslSocket *ss) return SECFailure; } + rv = tls13_ComputeHandshakeSecret(ss); + if (rv != SECSuccess) { + return SECFailure; /* error code is set. */ + } + rv = ssl3_SendServerHello(ss); if (rv != SECSuccess) { return rv; /* err code is set. */ @@ -2909,7 +2928,7 @@ tls13_SendServerHelloSequence(sslSocket *ss) } SECStatus -tls13_HandleServerHelloPart2(sslSocket *ss) +tls13_HandleServerHelloPart2(sslSocket *ss, const PRUint8 *savedMsg, PRUint32 savedLength) { SECStatus rv; sslSessionID *sid = ss->sec.ci.sid; @@ -2991,6 +3010,17 @@ tls13_HandleServerHelloPart2(sslSocket *ss) if (rv != SECSuccess) { return SECFailure; } + + rv = tls13_ComputeHandshakeSecret(ss); + if (rv != SECSuccess) { + return SECFailure; /* error code is set. */ + } + + rv = tls13_MaybeHandleEchSignal(ss, savedMsg, savedLength); + if (rv != SECSuccess) { + return SECFailure; /* error code is set. */ + } + rv = tls13_ComputeHandshakeSecrets(ss); if (rv != SECSuccess) { return SECFailure; /* error code is set. */ @@ -4960,15 +4990,16 @@ tls13_FinishHandshake(sslSocket *ss) TLS13_SET_HS_STATE(ss, idle_handshake); - if (offeredEch && - !ssl3_ExtensionNegotiated(ss, ssl_tls13_encrypted_client_hello_xtn)) { + PORT_Assert(ss->ssl3.hs.echAccepted == + ssl3_ExtensionNegotiated(ss, ssl_tls13_encrypted_client_hello_xtn)); + if (offeredEch && !ss->ssl3.hs.echAccepted) { SSL3_SendAlert(ss, alert_fatal, ech_required); - /* "If [one, none] of the values contains a supported version, the client can + /* "If [one, none] of the retry_configs contains a supported version, the client can * regard ECH as securely [replaced, disabled] by the server." */ - if (ss->xtnData.echRetryConfigs.len) { + if (ss->xtnData.ech && ss->xtnData.ech->retryConfigs.len) { PORT_SetError(SSL_ERROR_ECH_RETRY_WITH_ECH); - ss->xtnData.echRetryConfigsValid = PR_TRUE; + ss->xtnData.ech->retryConfigsValid = PR_TRUE; } else { PORT_SetError(SSL_ERROR_ECH_RETRY_WITHOUT_ECH); } @@ -5520,6 +5551,7 @@ static const struct { hello_retry_request) }, { ssl_record_size_limit_xtn, _M2(client_hello, encrypted_extensions) }, { ssl_tls13_encrypted_client_hello_xtn, _M2(client_hello, encrypted_extensions) }, + { ssl_tls13_ech_is_inner_xtn, _M1(client_hello) }, { ssl_tls13_outer_extensions_xtn, _M_NONE /* Encoding/decoding only */ }, { ssl_tls13_post_handshake_auth_xtn, _M1(client_hello) } }; diff --git a/lib/ssl/tls13con.h b/lib/ssl/tls13con.h index ae0b4ae33..8c648ac30 100644 --- a/lib/ssl/tls13con.h +++ b/lib/ssl/tls13con.h @@ -76,7 +76,7 @@ SECStatus tls13_HandleClientHelloPart2(sslSocket *ss, sslSessionID *sid, const PRUint8 *msg, unsigned int len); -SECStatus tls13_HandleServerHelloPart2(sslSocket *ss); +SECStatus tls13_HandleServerHelloPart2(sslSocket *ss, const PRUint8 *savedMsg, PRUint32 savedLength); SECStatus tls13_HandlePostHelloHandshakeMessage(sslSocket *ss, PRUint8 *b, PRUint32 length); SECStatus tls13_ConstructHelloRetryRequest(sslSocket *ss, diff --git a/lib/ssl/tls13ech.c b/lib/ssl/tls13ech.c index a42bda01a..7b6c2f0a4 100644 --- a/lib/ssl/tls13ech.c +++ b/lib/ssl/tls13ech.c @@ -14,14 +14,22 @@ #include "ssl3exthandle.h" #include "tls13ech.h" #include "tls13exthandle.h" +#include "tls13hashstate.h" #include "tls13hkdf.h" extern SECStatus -ssl3_UpdateExplicitHandshakeTranscript(sslSocket *ss, const unsigned char *b, - unsigned int l, sslBuffer *transcriptBuf); +ssl3_UpdateHandshakeHashesInt(sslSocket *ss, const unsigned char *b, + unsigned int l, sslBuffer *transcriptBuf); extern SECStatus ssl3_HandleClientHelloPreamble(sslSocket *ss, PRUint8 **b, PRUint32 *length, SECItem *sidBytes, SECItem *cookieBytes, SECItem *suites, SECItem *comps); +extern SECStatus +tls13_DeriveSecret(sslSocket *ss, PK11SymKey *key, + const char *label, + unsigned int labelLen, + const SSL3Hashes *hashes, + PK11SymKey **dest, + SSLHashType hash); void tls13_DestroyEchConfig(sslEchConfig *config) @@ -48,6 +56,19 @@ tls13_DestroyEchConfigs(PRCList *list) } } +void +tls13_DestroyEchXtnState(sslEchXtnState *state) +{ + if (!state) { + return; + } + SECITEM_FreeItem(&state->innerCh, PR_FALSE); + SECITEM_FreeItem(&state->senderPubKey, PR_FALSE); + SECITEM_FreeItem(&state->configId, PR_FALSE); + SECITEM_FreeItem(&state->retryConfigs, PR_FALSE); + PORT_ZFree(state, sizeof(*state)); +} + SECStatus tls13_CopyEchConfigs(PRCList *oConfigs, PRCList *configs) { @@ -86,6 +107,7 @@ tls13_CopyEchConfigs(PRCList *oConfigs, PRCList *configs) newConfig->contents.kdfId = config->contents.kdfId; newConfig->contents.aeadId = config->contents.aeadId; newConfig->contents.maxNameLen = config->contents.maxNameLen; + newConfig->version = config->version; PORT_Memcpy(newConfig->configId, config->configId, sizeof(newConfig->configId)); PR_APPEND_LINK(&newConfig->link, configs); } @@ -127,7 +149,7 @@ tls13_DigestEchConfig(const sslEchConfig *cfg, PRUint8 *digest, size_t maxDigest params.pInfo = CONST_CAST(CK_BYTE, hHkdfInfoEchConfigID); params.ulInfoLen = strlen(hHkdfInfoEchConfigID); derived = PK11_DeriveWithFlags(configKey, CKM_HKDF_DATA, - ¶msi, CKM_HKDF_DERIVE, CKA_DERIVE, 32, + ¶msi, CKM_HKDF_DERIVE, CKA_DERIVE, 8, CKF_SIGN | CKF_VERIFY); rv = PK11_ExtractKeyValue(derived); @@ -182,8 +204,11 @@ tls13_DecodeEchConfigContents(const sslReadBuffer *rawConfig, if (rv != SECSuccess) { goto loser; } - /* Make sure the public name doesn't contain any NULLs. - * TODO: Just store the SECItem instead. */ + + if (tmpBuf.len == 0) { + PORT_SetError(SSL_ERROR_RX_MALFORMED_ECH_CONFIG); + goto loser; + } for (tmpn = 0; tmpn < tmpBuf.len; tmpn++) { if (tmpBuf.buf[tmpn] == '\0') { PORT_SetError(SSL_ERROR_RX_MALFORMED_ECH_CONFIG); @@ -193,7 +218,6 @@ tls13_DecodeEchConfigContents(const sslReadBuffer *rawConfig, contents.publicName = PORT_ZAlloc(tmpBuf.len + 1); if (!contents.publicName) { - PORT_SetError(SSL_ERROR_RX_MALFORMED_ECH_CONFIG); goto loser; } PORT_Memcpy(contents.publicName, (PRUint8 *)tmpBuf.buf, tmpBuf.len); @@ -430,8 +454,8 @@ SSLExp_EncodeEchConfig(const char *publicName, const PRUint32 *hpkeSuites, PRUint8 tmpBuf[66]; // Large enough for an EC public key, currently only X25519. unsigned int tmpLen; - if (!publicName || PORT_Strlen(publicName) == 0 || !hpkeSuites || - hpkeSuiteCount == 0 || !pubKey || maxNameLen == 0 || !out || !outlen) { + if (!publicName || !hpkeSuites || hpkeSuiteCount == 0 || + !pubKey || maxNameLen == 0 || !out || !outlen) { PORT_SetError(SEC_ERROR_INVALID_ARGS); return SECFailure; } @@ -535,12 +559,18 @@ SSLExp_GetEchRetryConfigs(PRFileDesc *fd, SECItem *retryConfigs) PORT_SetError(SEC_ERROR_INVALID_ARGS); return SECFailure; } - if (!ss->xtnData.echRetryConfigsValid) { + + /* We don't distinguish between "handshake completed + * without retry configs", and "handshake not completed". + * An application should only call this after receiving a + * RETRY_WITH_ECH error code, which implies retry_configs. */ + if (!ss->xtnData.ech || !ss->xtnData.ech->retryConfigsValid) { PORT_SetError(SSL_ERROR_HANDSHAKE_NOT_COMPLETED); return SECFailure; } + /* May be empty. */ - rv = SECITEM_CopyItem(NULL, &out, &ss->xtnData.echRetryConfigs); + rv = SECITEM_CopyItem(NULL, &out, &ss->xtnData.ech->retryConfigs); if (rv == SECFailure) { return SECFailure; } @@ -571,8 +601,8 @@ SSLExp_RemoveEchConfigs(PRFileDesc *fd) } /* Also remove any retry_configs and handshake context. */ - if (ss->xtnData.echRetryConfigs.len) { - SECITEM_FreeItem(&ss->xtnData.echRetryConfigs, PR_FALSE); + if (ss->xtnData.ech && ss->xtnData.ech->retryConfigs.len) { + SECITEM_FreeItem(&ss->xtnData.ech->retryConfigs, PR_FALSE); } if (ss->ssl3.hs.echHpkeCtx) { @@ -704,14 +734,7 @@ tls13_ClientSetupEch(sslSocket *ss, sslClientHelloType type) HpkeContext *cx = NULL; SECKEYPublicKey *pkR = NULL; SECItem hpkeInfo = { siBuffer, NULL, 0 }; - PK11SymKey *hrrPsk = NULL; sslEchConfig *cfg = NULL; - const SECItem kEchHrrInfoItem = { siBuffer, - (unsigned char *)kHpkeInfoEchHrr, - strlen(kHpkeInfoEchHrr) }; - const SECItem kEchHrrPskLabelItem = { siBuffer, - (unsigned char *)kHpkeLabelHrrPsk, - strlen(kHpkeLabelHrrPsk) }; if (PR_CLIST_IS_EMPTY(&ss->echConfigs) || !ssl_ShouldSendSNIExtension(ss, ss->url) || @@ -739,20 +762,12 @@ tls13_ClientSetupEch(sslSocket *ss, sslClientHelloType type) cfg->contents.aeadId, NULL, NULL); break; case client_hello_retry: - PORT_Assert(ss->ssl3.hs.echHpkeCtx && ss->ssl3.hs.echPublicName); - rv = PK11_HPKE_ExportSecret(ss->ssl3.hs.echHpkeCtx, - &kEchHrrInfoItem, 32, &hrrPsk); - if (rv != SECSuccess) { - goto loser; + if (!ss->ssl3.hs.echHpkeCtx || !ss->ssl3.hs.echPublicName) { + FATAL_ERROR(ss, SEC_ERROR_LIBRARY_FAILURE, internal_error); + return SECFailure; } - - PK11_HPKE_DestroyContext(ss->ssl3.hs.echHpkeCtx, PR_TRUE); - PORT_Free((void *)ss->ssl3.hs.echPublicName); /* CONST */ - ss->ssl3.hs.echHpkeCtx = NULL; - ss->ssl3.hs.echPublicName = NULL; - cx = PK11_HPKE_NewContext(cfg->contents.kemId, cfg->contents.kdfId, - cfg->contents.aeadId, hrrPsk, &kEchHrrPskLabelItem); - break; + /* Nothing else to do. */ + return SECSuccess; default: PORT_Assert(0); goto loser; @@ -779,11 +794,9 @@ tls13_ClientSetupEch(sslSocket *ss, sslClientHelloType type) goto loser; } - if (!ss->ssl3.hs.helloRetry) { - rv = ssl3_GetNewRandom(ss->ssl3.hs.client_inner_random); - if (rv != SECSuccess) { - goto loser; /* code set */ - } + rv = ssl3_GetNewRandom(ss->ssl3.hs.client_inner_random); + if (rv != SECSuccess) { + goto loser; /* code set */ } /* If ECH is rejected, the application will use SSLChannelInfo @@ -794,22 +807,21 @@ tls13_ClientSetupEch(sslSocket *ss, sslClientHelloType type) } ss->ssl3.hs.echHpkeCtx = cx; - PK11_FreeSymKey(hrrPsk); SECKEY_DestroyPublicKey(pkR); SECITEM_FreeItem(&hpkeInfo, PR_FALSE); return SECSuccess; loser: PK11_HPKE_DestroyContext(cx, PR_TRUE); - PK11_FreeSymKey(hrrPsk); SECKEY_DestroyPublicKey(pkR); SECITEM_FreeItem(&hpkeInfo, PR_FALSE); + PORT_Assert(PORT_GetError() != 0); return SECFailure; } /* * enum { - * encrypted_client_hello(0xfe08), (65535) + * encrypted_client_hello(0xfe09), (65535) * } ExtensionType; * * struct { @@ -871,13 +883,22 @@ tls13_EncryptClientHello(sslSocket *ss, sslBuffer *outerAAD, sslBuffer *chInner) if (rv != SECSuccess) { goto loser; } - rv = sslBuffer_AppendVariable(chInner, cfg->configId, sizeof(cfg->configId), 1); - if (rv != SECSuccess) { - goto loser; - } - rv = sslBuffer_AppendVariable(chInner, hpkeEnc->data, hpkeEnc->len, 2); - if (rv != SECSuccess) { - goto loser; + + if (!ss->ssl3.hs.helloRetry) { + rv = sslBuffer_AppendVariable(chInner, cfg->configId, sizeof(cfg->configId), 1); + if (rv != SECSuccess) { + goto loser; + } + rv = sslBuffer_AppendVariable(chInner, hpkeEnc->data, hpkeEnc->len, 2); + if (rv != SECSuccess) { + goto loser; + } + } else { + /* one byte for empty configId, two for empty Enc. */ + rv = sslBuffer_AppendNumber(chInner, 0, 3); + if (rv != SECSuccess) { + goto loser; + } } rv = sslBuffer_AppendVariable(chInner, chCt->data, chCt->len, 2); if (rv != SECSuccess) { @@ -892,14 +913,14 @@ loser: } SECStatus -tls13_GetMatchingEchConfig(const sslSocket *ss, HpkeKdfId kdf, HpkeAeadId aead, - const SECItem *configId, sslEchConfig **cfg) +tls13_GetMatchingEchConfigs(const sslSocket *ss, HpkeKdfId kdf, HpkeAeadId aead, + const SECItem *configId, const sslEchConfig *cur, sslEchConfig **next) { - sslEchConfig *candidate; PRINT_BUF(50, (ss, "Server GetMatchingEchConfig with digest:", configId->data, configId->len)); - for (PRCList *cur_p = PR_LIST_HEAD(&ss->echConfigs); + /* If |cur|, resume the search at that node, else the list head. */ + for (PRCList *cur_p = cur ? ((PRCList *)cur)->next : PR_LIST_HEAD(&ss->echConfigs); cur_p != &ss->echConfigs; cur_p = PR_NEXT_LINK(cur_p)) { sslEchConfig *echConfig = (sslEchConfig *)cur_p; @@ -907,139 +928,17 @@ tls13_GetMatchingEchConfig(const sslSocket *ss, HpkeKdfId kdf, HpkeAeadId aead, PORT_Memcmp(echConfig->configId, configId->data, sizeof(echConfig->configId))) { continue; } - candidate = (sslEchConfig *)PR_LIST_HEAD(&ss->echConfigs); - if (candidate->contents.aeadId != aead || - candidate->contents.kdfId != kdf) { - continue; + if (echConfig->contents.aeadId == aead && + echConfig->contents.kdfId == kdf) { + *next = echConfig; + return SECSuccess; } - *cfg = candidate; - return SECSuccess; } - SSL_TRC(50, ("%d: TLS13[%d]: Server found no matching ECHConfig", - SSL_GETPID(), ss->fd)); - - *cfg = NULL; + *next = NULL; return SECSuccess; } -/* This is unfortunate in that it requires a second decryption of the cookie. - * This is largely copied from tls13hashstate.c as HRR handling is still in flux. - * TODO: Consolidate this code no later than -09. */ -/* struct { - * uint8 indicator = 0xff; // To disambiguate from tickets. - * uint16 cipherSuite; // Selected cipher suite. - * uint16 keyShare; // Requested key share group (0=none) - * opaque applicationToken<0..65535>; // Application token - * opaque echHrrPsk<0..255>; // Encrypted ClientHello HRR PSK - * opaque echConfigId<0..255>; // ECH config ID selected in CH1, to decrypt the CH2 ECH payload. - * opaque ch_hash[rest_of_buffer]; // H(ClientHello) - * } CookieInner; - */ -SECStatus -tls13_GetEchInfoFromCookie(sslSocket *ss, const TLSExtension *hrrCookie, PK11SymKey **echHrrPsk, SECItem *echConfigId) -{ - SECStatus rv; - PK11SymKey *hrrKey = NULL; - PRUint64 tmpn; - sslReadBuffer tmpReader = { 0 }; - PK11SlotInfo *slot = NULL; - unsigned char plaintext[1024]; - unsigned int plaintextLen = 0; - SECItem hrrPskItem = { siBuffer, NULL, 0 }; - SECItem hrrCookieData = { siBuffer, NULL, 0 }; - SECItem saveHrrCookieData = hrrCookieData; - SECItem previousEchConfigId = { siBuffer, NULL, 0 }; - - /* Copy the extension data so as to not consume it in the handler. - * The extension handler walks the pointer, so save a copy to free. */ - rv = SECITEM_CopyItem(NULL, &hrrCookieData, &hrrCookie->data); - if (rv != SECSuccess) { - goto loser; - } - saveHrrCookieData = hrrCookieData; - - rv = tls13_ServerHandleCookieXtn(ss, &ss->xtnData, &hrrCookieData); - if (rv != SECSuccess) { - goto loser; - } - - rv = ssl_SelfEncryptUnprotect(ss, ss->xtnData.cookie.data, ss->xtnData.cookie.len, - plaintext, &plaintextLen, sizeof(plaintext)); - if (rv != SECSuccess) { - goto loser; - } - - sslReader reader = SSL_READER(plaintext, plaintextLen); - - /* Should start with 0xff. */ - rv = sslRead_ReadNumber(&reader, 1, &tmpn); - if ((rv != SECSuccess) || (tmpn != 0xff)) { - rv = SECFailure; - goto loser; - } - rv = sslRead_ReadNumber(&reader, 2, &tmpn); - if (rv != SECSuccess) { - goto loser; - } - /* The named group, if any. */ - rv = sslRead_ReadNumber(&reader, 2, &tmpn); - if (rv != SECSuccess) { - goto loser; - } - /* Application token. */ - rv = sslRead_ReadNumber(&reader, 2, &tmpn); - if (rv != SECSuccess) { - goto loser; - } - rv = sslRead_Read(&reader, tmpn, &tmpReader); - if (rv != SECSuccess) { - goto loser; - } - - /* ECH Config ID */ - rv = sslRead_ReadVariable(&reader, 1, &tmpReader); - if (rv != SECSuccess) { - goto loser; - } - rv = SECITEM_MakeItem(NULL, &previousEchConfigId, - tmpReader.buf, tmpReader.len); - if (rv != SECSuccess) { - goto loser; - } - - /* ECH HRR key. */ - rv = sslRead_ReadVariable(&reader, 1, &tmpReader); - if (rv != SECSuccess) { - goto loser; - } - if (tmpReader.len) { - slot = PK11_GetInternalSlot(); - if (!slot) { - rv = SECFailure; - goto loser; - } - hrrPskItem.len = tmpReader.len; - hrrPskItem.data = CONST_CAST(PRUint8, tmpReader.buf); - hrrKey = PK11_ImportSymKey(slot, CKM_HKDF_KEY_GEN, PK11_OriginUnwrap, - CKA_DERIVE, &hrrPskItem, NULL); - PK11_FreeSlot(slot); - if (!hrrKey) { - rv = SECFailure; - goto loser; - } - } - *echConfigId = previousEchConfigId; - *echHrrPsk = hrrKey; - SECITEM_FreeItem(&saveHrrCookieData, PR_FALSE); - return SECSuccess; - -loser: - SECITEM_FreeItem(&previousEchConfigId, PR_FALSE); - SECITEM_FreeItem(&saveHrrCookieData, PR_FALSE); - return SECFailure; -} - /* Given a CH with extensions, copy from the start up to the extensions * into |writer| and return the extensions themselves in |extensions|. * If |explicitSid|, place this value into |writer| as the SID. Else, @@ -1110,19 +1009,55 @@ tls13_CopyChPreamble(sslReader *reader, const SECItem *explicitSid, sslBuffer *w return SECSuccess; } +/* + * struct { + * HpkeKdfId kdfId; // ClientECH.cipher_suite.kdf + * HpkeAeadId aeadId; // ClientECH.cipher_suite.aead + * opaque config_id<0..255>; // ClientECH.config_id + * opaque enc<1..2^16-1>; // ClientECH.enc + * opaque outer_hello<1..2^24-1>; + * } ClientHelloOuterAAD; + */ static SECStatus -tls13_MakeChOuterAAD(const SECItem *outer, sslBuffer *outerAAD) +tls13_MakeChOuterAAD(sslSocket *ss, const SECItem *outer, SECItem *outerAAD) { SECStatus rv; sslBuffer aad = SSL_BUFFER_EMPTY; - sslReadBuffer aadXtns; + sslReadBuffer aadXtns = { 0 }; sslReader chReader = SSL_READER(outer->data, outer->len); PRUint64 tmpn; - sslReadBuffer tmpvar; + sslReadBuffer tmpvar = { 0 }; unsigned int offset; - unsigned int preambleLen; + unsigned int savedOffset; + PORT_Assert(ss->xtnData.ech); + + rv = sslBuffer_AppendNumber(&aad, ss->xtnData.ech->kdfId, 2); + if (rv != SECSuccess) { + goto loser; + } + rv = sslBuffer_AppendNumber(&aad, ss->xtnData.ech->aeadId, 2); + if (rv != SECSuccess) { + goto loser; + } - rv = sslBuffer_Skip(&aad, 4, NULL); + if (!ss->ssl3.hs.helloRetry) { + rv = sslBuffer_AppendVariable(&aad, ss->xtnData.ech->configId.data, + ss->xtnData.ech->configId.len, 1); + if (rv != SECSuccess) { + goto loser; + } + rv = sslBuffer_AppendVariable(&aad, ss->xtnData.ech->senderPubKey.data, + ss->xtnData.ech->senderPubKey.len, 2); + } else { + /* 1B config_id length, 2B enc length. */ + rv = sslBuffer_AppendNumber(&aad, 0, 3); + } + if (rv != SECSuccess) { + goto loser; + } + + /* Skip 3 bytes for the CHOuter length. */ + rv = sslBuffer_Skip(&aad, 3, &savedOffset); if (rv != SECSuccess) { goto loser; } @@ -1132,9 +1067,7 @@ tls13_MakeChOuterAAD(const SECItem *outer, sslBuffer *outerAAD) if (rv != SECSuccess) { goto loser; } - sslReader xtnsReader = SSL_READER(aadXtns.buf, aadXtns.len); - preambleLen = SSL_BUFFER_LEN(&aad); /* Save room for extensions length. */ rv = sslBuffer_Skip(&aad, 2, &offset); @@ -1165,22 +1098,18 @@ tls13_MakeChOuterAAD(const SECItem *outer, sslBuffer *outerAAD) } } - rv = sslBuffer_InsertNumber(&aad, offset, SSL_BUFFER_LEN(&aad) - preambleLen - 2, 2); + rv = sslBuffer_InsertLength(&aad, offset, 2); if (rv != SECSuccess) { goto loser; } - /* Give it a message header. */ - rv = sslBuffer_InsertNumber(&aad, 0, ssl_hs_client_hello, 1); + rv = sslBuffer_InsertLength(&aad, savedOffset, 3); if (rv != SECSuccess) { goto loser; } - rv = sslBuffer_InsertLength(&aad, 1, 3); - if (rv != SECSuccess) { - goto loser; - } - *outerAAD = aad; + outerAAD->data = aad.buf; + outerAAD->len = aad.len; return SECSuccess; loser: @@ -1189,55 +1118,47 @@ loser: } SECStatus -tls13_OpenClientHelloInner(sslSocket *ss, const SECItem *outer, sslEchConfig *cfg, PK11SymKey *echHrrPsk, SECItem **chInner) +tls13_OpenClientHelloInner(sslSocket *ss, const SECItem *outer, const SECItem *outerAAD, sslEchConfig *cfg, SECItem **chInner) { SECStatus rv; - sslBuffer outerAAD = SSL_BUFFER_EMPTY; HpkeContext *cx = NULL; SECItem *decryptedChInner = NULL; SECItem hpkeInfo = { siBuffer, NULL, 0 }; - SECItem outerAADItem = { siBuffer, NULL, 0 }; - const SECItem kEchHrrPskLabelItem = { siBuffer, - (unsigned char *)kHpkeLabelHrrPsk, - strlen(kHpkeLabelHrrPsk) }; SSL_TRC(50, ("%d: TLS13[%d]: Server opening ECH Inner%s", SSL_GETPID(), ss->fd, ss->ssl3.hs.helloRetry ? " after HRR" : "")); - cx = PK11_HPKE_NewContext(cfg->contents.kemId, cfg->contents.kdfId, - cfg->contents.aeadId, echHrrPsk, - echHrrPsk ? &kEchHrrPskLabelItem : NULL); - if (!cx) { - goto loser; - } - - if (!SECITEM_AllocItem(NULL, &hpkeInfo, strlen(kHpkeInfoEch) + 1 + cfg->raw.len)) { - goto loser; - } - PORT_Memcpy(&hpkeInfo.data[0], kHpkeInfoEch, strlen(kHpkeInfoEch)); - PORT_Memset(&hpkeInfo.data[strlen(kHpkeInfoEch)], 0, 1); - PORT_Memcpy(&hpkeInfo.data[strlen(kHpkeInfoEch) + 1], cfg->raw.data, cfg->raw.len); + if (!ss->ssl3.hs.helloRetry) { + PORT_Assert(!ss->ssl3.hs.echHpkeCtx); + cx = PK11_HPKE_NewContext(cfg->contents.kemId, cfg->contents.kdfId, + cfg->contents.aeadId, NULL, NULL); + if (!cx) { + goto loser; + } - rv = PK11_HPKE_SetupR(cx, ss->echPubKey, ss->echPrivKey, - &ss->xtnData.echSenderPubKey, &hpkeInfo); - if (rv != SECSuccess) { - goto loser; /* code set */ - } + if (!SECITEM_AllocItem(NULL, &hpkeInfo, strlen(kHpkeInfoEch) + 1 + cfg->raw.len)) { + goto loser; + } + PORT_Memcpy(&hpkeInfo.data[0], kHpkeInfoEch, strlen(kHpkeInfoEch)); + PORT_Memset(&hpkeInfo.data[strlen(kHpkeInfoEch)], 0, 1); + PORT_Memcpy(&hpkeInfo.data[strlen(kHpkeInfoEch) + 1], cfg->raw.data, cfg->raw.len); - rv = tls13_MakeChOuterAAD(outer, &outerAAD); - if (rv != SECSuccess) { - goto loser; /* code set */ + rv = PK11_HPKE_SetupR(cx, ss->echPubKey, ss->echPrivKey, + &ss->xtnData.ech->senderPubKey, &hpkeInfo); + if (rv != SECSuccess) { + goto loser; /* code set */ + } + } else { + PORT_Assert(ss->ssl3.hs.echHpkeCtx); + cx = ss->ssl3.hs.echHpkeCtx; } - outerAADItem.data = outerAAD.buf; - outerAADItem.len = outerAAD.len; - #ifndef UNSAFE_FUZZER_MODE - rv = PK11_HPKE_Open(cx, &outerAADItem, &ss->xtnData.innerCh, &decryptedChInner); + rv = PK11_HPKE_Open(cx, outerAAD, &ss->xtnData.ech->innerCh, &decryptedChInner); if (rv != SECSuccess) { goto loser; /* code set */ } #else - rv = SECITEM_CopyItem(NULL, decryptedChInner, &ss->xtnData.innerCh); + rv = SECITEM_CopyItem(NULL, decryptedChInner, &ss->xtnData.ech->innerCh); if (rv != SECSuccess) { goto loser; } @@ -1248,14 +1169,15 @@ tls13_OpenClientHelloInner(sslSocket *ss, const SECItem *outer, sslEchConfig *cf ss->ssl3.hs.echHpkeCtx = cx; *chInner = decryptedChInner; SECITEM_FreeItem(&hpkeInfo, PR_FALSE); - sslBuffer_Clear(&outerAAD); return SECSuccess; loser: SECITEM_FreeItem(decryptedChInner, PR_TRUE); - PK11_HPKE_DestroyContext(cx, PR_TRUE); SECITEM_FreeItem(&hpkeInfo, PR_FALSE); - sslBuffer_Clear(&outerAAD); + if (cx != ss->ssl3.hs.echHpkeCtx) { + /* Don't double-free if it's already global. */ + PK11_HPKE_DestroyContext(cx, PR_TRUE); + } return SECFailure; } @@ -1289,7 +1211,7 @@ tls13_ConstructInnerExtensionsFromOuter(sslSocket *ss, sslBuffer *chOuterXtnsBuf /* When offering the "encrypted_client_hello" extension in its * ClientHelloOuter, the client MUST also offer an empty * "encrypted_client_hello" extension in its ClientHelloInner. */ - rv = sslBuffer_AppendNumber(chInnerXtns, ssl_tls13_encrypted_client_hello_xtn, 2); + rv = sslBuffer_AppendNumber(chInnerXtns, ssl_tls13_ech_is_inner_xtn, 2); if (rv != SECSuccess) { goto loser; } @@ -1508,9 +1430,11 @@ tls13_ConstructClientHelloWithEch(sslSocket *ss, const sslSessionID *sid, PRBool sslBuffer encodedChInner = SSL_BUFFER_EMPTY; sslBuffer chInnerXtns = SSL_BUFFER_EMPTY; sslBuffer pskXtn = SSL_BUFFER_EMPTY; - sslBuffer outerAAD = SSL_BUFFER_EMPTY; + sslBuffer aad = SSL_BUFFER_EMPTY; unsigned int encodedChLen; unsigned int preambleLen; + const SECItem *hpkeEnc = NULL; + unsigned int savedOffset; SSL_TRC(50, ("%d: TLS13[%d]: Constructing ECH inner", SSL_GETPID())); /* Create the full (uncompressed) inner extensions and steal any PSK extension. @@ -1551,8 +1475,8 @@ tls13_ConstructClientHelloWithEch(sslSocket *ss, const sslSessionID *sid, PRBool goto loser; } - rv = ssl3_UpdateExplicitHandshakeTranscript(ss, chInner.buf, chInner.len, - &ss->ssl3.hs.echInnerMessages); + rv = ssl3_UpdateHandshakeHashesInt(ss, chInner.buf, chInner.len, + &ss->ssl3.hs.echInnerMessages); if (rv != SECSuccess) { goto loser; /* code set */ } @@ -1566,7 +1490,6 @@ tls13_ConstructClientHelloWithEch(sslSocket *ss, const sslSessionID *sid, PRBool goto loser; } - /* TODO: Pad CHInner */ rv = tls13_EncodeClientHelloInner(ss, &chInner, &chInnerXtns, &encodedChInner); if (rv != SECSuccess) { goto loser; @@ -1575,29 +1498,67 @@ tls13_ConstructClientHelloWithEch(sslSocket *ss, const sslSessionID *sid, PRBool /* Pad the outer prior to appending ECH (for the AAD). * Encoded extension size is (echCipherSuite + enc + configId + payload + tag). * Post-encryption, we'll assert that this was correct. */ - encodedChLen = 4 + 33 + 34 + 2 + encodedChInner.len + 16; + encodedChLen = 4 + 1 + 2 + 2 + encodedChInner.len + 16; + if (!ss->ssl3.hs.helloRetry) { + encodedChLen += 8 + 32; /* configId || enc */ + } rv = ssl_InsertPaddingExtension(ss, chOuter->len + encodedChLen, chOuterXtnsBuf); if (rv != SECSuccess) { goto loser; } - /* Make the ClientHelloOuterAAD value, which is complete - * chOuter minus encrypted_client_hello xtn. */ - rv = sslBuffer_Append(&outerAAD, chOuter->buf, chOuter->len); + PORT_Assert(!PR_CLIST_IS_EMPTY(&ss->echConfigs)); + sslEchConfig *cfg = (sslEchConfig *)PR_LIST_HEAD(&ss->echConfigs); + rv = sslBuffer_AppendNumber(&aad, cfg->contents.kdfId, 2); + if (rv != SECSuccess) { + goto loser; + } + rv = sslBuffer_AppendNumber(&aad, cfg->contents.aeadId, 2); if (rv != SECSuccess) { goto loser; } - rv = sslBuffer_AppendBufferVariable(&outerAAD, chOuterXtnsBuf, 2); + + if (!ss->ssl3.hs.helloRetry) { + rv = sslBuffer_AppendVariable(&aad, cfg->configId, sizeof(cfg->configId), 1); + if (rv != SECSuccess) { + goto loser; + } + hpkeEnc = PK11_HPKE_GetEncapPubKey(ss->ssl3.hs.echHpkeCtx); + if (!hpkeEnc) { + FATAL_ERROR(ss, SEC_ERROR_LIBRARY_FAILURE, internal_error); + goto loser; + } + rv = sslBuffer_AppendVariable(&aad, hpkeEnc->data, hpkeEnc->len, 2); + } else { + /* 1B config_id length, 2B enc length. */ + rv = sslBuffer_AppendNumber(&aad, 0, 3); + } if (rv != SECSuccess) { goto loser; } - rv = sslBuffer_InsertLength(&outerAAD, 1, 3); + + rv = sslBuffer_Skip(&aad, 3, &savedOffset); + if (rv != SECSuccess) { + goto loser; + } + + /* Skip the handshake header. */ + PORT_Assert(chOuter->len > 4); + rv = sslBuffer_Append(&aad, &chOuter->buf[4], chOuter->len - 4); + if (rv != SECSuccess) { + goto loser; + } + rv = sslBuffer_AppendBufferVariable(&aad, chOuterXtnsBuf, 2); + if (rv != SECSuccess) { + goto loser; + } + rv = sslBuffer_InsertLength(&aad, savedOffset, 3); if (rv != SECSuccess) { goto loser; } /* Insert the encrypted_client_hello xtn and coalesce. */ - rv = tls13_EncryptClientHello(ss, &outerAAD, &encodedChInner); + rv = tls13_EncryptClientHello(ss, &aad, &encodedChInner); if (rv != SECSuccess) { goto loser; } @@ -1618,64 +1579,123 @@ tls13_ConstructClientHelloWithEch(sslSocket *ss, const sslSessionID *sid, PRBool if (rv != SECSuccess) { goto loser; } + sslBuffer_Clear(&chInner); + sslBuffer_Clear(&encodedChInner); + sslBuffer_Clear(&chInnerXtns); + sslBuffer_Clear(&pskXtn); + sslBuffer_Clear(&aad); + return SECSuccess; loser: sslBuffer_Clear(&chInner); sslBuffer_Clear(&encodedChInner); sslBuffer_Clear(&chInnerXtns); sslBuffer_Clear(&pskXtn); - sslBuffer_Clear(&outerAAD); - return rv; + sslBuffer_Clear(&aad); + PORT_Assert(PORT_GetError() != 0); + return SECFailure; } +/* Compute the ECH signal using the transcript (up to, excluding) Server Hello. + * We'll append an artificial SH (ServerHelloECHConf). The server sources + * this transcript prefix from ss->ssl3.hs.messages, as it never uses + * ss->ssl3.hs.echInnerMessages. The client uses the inner transcript, echInnerMessages. */ static SECStatus -tls13_ComputeEchSignal(sslSocket *ss, PRUint8 *out) +tls13_ComputeEchSignal(sslSocket *ss, const PRUint8 *sh, unsigned int shLen, PRUint8 *out) { SECStatus rv; - PRUint8 derived[64]; - SECItem randItem = { siBuffer, - ss->sec.isServer ? ss->ssl3.hs.client_random : ss->ssl3.hs.client_inner_random, - SSL3_RANDOM_LENGTH }; - SSLHashType hashAlg = tls13_GetHash(ss); - PK11SymKey *extracted = NULL; - PK11SymKey *randKey = NULL; - PK11SlotInfo *slot = PK11_GetInternalSlot(); - if (!slot) { + PK11SymKey *confirmationKey = NULL; + sslBuffer confMsgs = SSL_BUFFER_EMPTY; + sslBuffer *chSource = ss->sec.isServer ? &ss->ssl3.hs.messages : &ss->ssl3.hs.echInnerMessages; + SSL3Hashes hashes; + SECItem *confirmationBytes; + unsigned int offset = sizeof(SSL3ProtocolVersion) + + SSL3_RANDOM_LENGTH - TLS13_ECH_SIGNAL_LEN; + PORT_Assert(sh && shLen > offset); + PORT_Assert(TLS13_ECH_SIGNAL_LEN <= SSL3_RANDOM_LENGTH); + + rv = sslBuffer_AppendBuffer(&confMsgs, chSource); + if (rv != SECSuccess) { goto loser; } - randKey = PK11_ImportDataKey(slot, CKM_HKDF_DATA, PK11_OriginUnwrap, - CKA_DERIVE, &randItem, NULL); - if (!randKey) { + /* Re-create the message header. */ + rv = sslBuffer_AppendNumber(&confMsgs, ssl_hs_server_hello, 1); + if (rv != SECSuccess) { goto loser; } - rv = tls13_HkdfExtract(NULL, randKey, hashAlg, &extracted); + rv = sslBuffer_AppendNumber(&confMsgs, shLen, 3); if (rv != SECSuccess) { goto loser; } - rv = tls13_HkdfExpandLabelRaw(extracted, hashAlg, ss->ssl3.hs.server_random, 24, - kHkdfInfoEchConfirm, strlen(kHkdfInfoEchConfirm), - ss->protocolVariant, derived, TLS13_ECH_SIGNAL_LEN); + /* Copy the version and 24B of server_random. */ + rv = sslBuffer_Append(&confMsgs, sh, offset); if (rv != SECSuccess) { goto loser; } - PORT_Memcpy(out, derived, TLS13_ECH_SIGNAL_LEN); + /* Zero the signal placeholder. */ + rv = sslBuffer_AppendNumber(&confMsgs, 0, TLS13_ECH_SIGNAL_LEN); + if (rv != SECSuccess) { + goto loser; + } + offset += TLS13_ECH_SIGNAL_LEN; + + /* Use the remainder of SH. */ + rv = sslBuffer_Append(&confMsgs, &sh[offset], shLen - offset); + if (rv != SECSuccess) { + goto loser; + } + + rv = tls13_ComputeHash(ss, &hashes, confMsgs.buf, confMsgs.len, + tls13_GetHash(ss)); + if (rv != SECSuccess) { + goto loser; + } + + /* accept_confirmation = + * Derive-Secret(Handshake Secret, + * "ech accept confirmation", + * ClientHelloInner...ServerHelloECHConf) + */ + rv = tls13_DeriveSecret(ss, ss->ssl3.hs.currentSecret, + kHkdfInfoEchConfirm, strlen(kHkdfInfoEchConfirm), + &hashes, &confirmationKey, tls13_GetHash(ss)); + if (rv != SECSuccess) { + return SECFailure; + } + + rv = PK11_ExtractKeyValue(confirmationKey); + if (rv != SECSuccess) { + goto loser; + } + confirmationBytes = PK11_GetKeyData(confirmationKey); + if (!confirmationBytes) { + rv = SECFailure; + PORT_SetError(SSL_ERROR_ECH_FAILED); + goto loser; + } + if (confirmationBytes->len < TLS13_ECH_SIGNAL_LEN) { + FATAL_ERROR(ss, SEC_ERROR_LIBRARY_FAILURE, internal_error); + goto loser; + } SSL_TRC(50, ("%d: TLS13[%d]: %s computed ECH signal", SSL_GETPID(), ss->fd, SSL_ROLE(ss))); PRINT_BUF(50, (ss, "", out, TLS13_ECH_SIGNAL_LEN)); - PK11_FreeSymKey(extracted); - PK11_FreeSymKey(randKey); - PK11_FreeSlot(slot); + + PORT_Memcpy(out, confirmationBytes->data, TLS13_ECH_SIGNAL_LEN); + PK11_FreeSymKey(confirmationKey); + sslBuffer_Clear(&confMsgs); + sslBuffer_Clear(&ss->ssl3.hs.messages); + sslBuffer_Clear(&ss->ssl3.hs.echInnerMessages); return SECSuccess; loser: - PK11_FreeSymKey(extracted); - PK11_FreeSymKey(randKey); - if (slot) { - PK11_FreeSlot(slot); - } + PK11_FreeSymKey(confirmationKey); + sslBuffer_Clear(&confMsgs); + sslBuffer_Clear(&ss->ssl3.hs.messages); + sslBuffer_Clear(&ss->ssl3.hs.echInnerMessages); return SECFailure; } @@ -1695,6 +1715,8 @@ tls13_MaybeGreaseEch(sslSocket *ss, unsigned int preambleLen, sslBuffer *buf) SECItem *rawData; CK_HKDF_PARAMS params; SECItem paramsi; + /* 1B aead determinant (don't send), 8B config_id, 32B enc, payload */ + const int kNonPayloadLen = 41; if (!ss->opt.enableTls13GreaseEch || ss->ssl3.hs.echHpkeCtx) { return SECSuccess; @@ -1705,6 +1727,13 @@ tls13_MaybeGreaseEch(sslSocket *ss, unsigned int preambleLen, sslBuffer *buf) return SECSuccess; } + /* In draft-09, CH2 sends exactly the same GREASE ECH extension. */ + if (ss->ssl3.hs.helloRetry) { + return ssl3_EmplaceExtension(ss, buf, ssl_tls13_encrypted_client_hello_xtn, + ss->ssl3.hs.greaseEchBuf.buf, + ss->ssl3.hs.greaseEchBuf.len, PR_TRUE); + } + /* Compress the extensions for payload length. */ rv = tls13_ConstructInnerExtensionsFromOuter(ss, buf, &chInnerXtns, NULL, PR_TRUE); @@ -1734,7 +1763,7 @@ tls13_MaybeGreaseEch(sslSocket *ss, unsigned int preambleLen, sslBuffer *buf) paramsi.len = sizeof(params); derivedData = PK11_DeriveWithFlags(hmacPrk, CKM_HKDF_DATA, ¶msi, CKM_HKDF_DATA, - CKA_DERIVE, 65 + payloadLen, + CKA_DERIVE, kNonPayloadLen + payloadLen, CKF_VERIFY); if (!derivedData) { goto loser; @@ -1745,12 +1774,11 @@ tls13_MaybeGreaseEch(sslSocket *ss, unsigned int preambleLen, sslBuffer *buf) goto loser; } - /* 1B aead determinant (don't send), 32B config_id, 32B enc, payload */ rawData = PK11_GetKeyData(derivedData); if (!rawData) { goto loser; } - PORT_Assert(rawData->len == 65 + payloadLen); + PORT_Assert(rawData->len == kNonPayloadLen + payloadLen); /* struct { HpkeKdfId kdf_id; @@ -1773,31 +1801,34 @@ tls13_MaybeGreaseEch(sslSocket *ss, unsigned int preambleLen, sslBuffer *buf) goto loser; } - rv = sslBuffer_AppendVariable(&greaseBuf, &rawData->data[1], 32, 1); + /* config_id, 8B */ + rv = sslBuffer_AppendVariable(&greaseBuf, &rawData->data[1], 8, 1); if (rv != SECSuccess) { goto loser; } /* enc len is fixed 32B for X25519. */ - rv = sslBuffer_AppendVariable(&greaseBuf, &rawData->data[33], 32, 2); + rv = sslBuffer_AppendVariable(&greaseBuf, &rawData->data[9], 32, 2); if (rv != SECSuccess) { goto loser; } - rv = sslBuffer_AppendVariable(&greaseBuf, &rawData->data[65], payloadLen, 2); + rv = sslBuffer_AppendVariable(&greaseBuf, &rawData->data[kNonPayloadLen], payloadLen, 2); if (rv != SECSuccess) { goto loser; } /* Mark ECH as advertised so that we can validate any response. - * We'll use echHpkeCtx to determine if we sent real or GREASE ECH. - * TODO: Maybe a broader need to similarly track GREASED extensions? */ + * We'll use echHpkeCtx to determine if we sent real or GREASE ECH. */ rv = ssl3_EmplaceExtension(ss, buf, ssl_tls13_encrypted_client_hello_xtn, greaseBuf.buf, greaseBuf.len, PR_TRUE); if (rv != SECSuccess) { goto loser; } - sslBuffer_Clear(&greaseBuf); + + /* Stash the GREASE ECH extension - in the case of HRR, CH2 must echo it. */ + ss->ssl3.hs.greaseEchBuf = greaseBuf; + sslBuffer_Clear(&chInnerXtns); PK11_FreeSymKey(hmacPrk); PK11_FreeSymKey(derivedData); @@ -1805,7 +1836,6 @@ tls13_MaybeGreaseEch(sslSocket *ss, unsigned int preambleLen, sslBuffer *buf) return SECSuccess; loser: - sslBuffer_Clear(&greaseBuf); sslBuffer_Clear(&chInnerXtns); PK11_FreeSymKey(hmacPrk); PK11_FreeSymKey(derivedData); @@ -1861,15 +1891,11 @@ tls13_MaybeHandleEch(sslSocket *ss, const PRUint8 *msg, PRUint32 msgLen, SECItem /* Since in Outer we explicitly call the ECH handler, do the same on Inner. * Extensions are already parsed in tls13_MaybeAcceptEch. */ - echExtension = ssl3_FindExtension(ss, ssl_tls13_encrypted_client_hello_xtn); + echExtension = ssl3_FindExtension(ss, ssl_tls13_ech_is_inner_xtn); if (!echExtension) { - FATAL_ERROR(ss, SSL_ERROR_MISSING_ECH_EXTENSION, decode_error); + FATAL_ERROR(ss, SSL_ERROR_MISSING_ECH_EXTENSION, illegal_parameter); goto loser; } - rv = tls13_ServerHandleEchXtn(ss, &ss->xtnData, &echExtension->data); - if (rv != SECSuccess) { - goto loser; /* code set, alert sent. */ - } versionExtension = ssl3_FindExtension(ss, ssl_tls13_supported_versions_xtn); if (!versionExtension) { @@ -1895,11 +1921,12 @@ tls13_MaybeHandleEch(sslSocket *ss, const PRUint8 *msg, PRUint32 msgLen, SECItem loser: SECITEM_FreeItem(tmpEchInner, PR_TRUE); + PORT_Assert(PORT_GetError() != 0); return SECFailure; } SECStatus -tls13_MaybeHandleEchSignal(sslSocket *ss) +tls13_MaybeHandleEchSignal(sslSocket *ss, const PRUint8 *sh, PRUint32 shLen) { SECStatus rv; PRUint8 computed[TLS13_ECH_SIGNAL_LEN]; @@ -1907,29 +1934,41 @@ tls13_MaybeHandleEchSignal(sslSocket *ss) PORT_Assert(!ss->sec.isServer); /* If !echHpkeCtx, we either didn't advertise or sent GREASE ECH. */ - if (ss->ssl3.hs.echHpkeCtx) { - PORT_Assert(ssl3_ExtensionAdvertised(ss, ssl_tls13_encrypted_client_hello_xtn)); - rv = tls13_ComputeEchSignal(ss, computed); - if (rv != SECSuccess) { + if (!ss->ssl3.hs.echHpkeCtx) { + ss->ssl3.hs.preliminaryInfo |= ssl_preinfo_ech; + return SECSuccess; + } + + PORT_Assert(ssl3_ExtensionAdvertised(ss, ssl_tls13_encrypted_client_hello_xtn)); + rv = tls13_ComputeEchSignal(ss, sh, shLen, computed); + if (rv != SECSuccess) { + return SECFailure; + } + + ss->ssl3.hs.echAccepted = !PORT_Memcmp(computed, signal, TLS13_ECH_SIGNAL_LEN); + ss->ssl3.hs.preliminaryInfo |= ssl_preinfo_ech; + if (ss->ssl3.hs.echAccepted) { + if (ss->version < SSL_LIBRARY_VERSION_TLS_1_3) { + FATAL_ERROR(ss, SSL_ERROR_RX_MALFORMED_SERVER_HELLO, illegal_parameter); return SECFailure; } - - ss->ssl3.hs.echAccepted = !PORT_Memcmp(computed, signal, TLS13_ECH_SIGNAL_LEN); - if (ss->ssl3.hs.echAccepted) { - if (ss->version < SSL_LIBRARY_VERSION_TLS_1_3) { - FATAL_ERROR(ss, SSL_ERROR_RX_MALFORMED_SERVER_HELLO, illegal_parameter); + if (ss->ssl3.hs.helloRetry && ss->sec.isServer) { + /* Enc and ConfigId are stored in the cookie and must not + * be included in CH2.ClientECH. */ + if (ss->xtnData.ech->senderPubKey.len || ss->xtnData.ech->configId.len) { + ssl3_ExtSendAlert(ss, alert_fatal, illegal_parameter); + PORT_SetError(SSL_ERROR_BAD_2ND_CLIENT_HELLO); return SECFailure; } - ss->xtnData.negotiated[ss->xtnData.numNegotiated++] = ssl_tls13_encrypted_client_hello_xtn; - PORT_Memcpy(ss->ssl3.hs.client_random, ss->ssl3.hs.client_inner_random, SSL3_RANDOM_LENGTH); } - /* If rejected, leave echHpkeCtx and echPublicName for rejection paths. */ - ssl3_CoalesceEchHandshakeHashes(ss); - SSL_TRC(50, ("%d: TLS13[%d]: ECH %s accepted by server", - SSL_GETPID(), ss->fd, ss->ssl3.hs.echAccepted ? "is" : "is not")); - } - ss->ssl3.hs.preliminaryInfo |= ssl_preinfo_ech; + ss->xtnData.negotiated[ss->xtnData.numNegotiated++] = ssl_tls13_encrypted_client_hello_xtn; + PORT_Memcpy(ss->ssl3.hs.client_random, ss->ssl3.hs.client_inner_random, SSL3_RANDOM_LENGTH); + } + /* If rejected, leave echHpkeCtx and echPublicName for rejection paths. */ + ssl3_CoalesceEchHandshakeHashes(ss); + SSL_TRC(50, ("%d: TLS13[%d]: ECH %s accepted by server", + SSL_GETPID(), ss->fd, ss->ssl3.hs.echAccepted ? "is" : "is not")); return SECSuccess; } @@ -2092,75 +2131,111 @@ tls13_MaybeAcceptEch(sslSocket *ss, const SECItem *sidBytes, const PRUint8 *chOu SECStatus rv; SECItem outer = { siBuffer, CONST_CAST(PRUint8, chOuter), chOuterLen }; SECItem *decryptedChInner = NULL; - PK11SymKey *echHrrPsk = NULL; SECItem hrrCh1ConfigId = { siBuffer, NULL, 0 }; - HpkeKdfId kdf; - HpkeAeadId aead; + SECItem outerAAD = { siBuffer, NULL, 0 }; + SECItem cookieData = { siBuffer, NULL, 0 }; + HpkeContext *ch1EchHpkeCtx = NULL; + HpkeKdfId echKdfId; + HpkeAeadId echAeadId; sslEchConfig *candidate = NULL; /* non-owning */ TLSExtension *hrrXtn; - SECItem *configId = ss->ssl3.hs.helloRetry ? &hrrCh1ConfigId : &ss->xtnData.echConfigId; - if (!ss->xtnData.innerCh.len) { + + if (!ss->xtnData.ech) { return SECSuccess; } - PORT_Assert(ss->xtnData.echSenderPubKey.data); - PORT_Assert(ss->xtnData.echConfigId.data); - PORT_Assert(ss->xtnData.echCipherSuite); + PORT_Assert(ss->xtnData.ech->innerCh.data); if (ss->ssl3.hs.helloRetry) { + PORT_Assert(!ss->ssl3.hs.echHpkeCtx); hrrXtn = ssl3_FindExtension(ss, ssl_tls13_cookie_xtn); if (!hrrXtn) { /* If the client doesn't echo cookie, we can't decrypt. */ return SECSuccess; } - rv = tls13_GetEchInfoFromCookie(ss, hrrXtn, &echHrrPsk, &hrrCh1ConfigId); + PORT_Assert(!ss->xtnData.ech->configId.data); + PORT_Assert(!ss->ssl3.hs.echHpkeCtx); + + PRUint8 *tmp = hrrXtn->data.data; + PRUint32 len = hrrXtn->data.len; + rv = ssl3_ExtConsumeHandshakeVariable(ss, &cookieData, 2, + &tmp, &len); + if (rv != SECSuccess) { + return SECFailure; + } + + /* Extract ECH info without restoring hash state. If there's + * something wrong with the cookie, continue without ECH + * and let HRR code handle the problem. */ + rv = tls13_HandleHrrCookie(ss, cookieData.data, cookieData.len, + NULL, NULL, NULL, &echKdfId, &echAeadId, + &hrrCh1ConfigId, &ch1EchHpkeCtx, PR_FALSE); if (rv != SECSuccess) { - /* If we failed due to an issue with the cookie, continue without - * ECH and let the HRR code handle the problem. */ - goto exit_success; + return SECSuccess; } - /* No CH1 config_id means ECH wasn't advertised in CH1. - * No CH1 HRR PSK means that ECH was not accepted in CH1, and the - * HRR was generated off CH1Outer. */ - if (hrrCh1ConfigId.len == 0) { + ss->xtnData.ech->configId = hrrCh1ConfigId; + ss->ssl3.hs.echHpkeCtx = ch1EchHpkeCtx; + + if (echKdfId != ss->xtnData.ech->kdfId || + echAeadId != ss->xtnData.ech->aeadId) { FATAL_ERROR(ss, SSL_ERROR_BAD_2ND_CLIENT_HELLO, illegal_parameter); - goto loser; + return SECFailure; } - if (!echHrrPsk) { - goto exit_success; + + if (!ss->ssl3.hs.echHpkeCtx) { + return SECSuccess; } } - kdf = (HpkeKdfId)(ss->xtnData.echCipherSuite & 0xFFFF); - aead = (HpkeAeadId)(((ss->xtnData.echCipherSuite) >> 16) & 0xFFFF); - rv = tls13_GetMatchingEchConfig(ss, kdf, aead, configId, &candidate); + + /* Cookie data was good, proceed with ECH. */ + PORT_Assert(ss->xtnData.ech->configId.data); + rv = tls13_GetMatchingEchConfigs(ss, ss->xtnData.ech->kdfId, ss->xtnData.ech->aeadId, + &ss->xtnData.ech->configId, candidate, &candidate); if (rv != SECSuccess) { - goto loser; + FATAL_ERROR(ss, SEC_ERROR_LIBRARY_FAILURE, internal_error); + return SECFailure; } - if (!candidate || candidate->contents.kdfId != kdf || - candidate->contents.aeadId != aead) { - /* Send retry_configs if we have any. - * This does *not* count as negotiating ECH. */ - rv = ssl3_RegisterExtensionSender(ss, &ss->xtnData, - ssl_tls13_encrypted_client_hello_xtn, - tls13_ServerSendEchXtn); - goto exit_success; + + if (candidate) { + rv = tls13_MakeChOuterAAD(ss, &outer, &outerAAD); + if (rv != SECSuccess) { + return SECFailure; + } } - rv = tls13_OpenClientHelloInner(ss, &outer, candidate, echHrrPsk, &decryptedChInner); - if (rv != SECSuccess) { + while (candidate) { + rv = tls13_OpenClientHelloInner(ss, &outer, &outerAAD, candidate, &decryptedChInner); + if (rv != SECSuccess) { + /* Get the next matching config */ + rv = tls13_GetMatchingEchConfigs(ss, ss->xtnData.ech->kdfId, ss->xtnData.ech->aeadId, + &ss->xtnData.ech->configId, candidate, &candidate); + if (rv != SECSuccess) { + FATAL_ERROR(ss, SEC_ERROR_LIBRARY_FAILURE, internal_error); + SECITEM_FreeItem(&outerAAD, PR_FALSE); + return SECFailure; + } + continue; + } + break; + } + SECITEM_FreeItem(&outerAAD, PR_FALSE); + + if (rv != SECSuccess || !decryptedChInner) { if (ss->ssl3.hs.helloRetry) { - FATAL_ERROR(ss, SSL_ERROR_RX_MALFORMED_ESNI_EXTENSION, decrypt_error); - goto loser; + FATAL_ERROR(ss, SSL_ERROR_RX_MALFORMED_ECH_EXTENSION, decrypt_error); + return SECFailure; } else { - rv = ssl3_RegisterExtensionSender(ss, &ss->xtnData, - ssl_tls13_encrypted_client_hello_xtn, - tls13_ServerSendEchXtn); - goto exit_success; + /* Send retry_configs (if we have any) when we fail to decrypt or + * found no candidates. This does *not* count as negotiating ECH. */ + return ssl3_RegisterExtensionSender(ss, &ss->xtnData, + ssl_tls13_encrypted_client_hello_xtn, + tls13_ServerSendEchXtn); } } + SSL_TRC(20, ("%d: TLS13[%d]: Successfully opened ECH inner CH", SSL_GETPID(), ss->fd)); ss->ssl3.hs.echAccepted = PR_TRUE; @@ -2172,31 +2247,33 @@ tls13_MaybeAcceptEch(sslSocket *ss, const SECItem *sidBytes, const PRUint8 *chOu rv = tls13_UnencodeChInner(ss, sidBytes, &decryptedChInner); if (rv != SECSuccess) { SECITEM_FreeItem(decryptedChInner, PR_TRUE); - goto loser; /* code set */ + return SECFailure; /* code set */ } *chInner = decryptedChInner; - -exit_success: - PK11_FreeSymKey(echHrrPsk); - SECITEM_FreeItem(&hrrCh1ConfigId, PR_FALSE); return SECSuccess; - -loser: - PK11_FreeSymKey(echHrrPsk); - SECITEM_FreeItem(&hrrCh1ConfigId, PR_FALSE); - return SECFailure; } SECStatus -tls13_WriteServerEchSignal(sslSocket *ss) +tls13_WriteServerEchSignal(sslSocket *ss, PRUint8 *sh, unsigned int shLen) { SECStatus rv; PRUint8 signal[TLS13_ECH_SIGNAL_LEN]; - rv = tls13_ComputeEchSignal(ss, signal); + PRUint8 *msg_random = &sh[sizeof(SSL3ProtocolVersion)]; + + PORT_Assert(shLen > sizeof(SSL3ProtocolVersion) + SSL3_RANDOM_LENGTH); + PORT_Assert(ss->version >= SSL_LIBRARY_VERSION_TLS_1_3); + + rv = tls13_ComputeEchSignal(ss, sh, shLen, signal); if (rv != SECSuccess) { return SECFailure; } - PRUint8 *dest = &ss->ssl3.hs.server_random[SSL3_RANDOM_LENGTH - TLS13_ECH_SIGNAL_LEN]; + PRUint8 *dest = &msg_random[SSL3_RANDOM_LENGTH - TLS13_ECH_SIGNAL_LEN]; + PORT_Memcpy(dest, signal, TLS13_ECH_SIGNAL_LEN); + + /* Keep the socket copy consistent. */ + PORT_Assert(0 == memcmp(msg_random, &ss->ssl3.hs.server_random, SSL3_RANDOM_LENGTH - TLS13_ECH_SIGNAL_LEN)); + dest = &ss->ssl3.hs.server_random[SSL3_RANDOM_LENGTH - TLS13_ECH_SIGNAL_LEN]; PORT_Memcpy(dest, signal, TLS13_ECH_SIGNAL_LEN); + return SECSuccess; } diff --git a/lib/ssl/tls13ech.h b/lib/ssl/tls13ech.h index 5c53b3f8a..a39a0295c 100644 --- a/lib/ssl/tls13ech.h +++ b/lib/ssl/tls13ech.h @@ -11,7 +11,7 @@ #include "pk11hpke.h" -/* draft-08, shared-mode only. +/* draft-09, supporting shared-mode and split-mode as a backend server only. * Notes on the implementation status: * - Padding (https://tools.ietf.org/html/draft-ietf-tls-esni-08#section-6.2), * is not implemented (see bug 1677181). @@ -21,12 +21,10 @@ * - Some of the buffering (construction/compression/decompression) could likely * be optimized, but the spec is still evolving so that work is deferred. */ -#define TLS13_ECH_VERSION 0xfe08 +#define TLS13_ECH_VERSION 0xfe09 #define TLS13_ECH_SIGNAL_LEN 8 static const char kHpkeInfoEch[] = "tls ech"; -static const char kHpkeInfoEchHrr[] = "tls ech hrr key"; -static const char kHpkeLabelHrrPsk[] = "hrr key"; static const char hHkdfInfoEchConfigID[] = "tls ech config id"; static const char kHkdfInfoEchConfirm[] = "ech accept confirmation"; @@ -45,11 +43,23 @@ struct sslEchConfigContentsStr { struct sslEchConfigStr { PRCList link; SECItem raw; - PRUint8 configId[32]; + PRUint8 configId[8]; PRUint16 version; sslEchConfigContents contents; }; +struct sslEchXtnStateStr { + SECItem innerCh; /* Server: ClientECH.payload */ + SECItem senderPubKey; /* Server: ClientECH.enc */ + SECItem configId; /* Server: ClientECH.config_id */ + HpkeKdfId kdfId; /* Server: ClientECH.cipher_suite.kdf */ + HpkeAeadId aeadId; /* Server: ClientECH.cipher_suite.aead */ + SECItem retryConfigs; /* Client: ServerECH.retry_configs*/ + PRBool retryConfigsValid; /* Client: Extraction of retry_configss is allowed. + * This is set once the handshake completes (having + * verified to the ECHConfig public name). */ +}; + SECStatus SSLExp_EncodeEchConfig(const char *publicName, const PRUint32 *hpkeSuites, unsigned int hpkeSuiteCount, HpkeKemId kemId, const SECKEYPublicKey *pubKey, PRUint16 maxNameLen, @@ -69,14 +79,15 @@ SECStatus tls13_ConstructClientHelloWithEch(sslSocket *ss, const sslSessionID *s SECStatus tls13_CopyEchConfigs(PRCList *oconfigs, PRCList *configs); SECStatus tls13_DecodeEchConfigs(const SECItem *data, PRCList *configs); void tls13_DestroyEchConfigs(PRCList *list); +void tls13_DestroyEchXtnState(sslEchXtnState *state); SECStatus tls13_GetMatchingEchConfig(const sslSocket *ss, HpkeKdfId kdf, HpkeAeadId aead, const SECItem *configId, sslEchConfig **cfg); SECStatus tls13_MaybeHandleEch(sslSocket *ss, const PRUint8 *msg, PRUint32 msgLen, SECItem *sidBytes, SECItem *comps, SECItem *cookieBytes, SECItem *suites, SECItem **echInner); -SECStatus tls13_MaybeHandleEchSignal(sslSocket *ss); +SECStatus tls13_MaybeHandleEchSignal(sslSocket *ss, const PRUint8 *savedMsg, PRUint32 savedLength); SECStatus tls13_MaybeAcceptEch(sslSocket *ss, const SECItem *sidBytes, const PRUint8 *chOuter, unsigned int chOuterLen, SECItem **chInner); SECStatus tls13_MaybeGreaseEch(sslSocket *ss, unsigned int prefixLen, sslBuffer *buf); -SECStatus tls13_WriteServerEchSignal(sslSocket *ss); +SECStatus tls13_WriteServerEchSignal(sslSocket *ss, PRUint8 *sh, unsigned int shLen); #endif diff --git a/lib/ssl/tls13exthandle.c b/lib/ssl/tls13exthandle.c index 54a4abb09..7991a12c2 100644 --- a/lib/ssl/tls13exthandle.c +++ b/lib/ssl/tls13exthandle.c @@ -1211,6 +1211,12 @@ tls13_ClientHandleEchXtn(const sslSocket *ss, TLSExtensionData *xtnData, PRCList parsedConfigs; PR_INIT_CLIST(&parsedConfigs); + PORT_Assert(!xtnData->ech); + xtnData->ech = PORT_ZNew(sslEchXtnState); + if (!xtnData->ech) { + return SECFailure; + } + /* Parse the list to determine 1) That the configs are valid * and properly encoded, and 2) If any are compatible. */ rv = tls13_DecodeEchConfigs(data, &parsedConfigs); @@ -1219,11 +1225,11 @@ tls13_ClientHandleEchXtn(const sslSocket *ss, TLSExtensionData *xtnData, PORT_SetError(SSL_ERROR_RX_MALFORMED_ECH_CONFIG); return SECFailure; } - /* Don't mark ECH negotiated on retry. Save the the raw - * configs so the application can retry. If we sent GREASE - * ECH (no echHpkeCtx), don't apply returned retry_configs. */ + /* Don't mark ECH negotiated on rejection with retry_config. + * Save the the raw configs so the application can retry. If + * we sent GREASE ECH (no echHpkeCtx), don't apply retry_configs. */ if (ss->ssl3.hs.echHpkeCtx && !PR_CLIST_IS_EMPTY(&parsedConfigs)) { - rv = SECITEM_CopyItem(NULL, &xtnData->echRetryConfigs, data); + rv = SECITEM_CopyItem(NULL, &xtnData->ech->retryConfigs, data); } tls13_DestroyEchConfigs(&parsedConfigs); @@ -1465,14 +1471,22 @@ tls13_ServerHandleEchXtn(const sslSocket *ss, TLSExtensionData *xtnData, return SECSuccess; } - /* On CHInner, the extension must be empty. */ - if (ss->ssl3.hs.echAccepted && data->len > 0) { + if (ss->ssl3.hs.echAccepted) { ssl3_ExtSendAlert(ss, alert_fatal, illegal_parameter); - PORT_SetError(SSL_ERROR_RX_MALFORMED_ECH_EXTENSION); + PORT_SetError(SSL_ERROR_RX_UNEXPECTED_EXTENSION); + return SECFailure; + } + + if (ssl3_FindExtension(CONST_CAST(sslSocket, ss), ssl_tls13_ech_is_inner_xtn)) { + ssl3_ExtSendAlert(ss, alert_fatal, illegal_parameter); + PORT_SetError(SSL_ERROR_RX_UNEXPECTED_EXTENSION); + return SECFailure; + } + + PORT_Assert(!xtnData->ech); + xtnData->ech = PORT_ZNew(sslEchXtnState); + if (!xtnData->ech) { return SECFailure; - } else if (ss->ssl3.hs.echAccepted) { - xtnData->negotiated[xtnData->numNegotiated++] = ssl_tls13_encrypted_client_hello_xtn; - return SECSuccess; } /* Parse the KDF and AEAD. */ @@ -1503,37 +1517,40 @@ tls13_ServerHandleEchXtn(const sslSocket *ss, TLSExtensionData *xtnData, goto alert_loser; } - /* payload */ + /* payload, which must be final and non-empty. */ rv = ssl3_ExtConsumeHandshakeVariable(ss, &encryptedCh, 2, &data->data, &data->len); if (rv != SECSuccess) { goto alert_loser; } - - if (data->len) { + if (data->len || !encryptedCh.len) { goto alert_loser; } - /* All fields required. */ - if (!configId.len || !senderPubKey.len || !encryptedCh.len) { - goto alert_loser; - } + if (!ss->ssl3.hs.helloRetry) { + /* In the real ECH HRR case, config_id and enc should be empty. This + * is checked after acceptance, because it might be GREASE ECH. */ + if (!configId.len || !senderPubKey.len) { + goto alert_loser; + } - rv = SECITEM_CopyItem(NULL, &xtnData->echSenderPubKey, &senderPubKey); - if (rv == SECFailure) { - return SECFailure; - } + rv = SECITEM_CopyItem(NULL, &xtnData->ech->senderPubKey, &senderPubKey); + if (rv == SECFailure) { + return SECFailure; + } - rv = SECITEM_CopyItem(NULL, &xtnData->innerCh, &encryptedCh); - if (rv == SECFailure) { - return SECFailure; + rv = SECITEM_CopyItem(NULL, &xtnData->ech->configId, &configId); + if (rv == SECFailure) { + return SECFailure; + } } - rv = SECITEM_CopyItem(NULL, &xtnData->echConfigId, &configId); + rv = SECITEM_CopyItem(NULL, &xtnData->ech->innerCh, &encryptedCh); if (rv == SECFailure) { return SECFailure; } - xtnData->echCipherSuite = (aead & 0xFFFF) << 16 | (kdf & 0xFFFF); + xtnData->ech->kdfId = kdf; + xtnData->ech->aeadId = aead; /* Not negotiated until tls13_MaybeAcceptEch. */ return SECSuccess; @@ -1543,3 +1560,36 @@ alert_loser: PORT_SetError(SSL_ERROR_RX_MALFORMED_ECH_EXTENSION); return SECFailure; } + +SECStatus +tls13_ServerHandleEchIsInnerXtn(const sslSocket *ss, + TLSExtensionData *xtnData, + SECItem *data) +{ + SSL_TRC(3, ("%d: TLS13[%d]: handle ech_is_inner extension", + SSL_GETPID(), ss->fd)); + + if (data->len) { + PORT_SetError(SSL_ERROR_RX_MALFORMED_ECH_EXTENSION); + return SECFailure; + } + + if (ssl3_FindExtension(CONST_CAST(sslSocket, ss), ssl_tls13_encrypted_client_hello_xtn)) { + ssl3_ExtSendAlert(ss, alert_fatal, illegal_parameter); + PORT_SetError(SSL_ERROR_RX_UNEXPECTED_EXTENSION); + return SECFailure; + } + + /* Consider encrypted_client_hello_xtn negotiated if we performed the + * CHOuter decryption. This is only supported in shared mode, so we'll also + * handle ech_is_inner in that case. We might, however, receive a CHInner + * that was forwarded by a different client-facing server. In this case, + * mark ech_is_inner as negotiated, which triggers sending of the ECH + * acceptance signal. ech_is_inner_xtn being negotiated does not imply + * that any other ECH state actually exists. */ + if (ss->ssl3.hs.echAccepted) { + xtnData->negotiated[xtnData->numNegotiated++] = ssl_tls13_encrypted_client_hello_xtn; + } + xtnData->negotiated[xtnData->numNegotiated++] = ssl_tls13_ech_is_inner_xtn; + return SECSuccess; +} diff --git a/lib/ssl/tls13exthandle.h b/lib/ssl/tls13exthandle.h index 556737210..ae79ecba8 100644 --- a/lib/ssl/tls13exthandle.h +++ b/lib/ssl/tls13exthandle.h @@ -94,6 +94,9 @@ SECStatus tls13_ServerSendEchXtn(const sslSocket *ss, TLSExtensionData *xtnData, sslBuffer *buf, PRBool *added); SECStatus tls13_ClientHandleEchXtn(const sslSocket *ss, TLSExtensionData *xtnData, SECItem *data); +SECStatus tls13_ServerHandleEchIsInnerXtn(const sslSocket *ss, + TLSExtensionData *xtnData, + SECItem *data); SECStatus tls13_ClientSendPostHandshakeAuthXtn(const sslSocket *ss, TLSExtensionData *xtnData, sslBuffer *buf, PRBool *added); diff --git a/lib/ssl/tls13hashstate.c b/lib/ssl/tls13hashstate.c index 011b4838e..f2f55ba0f 100644 --- a/lib/ssl/tls13hashstate.c +++ b/lib/ssl/tls13hashstate.c @@ -24,9 +24,11 @@ * uint8 indicator = 0xff; // To disambiguate from tickets. * uint16 cipherSuite; // Selected cipher suite. * uint16 keyShare; // Requested key share group (0=none) + * HpkeKdfId kdfId; // ECH KDF (uint16) + * HpkeAeadId aeadId; // ECH AEAD (uint16) + * opaque echConfigId<0..255>; // ECH config_id + * opaque echHpkeCtx<0..65535>; // ECH serialized HPKE context * opaque applicationToken<0..65535>; // Application token - * echConfigId<0..255>; // Encrypted Client Hello config_id - * echHrrPsk<0..255>; // Encrypted Client Hello HRR PSK * opaque ch_hash[rest_of_buffer]; // H(ClientHello) * } CookieInner; * @@ -43,10 +45,7 @@ tls13_MakeHrrCookie(sslSocket *ss, const sslNamedGroupDef *selectedGroup, PRUint8 cookie[1024]; sslBuffer cookieBuf = SSL_BUFFER(cookie); static const PRUint8 indicator = 0xff; - SECItem hrrNonceInfoItem = { siBuffer, (unsigned char *)kHpkeInfoEchHrr, - strlen(kHpkeInfoEchHrr) }; - PK11SymKey *echHrrPsk = NULL; - SECItem *rawEchPsk = NULL; + SECItem *echHpkeCtx = NULL; /* Encode header. */ rv = sslBuffer_Append(&cookieBuf, &indicator, 1); @@ -63,50 +62,49 @@ tls13_MakeHrrCookie(sslSocket *ss, const sslNamedGroupDef *selectedGroup, return SECFailure; } - /* Application token. */ - rv = sslBuffer_AppendVariable(&cookieBuf, appToken, appTokenLen, 2); - if (rv != SECSuccess) { - return SECFailure; - } - - /* Received ECH config_id, regardless of acceptance or possession - * of a matching ECHConfig. If rejecting ECH, this is essentially a boolean - * indicating that ECH was offered in CH1. If accepting ECH, this config_id - * will be used for the ECH decryption in CH2. */ - if (ss->xtnData.echConfigId.len) { - rv = sslBuffer_AppendVariable(&cookieBuf, ss->xtnData.echConfigId.data, - ss->xtnData.echConfigId.len, 1); - } else { - PORT_Assert(!ssl3_FindExtension(ss, ssl_tls13_encrypted_client_hello_xtn)); - rv = sslBuffer_AppendNumber(&cookieBuf, 0, 1); - } - if (rv != SECSuccess) { - return SECFailure; - } - - /* Extract and encode the ech-hrr-key, if ECH was accepted - * (i.e. an Open() succeeded. */ - if (ss->ssl3.hs.echAccepted) { - rv = PK11_HPKE_ExportSecret(ss->ssl3.hs.echHpkeCtx, &hrrNonceInfoItem, 32, &echHrrPsk); + if (ss->xtnData.ech) { + rv = sslBuffer_AppendNumber(&cookieBuf, ss->xtnData.ech->kdfId, 2); if (rv != SECSuccess) { return SECFailure; } - rv = PK11_ExtractKeyValue(echHrrPsk); + rv = sslBuffer_AppendNumber(&cookieBuf, ss->xtnData.ech->aeadId, 2); if (rv != SECSuccess) { - PK11_FreeSymKey(echHrrPsk); return SECFailure; } - rawEchPsk = PK11_GetKeyData(echHrrPsk); - if (!rawEchPsk) { - PK11_FreeSymKey(echHrrPsk); + + /* Received ECH config_id, regardless of acceptance or possession + * of a matching ECHConfig. */ + PORT_Assert(ss->xtnData.ech->configId.len == 8); + rv = sslBuffer_AppendVariable(&cookieBuf, ss->xtnData.ech->configId.data, + ss->xtnData.ech->configId.len, 1); + if (rv != SECSuccess) { + return SECFailure; + } + + /* There might be no HPKE Context, e.g. when we lack a matching ECHConfig. */ + if (ss->ssl3.hs.echHpkeCtx) { + rv = PK11_HPKE_ExportContext(ss->ssl3.hs.echHpkeCtx, NULL, &echHpkeCtx); + if (rv != SECSuccess) { + return SECFailure; + } + rv = sslBuffer_AppendVariable(&cookieBuf, echHpkeCtx->data, echHpkeCtx->len, 2); + SECITEM_ZfreeItem(echHpkeCtx, PR_TRUE); + } else { + /* Zero length HPKE context. */ + rv = sslBuffer_AppendNumber(&cookieBuf, 0, 2); + } + if (rv != SECSuccess) { return SECFailure; } - rv = sslBuffer_AppendVariable(&cookieBuf, rawEchPsk->data, rawEchPsk->len, 1); - PK11_FreeSymKey(echHrrPsk); } else { - /* Zero length ech_hrr_key. */ - rv = sslBuffer_AppendNumber(&cookieBuf, 0, 1); + rv = sslBuffer_AppendNumber(&cookieBuf, 0, 7); + if (rv != SECSuccess) { + return SECFailure; + } } + + /* Application token. */ + rv = sslBuffer_AppendVariable(&cookieBuf, appToken, appTokenLen, 2); if (rv != SECSuccess) { return SECFailure; } @@ -131,23 +129,34 @@ tls13_MakeHrrCookie(sslSocket *ss, const sslNamedGroupDef *selectedGroup, return SECSuccess; } -/* Recover the hash state from the cookie. */ +/* Given a cookie and cookieLen, decrypt and parse, returning + * any values that were requested via the "previous_" params. If + * recoverHashState is true, the transcript state is recovered */ SECStatus -tls13_RecoverHashState(sslSocket *ss, - unsigned char *cookie, unsigned int cookieLen, - ssl3CipherSuite *previousCipherSuite, - const sslNamedGroupDef **previousGroup, - PRBool *previousEchOffered) +tls13_HandleHrrCookie(sslSocket *ss, + unsigned char *cookie, unsigned int cookieLen, + ssl3CipherSuite *previousCipherSuite, + const sslNamedGroupDef **previousGroup, + PRBool *previousEchOffered, + HpkeKdfId *previousEchKdfId, + HpkeAeadId *previousEchAeadId, + SECItem *previousEchConfigId, + HpkeContext **previousEchHpkeCtx, + PRBool recoverHashState) { SECStatus rv; unsigned char plaintext[1024]; unsigned int plaintextLen = 0; sslBuffer messageBuf = SSL_BUFFER_EMPTY; - sslReadBuffer echPskBuf; - sslReadBuffer echConfigIdBuf; + sslReadBuffer echHpkeBuf = { 0 }; + sslReadBuffer echConfigIdBuf = { 0 }; PRUint64 sentinel; PRUint64 cipherSuite; + HpkeContext *hpkeContext = NULL; + HpkeKdfId echKdfId; + HpkeAeadId echAeadId; PRUint64 group; + PRUint64 tmp64; const sslNamedGroupDef *selectedGroup; PRUint64 appTokenLen; @@ -180,77 +189,129 @@ tls13_RecoverHashState(sslSocket *ss, } selectedGroup = ssl_LookupNamedGroup(group); - /* Application token. */ - PORT_Assert(ss->xtnData.applicationToken.len == 0); - rv = sslRead_ReadNumber(&reader, 2, &appTokenLen); + /* ECH Ciphersuite */ + rv = sslRead_ReadNumber(&reader, 2, &tmp64); if (rv != SECSuccess) { FATAL_ERROR(ss, SSL_ERROR_RX_MALFORMED_CLIENT_HELLO, illegal_parameter); return SECFailure; } - if (SECITEM_AllocItem(NULL, &ss->xtnData.applicationToken, - appTokenLen) == NULL) { - FATAL_ERROR(ss, PORT_GetError(), internal_error); - return SECFailure; - } - ss->xtnData.applicationToken.len = appTokenLen; - sslReadBuffer appTokenReader = { 0 }; - rv = sslRead_Read(&reader, appTokenLen, &appTokenReader); + echKdfId = (HpkeKdfId)tmp64; + + rv = sslRead_ReadNumber(&reader, 2, &tmp64); if (rv != SECSuccess) { FATAL_ERROR(ss, SSL_ERROR_RX_MALFORMED_CLIENT_HELLO, illegal_parameter); return SECFailure; } - PORT_Assert(appTokenReader.len == appTokenLen); - PORT_Memcpy(ss->xtnData.applicationToken.data, appTokenReader.buf, appTokenLen); + echAeadId = (HpkeAeadId)tmp64; - /* ECH Config ID, which may be empty. */ + /* ECH Config ID and HPKE context may be empty. */ rv = sslRead_ReadVariable(&reader, 1, &echConfigIdBuf); if (rv != SECSuccess) { FATAL_ERROR(ss, SSL_ERROR_RX_MALFORMED_CLIENT_HELLO, illegal_parameter); return SECFailure; } - /* ECH HRR PSK, if present, is already used by tls13_GetEchInfoFromCookie */ - rv = sslRead_ReadVariable(&reader, 1, &echPskBuf); + rv = sslRead_ReadVariable(&reader, 2, &echHpkeBuf); if (rv != SECSuccess) { FATAL_ERROR(ss, SSL_ERROR_RX_MALFORMED_CLIENT_HELLO, illegal_parameter); return SECFailure; } - /* The remainder is the hash. */ - unsigned int hashLen = SSL_READER_REMAINING(&reader); - if (hashLen != tls13_GetHashSize(ss)) { + /* Application token. */ + PORT_Assert(ss->xtnData.applicationToken.len == 0); + rv = sslRead_ReadNumber(&reader, 2, &appTokenLen); + if (rv != SECSuccess) { FATAL_ERROR(ss, SSL_ERROR_RX_MALFORMED_CLIENT_HELLO, illegal_parameter); return SECFailure; } - - /* Now reinject the message. */ - SSL_ASSERT_HASHES_EMPTY(ss); - rv = ssl_HashHandshakeMessageInt(ss, ssl_hs_message_hash, 0, - SSL_READER_CURRENT(&reader), hashLen, - ssl3_UpdateHandshakeHashes); - if (rv != SECSuccess) { + if (SECITEM_AllocItem(NULL, &ss->xtnData.applicationToken, + appTokenLen) == NULL) { + FATAL_ERROR(ss, PORT_GetError(), internal_error); return SECFailure; } - - /* And finally reinject the HRR. */ - rv = tls13_ConstructHelloRetryRequest(ss, cipherSuite, - selectedGroup, - cookie, cookieLen, - &messageBuf); + ss->xtnData.applicationToken.len = appTokenLen; + sslReadBuffer appTokenReader = { 0 }; + rv = sslRead_Read(&reader, appTokenLen, &appTokenReader); if (rv != SECSuccess) { + FATAL_ERROR(ss, SSL_ERROR_RX_MALFORMED_CLIENT_HELLO, illegal_parameter); return SECFailure; } + PORT_Assert(appTokenReader.len == appTokenLen); + PORT_Memcpy(ss->xtnData.applicationToken.data, appTokenReader.buf, appTokenLen); - rv = ssl_HashHandshakeMessageInt(ss, ssl_hs_server_hello, 0, - SSL_BUFFER_BASE(&messageBuf), - SSL_BUFFER_LEN(&messageBuf), - ssl3_UpdateHandshakeHashes); - sslBuffer_Clear(&messageBuf); - if (rv != SECSuccess) { - return SECFailure; + /* The remainder is the hash. */ + if (recoverHashState) { + unsigned int hashLen = SSL_READER_REMAINING(&reader); + if (hashLen != tls13_GetHashSize(ss)) { + FATAL_ERROR(ss, SSL_ERROR_RX_MALFORMED_CLIENT_HELLO, illegal_parameter); + return SECFailure; + } + + /* Now reinject the message. */ + SSL_ASSERT_HASHES_EMPTY(ss); + rv = ssl_HashHandshakeMessageInt(ss, ssl_hs_message_hash, 0, + SSL_READER_CURRENT(&reader), hashLen, + ssl3_UpdateHandshakeHashes); + if (rv != SECSuccess) { + return SECFailure; + } + + /* And finally reinject the HRR. */ + rv = tls13_ConstructHelloRetryRequest(ss, cipherSuite, + selectedGroup, + cookie, cookieLen, + &messageBuf); + if (rv != SECSuccess) { + return SECFailure; + } + + rv = ssl_HashHandshakeMessageInt(ss, ssl_hs_server_hello, 0, + SSL_BUFFER_BASE(&messageBuf), + SSL_BUFFER_LEN(&messageBuf), + ssl3_UpdateHandshakeHashes); + sslBuffer_Clear(&messageBuf); + if (rv != SECSuccess) { + return SECFailure; + } + } + + if (previousEchHpkeCtx && echHpkeBuf.len) { + const SECItem hpkeItem = { siBuffer, CONST_CAST(unsigned char, echHpkeBuf.buf), + echHpkeBuf.len }; + hpkeContext = PK11_HPKE_ImportContext(&hpkeItem, NULL); + if (!hpkeContext) { + FATAL_ERROR(ss, PORT_GetError(), internal_error); + return SECFailure; + } } - *previousCipherSuite = cipherSuite; - *previousGroup = selectedGroup; - *previousEchOffered = echConfigIdBuf.len > 0; + if (previousEchConfigId && echConfigIdBuf.len) { + SECItem tmp = { siBuffer, NULL, 0 }; + rv = SECITEM_MakeItem(NULL, &tmp, echConfigIdBuf.buf, echConfigIdBuf.len); + if (rv != SECSuccess) { + PK11_HPKE_DestroyContext(hpkeContext, PR_TRUE); + FATAL_ERROR(ss, PORT_GetError(), internal_error); + return SECFailure; + } + *previousEchConfigId = tmp; + } + + if (previousEchKdfId) { + *previousEchKdfId = echKdfId; + } + if (previousEchAeadId) { + *previousEchAeadId = echAeadId; + } + if (previousEchHpkeCtx) { + *previousEchHpkeCtx = hpkeContext; + } + if (previousCipherSuite) { + *previousCipherSuite = cipherSuite; + } + if (previousGroup) { + *previousGroup = selectedGroup; + } + if (previousEchOffered) { + *previousEchOffered = echConfigIdBuf.len > 0; + } return SECSuccess; } diff --git a/lib/ssl/tls13hashstate.h b/lib/ssl/tls13hashstate.h index 8126bd0db..48832f04a 100644 --- a/lib/ssl/tls13hashstate.h +++ b/lib/ssl/tls13hashstate.h @@ -17,9 +17,14 @@ SECStatus tls13_MakeHrrCookie(sslSocket *ss, const sslNamedGroupDef *selectedGro const PRUint8 *appToken, unsigned int appTokenLen, PRUint8 *buf, unsigned int *len, unsigned int maxlen); SECStatus tls13_GetHrrCookieLength(sslSocket *ss, unsigned int *length); -SECStatus tls13_RecoverHashState(sslSocket *ss, - unsigned char *cookie, unsigned int cookieLen, - ssl3CipherSuite *previousCipherSuite, - const sslNamedGroupDef **previousGroup, - PRBool *previousEchOffered); +SECStatus tls13_HandleHrrCookie(sslSocket *ss, + unsigned char *cookie, unsigned int cookieLen, + ssl3CipherSuite *previousCipherSuite, + const sslNamedGroupDef **previousGroup, + PRBool *previousEchOffered, + HpkeKdfId *previousEchKdfId, + HpkeAeadId *previousEchAeadId, + SECItem *previousEchConfigId, + HpkeContext **previousEchHpkeCtx, + PRBool recoverHashState); #endif |