diff options
author | Kevin Jacobs <kjacobs@mozilla.com> | 2021-01-25 17:42:03 +0000 |
---|---|---|
committer | Kevin Jacobs <kjacobs@mozilla.com> | 2021-01-25 17:42:03 +0000 |
commit | d841f5ce8ed0e775c21c8d56feb55e83dd733c41 (patch) | |
tree | ea915cd091aee604a4ce4560a589e483560910e8 | |
parent | e7e4a4a881bc28422834df5b5370a35cf21ba149 (diff) | |
download | nss-hg-d841f5ce8ed0e775c21c8d56feb55e83dd733c41.tar.gz |
Bug 1681585 - Update ECH to Draft-09. r=mt
This patch updates ECH implementation to draft-09. Changes of note are:
- Acceptance signal derivation is now based on the handshake secret.
- `config_id` hint changes from 32B to 8B, trial decryption added on the server.
- Duplicate code in HRR cookie handling has been consolidated into `tls13_HandleHrrCookie`.
- `ech_is_inner` extension is added, which causes a server to indicate ECH acceptance.
- Per the above, support signaling ECH acceptance when acting as a backend server in split-mode
(i.e. when there is no other local Encrypted Client Hello state).
Differential Revision: https://phabricator.services.mozilla.com/D101049
-rw-r--r-- | automation/abi-check/expected-report-libssl3.so.txt | 11 | ||||
-rw-r--r-- | gtests/ssl_gtest/libssl_internals.c | 23 | ||||
-rw-r--r-- | gtests/ssl_gtest/libssl_internals.h | 4 | ||||
-rw-r--r-- | gtests/ssl_gtest/ssl_extension_unittest.cc | 16 | ||||
-rw-r--r-- | gtests/ssl_gtest/tls_ech_unittest.cc | 471 | ||||
-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 |
21 files changed, 1241 insertions, 762 deletions
diff --git a/automation/abi-check/expected-report-libssl3.so.txt b/automation/abi-check/expected-report-libssl3.so.txt index e69de29bb..15e1f47da 100644 --- a/automation/abi-check/expected-report-libssl3.so.txt +++ b/automation/abi-check/expected-report-libssl3.so.txt @@ -0,0 +1,11 @@ + +1 function with some indirect sub-type change: + + [C] 'function SECStatus SSL_HandshakeNegotiatedExtension(PRFileDesc*, SSLExtensionType, PRBool*)' at sslreveal.c:72:1 has some indirect sub-type changes: + parameter 2 of type 'typedef SSLExtensionType' has sub-type changes: + underlying type 'enum __anonymous_enum__' at sslt.h:519:1 changed: + type size hasn't changed + 1 enumerator insertion: + '__anonymous_enum__::ssl_tls13_ech_is_inner_xtn' value '55817' + 1 enumerator change: + '__anonymous_enum__::ssl_tls13_encrypted_client_hello_xtn' from value '65032' to '65033' at sslt.h:519:1 diff --git a/gtests/ssl_gtest/libssl_internals.c b/gtests/ssl_gtest/libssl_internals.c index 01d698e71..db0c9e86b 100644 --- a/gtests/ssl_gtest/libssl_internals.c +++ b/gtests/ssl_gtest/libssl_internals.c @@ -494,6 +494,27 @@ SECStatus SSLInt_SetRawEchConfigForRetry(PRFileDesc *fd, const uint8_t *buf, sslEchConfig *cfg = (sslEchConfig *)PR_LIST_HEAD(&ss->echConfigs); SECITEM_FreeItem(&cfg->raw, PR_FALSE); SECITEM_AllocItem(NULL, &cfg->raw, len); - memcpy(cfg->raw.data, buf, len); + PORT_Memcpy(cfg->raw.data, buf, len); + return SECSuccess; +} + +// Zero the echConfig.config_id for all configured echConfigs. +// This mimics a collision on the 8B config ID so that we can +// test trial decryption. +SECStatus SSLInt_ZeroEchConfigIds(PRFileDesc *fd) { + if (!fd) { + return SECFailure; + } + sslSocket *ss = ssl_FindSocket(fd); + if (!ss) { + return SECFailure; + } + + for (PRCList *cur_p = PR_LIST_HEAD(&ss->echConfigs); cur_p != &ss->echConfigs; + cur_p = PR_NEXT_LINK(cur_p)) { + PORT_Memset(((sslEchConfig *)cur_p)->configId, 0, + sizeof(((sslEchConfig *)cur_p)->configId)); + } + return SECSuccess; } diff --git a/gtests/ssl_gtest/libssl_internals.h b/gtests/ssl_gtest/libssl_internals.h index a6a3239d5..372f254d6 100644 --- a/gtests/ssl_gtest/libssl_internals.h +++ b/gtests/ssl_gtest/libssl_internals.h @@ -51,5 +51,5 @@ SECStatus SSLInt_SetDCAdvertisedSigSchemes(PRFileDesc *fd, SECStatus SSLInt_RemoveServerCertificates(PRFileDesc *fd); SECStatus SSLInt_SetRawEchConfigForRetry(PRFileDesc *fd, const uint8_t *buf, size_t len); - -#endif // ndef libssl_internals_h_ +SECStatus SSLInt_ZeroEchConfigIds(PRFileDesc *fd); +#endif // ifndef libssl_internals_h_ diff --git a/gtests/ssl_gtest/ssl_extension_unittest.cc b/gtests/ssl_gtest/ssl_extension_unittest.cc index b08eba59a..f60697900 100644 --- a/gtests/ssl_gtest/ssl_extension_unittest.cc +++ b/gtests/ssl_gtest/ssl_extension_unittest.cc @@ -1098,22 +1098,6 @@ TEST_P(TlsExtensionTest13, HrrThenRemoveSupportedGroups) { SSL_ERROR_MISSING_SUPPORTED_GROUPS_EXTENSION); } -#ifdef NSS_ENABLE_DRAFT_HPKE -TEST_P(TlsExtensionTest13, HrrThenRemoveEch) { - if (variant_ == ssl_variant_datagram) { - // ECH not supported in DTLS. - GTEST_SKIP(); - } - - EnsureTlsSetup(); - SetupEch(client_, server_); - ExpectAlert(server_, kTlsAlertIllegalParameter); - HrrThenRemoveExtensionsTest(ssl_tls13_encrypted_client_hello_xtn, - SSL_ERROR_ILLEGAL_PARAMETER_ALERT, - SSL_ERROR_BAD_2ND_CLIENT_HELLO); -} -#endif - TEST_P(TlsExtensionTest13, EmptyVersionList) { static const uint8_t ext[] = {0x00, 0x00}; ConnectWithBogusVersionList(ext, sizeof(ext)); diff --git a/gtests/ssl_gtest/tls_ech_unittest.cc b/gtests/ssl_gtest/tls_ech_unittest.cc index b05224bda..64a270624 100644 --- a/gtests/ssl_gtest/tls_ech_unittest.cc +++ b/gtests/ssl_gtest/tls_ech_unittest.cc @@ -19,9 +19,9 @@ namespace nss_test { class TlsAgentEchTest : public TlsAgentTestClient13 { protected: - void InstallEchConfig(const DataBuffer& record, PRErrorCode err = 0) { - SECStatus rv = - SSL_SetClientEchConfigs(agent_->ssl_fd(), record.data(), record.len()); + void InstallEchConfig(const DataBuffer& echconfig, PRErrorCode err = 0) { + SECStatus rv = SSL_SetClientEchConfigs(agent_->ssl_fd(), echconfig.data(), + echconfig.len()); if (err == 0) { ASSERT_EQ(SECSuccess, rv); } else { @@ -143,18 +143,6 @@ class TlsConnectStreamTls13Ech : public TlsConnectTestBase { Connect(); } - private: - // Testing certan invalid CHInner configurations is tricky, particularly - // since the CHOuter forms AAD and isn't available in filters. Instead of - // generating these inputs on the fly, use a fixed server keypair so that - // the input can be generated once (e.g. via a debugger) and replayed in - // each invocation of the test. - std::string kFixedServerPubkey = - "3067020100301406072a8648ce3d020106092b06010401da470f01044c304a" - "02010104205a8aa0d2476b28521588e0c704b14db82cdd4970d340d293a957" - "6deaee9ec1c7a1230321008756e2580c07c1d2ffcb662f5fadc6d6ff13da85" - "abd7adfecf984aaa102c1269"; - void ImportFixedEchKeypair(ScopedSECKEYPublicKey& pub, ScopedSECKEYPrivateKey& priv) { ScopedPK11SlotInfo slot(PK11_GetInternalSlot()); @@ -176,16 +164,30 @@ class TlsConnectStreamTls13Ech : public TlsConnectTestBase { ASSERT_NE(nullptr, tmp_pub); } + private: + // Testing certan invalid CHInner configurations is tricky, particularly + // since the CHOuter forms AAD and isn't available in filters. Instead of + // generating these inputs on the fly, use a fixed server keypair so that + // the input can be generated once (e.g. via a debugger) and replayed in + // each invocation of the test. + std::string kFixedServerPubkey = + "3067020100301406072a8648ce3d020106092b06010401da470f01044c304a" + "02010104205a8aa0d2476b28521588e0c704b14db82cdd4970d340d293a957" + "6deaee9ec1c7a1230321008756e2580c07c1d2ffcb662f5fadc6d6ff13da85" + "abd7adfecf984aaa102c1269"; + void SetMutualEchConfigs(ScopedSECKEYPublicKey& pub, ScopedSECKEYPrivateKey& priv) { - DataBuffer record; + DataBuffer echconfig; TlsConnectTestBase::GenerateEchConfig(HpkeDhKemX25519Sha256, kDefaultSuites, - kPublicName, 100, record, pub, priv); + kPublicName, 100, echconfig, pub, + priv); ASSERT_EQ(SECSuccess, SSL_SetServerEchConfigs(server_->ssl_fd(), pub.get(), priv.get(), - record.data(), record.len())); - ASSERT_EQ(SECSuccess, SSL_SetClientEchConfigs(client_->ssl_fd(), - record.data(), record.len())); + echconfig.data(), echconfig.len())); + ASSERT_EQ(SECSuccess, + SSL_SetClientEchConfigs(client_->ssl_fd(), echconfig.data(), + echconfig.len())); } }; @@ -224,17 +226,17 @@ TEST_P(TlsAgentEchTest, EchConfigsSupportedYesNo) { // ECHConfig 2 cipher_suites are unsupported. const std::string mixed = - "0086FE08003F000B7075626C69632E6E616D6500203BB6D46C201B820F1AE4AFD4DEC304" - "444156E4E04D1BF0FFDA7783B6B457F75600200008000100030001000100640000FE0800" + "0086FE09003F000B7075626C69632E6E616D6500203BB6D46C201B820F1AE4AFD4DEC304" + "444156E4E04D1BF0FFDA7783B6B457F75600200008000100030001000100640000FE0900" "3F000B7075626C69632E6E616D6500203BB6D46C201B820F1AE4AFD4DEC304444156E4E0" "4D1BF0FFDA7783B6B457F756002000080001FFFFFFFF000100640000"; std::vector<uint8_t> config = hex_string_to_bytes(mixed); - DataBuffer record(config.data(), config.size()); + DataBuffer echconfig(config.data(), config.size()); EnsureInit(); EXPECT_EQ(SECSuccess, SSL_EnableTls13GreaseEch(agent_->ssl_fd(), PR_FALSE)); // Don't GREASE - InstallEchConfig(record, 0); + InstallEchConfig(echconfig, 0); auto filter = MakeTlsFilter<TlsExtensionCapture>( agent_, ssl_tls13_encrypted_client_hello_xtn); agent_->Handshake(); @@ -249,17 +251,17 @@ TEST_P(TlsAgentEchTest, EchConfigsSupportedNoYes) { // ECHConfig 1 cipher_suites are unsupported. const std::string mixed = - "0086FE08003F000B7075626C69632E6E616D6500203BB6D46C201B820F1AE4AFD4DEC304" - "444156E4E04D1BF0FFDA7783B6B457F756002000080001FFFFFFFF000100640000FE0800" + "0086FE09003F000B7075626C69632E6E616D6500203BB6D46C201B820F1AE4AFD4DEC304" + "444156E4E04D1BF0FFDA7783B6B457F756002000080001FFFFFFFF000100640000FE0900" "3F000B7075626C69632E6E616D6500203BB6D46C201B820F1AE4AFD4DEC304444156E4E0" "4D1BF0FFDA7783B6B457F75600200008000100030001000100640000"; std::vector<uint8_t> config = hex_string_to_bytes(mixed); - DataBuffer record(config.data(), config.size()); + DataBuffer echconfig(config.data(), config.size()); EnsureInit(); EXPECT_EQ(SECSuccess, SSL_EnableTls13GreaseEch(agent_->ssl_fd(), PR_FALSE)); // Don't GREASE - InstallEchConfig(record, 0); + InstallEchConfig(echconfig, 0); auto filter = MakeTlsFilter<TlsExtensionCapture>( agent_, ssl_tls13_encrypted_client_hello_xtn); agent_->Handshake(); @@ -274,17 +276,17 @@ TEST_P(TlsAgentEchTest, EchConfigsSupportedNoNo) { // ECHConfig 1 and 2 cipher_suites are unsupported. const std::string unsupported = - "0086FE08003F000B7075626C69632E6E616D6500203BB6D46C201B820F1AE4AFD4DEC304" - "444156E4E04D1BF0FFDA7783B6B457F756002000080001FFFF0001FFFF00640000FE0800" + "0086FE09003F000B7075626C69632E6E616D6500203BB6D46C201B820F1AE4AFD4DEC304" + "444156E4E04D1BF0FFDA7783B6B457F756002000080001FFFF0001FFFF00640000FE0900" "3F000B7075626C69632E6E616D6500203BB6D46C201B820F1AE4AFD4DEC304444156E4E0" "4D1BF0FFDA7783B6B457F75600200008FFFF0003FFFF000100640000"; std::vector<uint8_t> config = hex_string_to_bytes(unsupported); - DataBuffer record(config.data(), config.size()); + DataBuffer echconfig(config.data(), config.size()); EnsureInit(); EXPECT_EQ(SECSuccess, SSL_EnableTls13GreaseEch(agent_->ssl_fd(), PR_FALSE)); // Don't GREASE - InstallEchConfig(record, SEC_ERROR_INVALID_ARGS); + InstallEchConfig(echconfig, SEC_ERROR_INVALID_ARGS); auto filter = MakeTlsFilter<TlsExtensionCapture>( agent_, ssl_tls13_encrypted_client_hello_xtn); agent_->Handshake(); @@ -296,11 +298,11 @@ TEST_P(TlsAgentEchTest, ShortEchConfig) { EnsureInit(); ScopedSECKEYPublicKey pub; ScopedSECKEYPrivateKey priv; - DataBuffer record; + DataBuffer echconfig; TlsConnectTestBase::GenerateEchConfig(HpkeDhKemX25519Sha256, kDefaultSuites, - kPublicName, 100, record, pub, priv); - record.Truncate(record.len() - 1); - InstallEchConfig(record, SEC_ERROR_BAD_DATA); + kPublicName, 100, echconfig, pub, priv); + echconfig.Truncate(echconfig.len() - 1); + InstallEchConfig(echconfig, SEC_ERROR_BAD_DATA); EXPECT_EQ(SECSuccess, SSL_EnableTls13GreaseEch(agent_->ssl_fd(), PR_FALSE)); // Don't GREASE auto filter = MakeTlsFilter<TlsExtensionCapture>( @@ -314,11 +316,11 @@ TEST_P(TlsAgentEchTest, LongEchConfig) { EnsureInit(); ScopedSECKEYPublicKey pub; ScopedSECKEYPrivateKey priv; - DataBuffer record; + DataBuffer echconfig; TlsConnectTestBase::GenerateEchConfig(HpkeDhKemX25519Sha256, kDefaultSuites, - kPublicName, 100, record, pub, priv); - record.Write(record.len(), 1, 1); // Append one byte - InstallEchConfig(record, SEC_ERROR_BAD_DATA); + kPublicName, 100, echconfig, pub, priv); + echconfig.Write(echconfig.len(), 1, 1); // Append one byte + InstallEchConfig(echconfig, SEC_ERROR_BAD_DATA); EXPECT_EQ(SECSuccess, SSL_EnableTls13GreaseEch(agent_->ssl_fd(), PR_FALSE)); // Don't GREASE auto filter = MakeTlsFilter<TlsExtensionCapture>( @@ -332,13 +334,13 @@ TEST_P(TlsAgentEchTest, UnsupportedEchConfigVersion) { EnsureInit(); ScopedSECKEYPublicKey pub; ScopedSECKEYPrivateKey priv; - DataBuffer record; + DataBuffer echconfig; static const uint8_t bad_version[] = {0xff, 0xff}; DataBuffer bad_ver_buf(bad_version, sizeof(bad_version)); TlsConnectTestBase::GenerateEchConfig(HpkeDhKemX25519Sha256, kDefaultSuites, - kPublicName, 100, record, pub, priv); - record.Splice(bad_ver_buf, 2, 2); - InstallEchConfig(record, SEC_ERROR_INVALID_ARGS); + kPublicName, 100, echconfig, pub, priv); + echconfig.Splice(bad_ver_buf, 2, 2); + InstallEchConfig(echconfig, SEC_ERROR_INVALID_ARGS); EXPECT_EQ(SECSuccess, SSL_EnableTls13GreaseEch(agent_->ssl_fd(), PR_FALSE)); // Don't GREASE auto filter = MakeTlsFilter<TlsExtensionCapture>( @@ -352,12 +354,12 @@ TEST_P(TlsAgentEchTest, UnsupportedHpkeKem) { EnsureInit(); ScopedSECKEYPublicKey pub; ScopedSECKEYPrivateKey priv; - DataBuffer record; + DataBuffer echconfig; // SSL_EncodeEchConfig encodes without validation. TlsConnectTestBase::GenerateEchConfig(static_cast<HpkeKemId>(0xff), kDefaultSuites, kPublicName, 100, - record, pub, priv); - InstallEchConfig(record, SEC_ERROR_INVALID_ARGS); + echconfig, pub, priv); + InstallEchConfig(echconfig, SEC_ERROR_INVALID_ARGS); EXPECT_EQ(SECSuccess, SSL_EnableTls13GreaseEch(agent_->ssl_fd(), PR_FALSE)); // Don't GREASE auto filter = MakeTlsFilter<TlsExtensionCapture>( @@ -371,10 +373,26 @@ TEST_P(TlsAgentEchTest, EchRejectIgnoreAllUnknownSuites) { EnsureInit(); ScopedSECKEYPublicKey pub; ScopedSECKEYPrivateKey priv; - DataBuffer record; + DataBuffer echconfig; TlsConnectTestBase::GenerateEchConfig(HpkeDhKemX25519Sha256, kBogusSuite, - kPublicName, 100, record, pub, priv); - InstallEchConfig(record, SEC_ERROR_INVALID_ARGS); + kPublicName, 100, echconfig, pub, priv); + InstallEchConfig(echconfig, SEC_ERROR_INVALID_ARGS); + EXPECT_EQ(SECSuccess, SSL_EnableTls13GreaseEch(agent_->ssl_fd(), + PR_FALSE)); // Don't GREASE + auto filter = MakeTlsFilter<TlsExtensionCapture>( + agent_, ssl_tls13_encrypted_client_hello_xtn); + agent_->Handshake(); + ASSERT_FALSE(filter->captured()); +} + +TEST_P(TlsAgentEchTest, EchConfigRejectEmptyPublicName) { + EnsureInit(); + ScopedSECKEYPublicKey pub; + ScopedSECKEYPrivateKey priv; + DataBuffer echconfig; + TlsConnectTestBase::GenerateEchConfig(HpkeDhKemX25519Sha256, kBogusSuite, "", + 100, echconfig, pub, priv); + InstallEchConfig(echconfig, SSL_ERROR_RX_MALFORMED_ECH_CONFIG); EXPECT_EQ(SECSuccess, SSL_EnableTls13GreaseEch(agent_->ssl_fd(), PR_FALSE)); // Don't GREASE auto filter = MakeTlsFilter<TlsExtensionCapture>( @@ -385,17 +403,18 @@ TEST_P(TlsAgentEchTest, EchRejectIgnoreAllUnknownSuites) { TEST_F(TlsConnectStreamTls13, EchAcceptIgnoreSingleUnknownSuite) { EnsureTlsSetup(); - DataBuffer record; + DataBuffer echconfig; ScopedSECKEYPublicKey pub; ScopedSECKEYPrivateKey priv; TlsConnectTestBase::GenerateEchConfig(HpkeDhKemX25519Sha256, kUnknownFirstSuite, kPublicName, 100, - record, pub, priv); - ASSERT_EQ(SECSuccess, SSL_SetClientEchConfigs(client_->ssl_fd(), - record.data(), record.len())); + echconfig, pub, priv); + ASSERT_EQ(SECSuccess, + SSL_SetClientEchConfigs(client_->ssl_fd(), echconfig.data(), + echconfig.len())); ASSERT_EQ(SECSuccess, SSL_SetServerEchConfigs(server_->ssl_fd(), pub.get(), priv.get(), - record.data(), record.len())); + echconfig.data(), echconfig.len())); client_->ExpectEch(); server_->ExpectEch(); @@ -488,10 +507,10 @@ TEST_P(TlsAgentEchTest, NoEarlyRetryConfigs) { ScopedSECKEYPublicKey pub; ScopedSECKEYPrivateKey priv; - DataBuffer record; + DataBuffer echconfig; TlsConnectTestBase::GenerateEchConfig(HpkeDhKemX25519Sha256, kDefaultSuites, - kPublicName, 100, record, pub, priv); - InstallEchConfig(record, 0); + kPublicName, 100, echconfig, pub, priv); + InstallEchConfig(echconfig, 0); EXPECT_EQ(SECFailure, SSL_GetEchRetryConfigs(agent_->ssl_fd(), &retry_configs)); @@ -502,11 +521,11 @@ TEST_P(TlsAgentEchTest, NoSniSoNoEch) { EnsureInit(); ScopedSECKEYPublicKey pub; ScopedSECKEYPrivateKey priv; - DataBuffer record; + DataBuffer echconfig; TlsConnectTestBase::GenerateEchConfig(HpkeDhKemX25519Sha256, kDefaultSuites, - kPublicName, 100, record, pub, priv); + kPublicName, 100, echconfig, pub, priv); SSL_SetURL(agent_->ssl_fd(), ""); - InstallEchConfig(record, 0); + InstallEchConfig(echconfig, 0); SSL_SetURL(agent_->ssl_fd(), ""); EXPECT_EQ(SECSuccess, SSL_EnableTls13GreaseEch(agent_->ssl_fd(), PR_FALSE)); // Don't GREASE @@ -520,7 +539,7 @@ TEST_P(TlsAgentEchTest, NoEchConfigSoNoEch) { EnsureInit(); ScopedSECKEYPublicKey pub; ScopedSECKEYPrivateKey priv; - DataBuffer record; + DataBuffer echconfig; EXPECT_EQ(SECSuccess, SSL_EnableTls13GreaseEch(agent_->ssl_fd(), PR_FALSE)); // Don't GREASE auto filter = MakeTlsFilter<TlsExtensionCapture>( @@ -533,29 +552,29 @@ TEST_P(TlsAgentEchTest, EchConfigDuplicateExtensions) { EnsureInit(); ScopedSECKEYPublicKey pub; ScopedSECKEYPrivateKey priv; - DataBuffer record; + DataBuffer echconfig; TlsConnectTestBase::GenerateEchConfig(HpkeDhKemX25519Sha256, kDefaultSuites, - kPublicName, 100, record, pub, priv); + kPublicName, 100, echconfig, pub, priv); static const uint8_t duped_xtn[] = {0x00, 0x08, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00}; DataBuffer buf(duped_xtn, sizeof(duped_xtn)); - record.Truncate(record.len() - 2); - record.Append(buf); + echconfig.Truncate(echconfig.len() - 2); + echconfig.Append(buf); uint32_t len; - ASSERT_TRUE(record.Read(0, 2, &len)); + ASSERT_TRUE(echconfig.Read(0, 2, &len)); len += buf.len() - 2; DataBuffer new_len; ASSERT_TRUE(new_len.Write(0, len, 2)); - record.Splice(new_len, 0, 2); + echconfig.Splice(new_len, 0, 2); new_len.Truncate(0); - ASSERT_TRUE(record.Read(4, 2, &len)); + ASSERT_TRUE(echconfig.Read(4, 2, &len)); len += buf.len() - 2; ASSERT_TRUE(new_len.Write(0, len, 2)); - record.Splice(new_len, 4, 2); + echconfig.Splice(new_len, 4, 2); - InstallEchConfig(record, SEC_ERROR_EXTENSION_VALUE_INVALID); + InstallEchConfig(echconfig, SEC_ERROR_EXTENSION_VALUE_INVALID); EXPECT_EQ(SECSuccess, SSL_EnableTls13GreaseEch(agent_->ssl_fd(), PR_FALSE)); // Don't GREASE auto filter = MakeTlsFilter<TlsExtensionCapture>( @@ -570,17 +589,16 @@ TEST_P(TlsAgentEchTest, EchConfigDuplicateExtensions) { // extension in ClientHelloOuter. TEST_F(TlsConnectStreamTls13Ech, EchOuterExtensionsReferencesMissing) { std::string ch = - "01000170030374d616d97efe591bf9bee4496bcc1118145b4dd02f7d1ff979fd0cf61749" - "a91e0000061301130313020100014100000010000e00000b7075626c69632e6e616d65ff" + "010001580303dfff91b5e1ba00f29d2338419b3abf125ee1051a942ae25163bbf609a1ea" + "11920000061301130313020100012900000010000e00000b7075626c69632e6e616d65ff" "01000100000a00140012001d00170018001901000101010201030104003300260024001d" - "00204f346f86351b077492c83564c909d1aaab4f6f3ee2566af0e90a4684c793805d002b" + "0020d94c1590c261e9ea8ae55bc9581f397cc598115f8b70aec1b0236f4c8c555537002b" "0003020304000d0018001604030503060302030804080508060401050106010201002d00" - "020101001c00024001fe0800b30001000320a10698ccbd4bd86df91f617e58dd2ca96b8b" - "a5f058dd5c5ab1ca9750ef9d28c70020924764b36fe5d4a985f9857ceb75edb10b5f4b5b" - "f9d59290db70743e3c582163006acea5d7785cc506ecf5c859a9cad18f2b1df1a32231fe" - "0330471ee0e88ece9047e6491a381bfabed58f7fc542f0ba78eb55030bcfe1d400f67275" - "eac8619d1e4237e9d6176dd4eb54f3f25865686756f313a4ba47901c83e5ad5413609d39" - "816346b940115fd68e534609"; + "020101001c00024001fe09009b0001000308fde4163c5c6e8bb6002067a895efa2721c88" + "63ecfa1bea1e520ae6f6cf938e3e37802688f7a83a871a04006aa693f053f87db87cf82a" + "7caa20670d79b92ccda97893fdf99352fc766fb3dd5570948311dddb6d41214234fae585" + "e354a048c072b3fb00a0a64e8e089e4a90152ee91a2c5b947c99d3dcebfb6334453b023d" + "4d725010996a290a0552e4b238ec91c21440adc0d51a4435"; ReplayChWithMalformedInner(ch, kTlsAlertIllegalParameter, SSL_ERROR_RX_MALFORMED_ECH_EXTENSION, SSL_ERROR_ILLEGAL_PARAMETER_ALERT); @@ -589,17 +607,16 @@ TEST_F(TlsConnectStreamTls13Ech, EchOuterExtensionsReferencesMissing) { // Drop supported_versions from CHInner, make sure we don't negotiate 1.2+ECH. TEST_F(TlsConnectStreamTls13Ech, EchVersion12Inner) { std::string ch = - "0100017003034dd5bf4c12835e9be21f983953720e3595b3a8eeb4a44467678caceb7727" - "3be90000061301130313020100014100000010000e00000b7075626c69632e6e616d65ff" + "0100015103038fbe6f75b0123116fa5c4eccf0cf26c17ab1ded5529307e419c036ac7e9c" + "e8e30000061301130313020100012200000010000e00000b7075626c69632e6e616d65ff" "01000100000a00140012001d00170018001901000101010201030104003300260024001d" - "0020af7b976cdf69ffcd494ca5a93ae3ecde692b09be518ee033aad908c45b82c368002b" + "002078d644583b4f056bec4d8ae9bddd383aed6eb7cdb3294f88b0e37a4f26a02549002b" "0003020304000d0018001604030503060302030804080508060401050106010201002d00" - "020101001c0002400100150003000000fe0800ac0001000320a10698ccbd4bd86df91f61" - "7e58dd2ca96b8ba5f058dd5c5ab1ca9750ef9d28c70020f5ece4c187b76f7e3d467c7506" - "215e73c27c918cd863c0e80d76a7987ec274320063e037492868eff5296a22dc50885e9d" - "f6964a5e26546f1bada043f8834988dfea5394b4c45a4d0b3afc52142d33f94161135a63" - "ed3c1b63f60d8133fb1cff17e1f9ced6c871984e412ed8ddb0f487c4d09d7aea80488004" - "c45a17cd3b5cdca316155fdb"; + "020101001c00024001fe0900940001000308fde4163c5c6e8bb600208958e66d1d4bbd46" + "4792f392e119dbce91ee3e65067899b45c83855dae61e67a00637df038e7b35483786707" + "dd1b25be5cd3dd07f1ca4b33a3595ddb959e5c0da3d2f0b3314417614968691700c05232" + "07c729b34f3b5de62728b3cb6b45b00e6f94b204a9504d0e7e24c66f42aacc73591c86ef" + "571e61cebd6ba671081150a2dae89e7493"; ReplayChWithMalformedInner(ch, kTlsAlertProtocolVersion, SSL_ERROR_UNSUPPORTED_VERSION, SSL_ERROR_PROTOCOL_VERSION_ALERT); @@ -608,40 +625,103 @@ TEST_F(TlsConnectStreamTls13Ech, EchVersion12Inner) { // Use CHInner supported_versions to negotiate 1.2. TEST_F(TlsConnectStreamTls13Ech, EchVersion12InnerSupportedVersions) { std::string ch = - "010001700303845c298db4017d2ed2584284b90e4ecba57a63663560c57aa0b1ac51203d" - "c8560000061301130313020100014100000010000e00000b7075626c69632e6e616d65ff" + "01000158030378a601a3f12229e53e0b8d92c3599bf1782e8261d2ecaec9bbe595d4c901" + "98770000061301130313020100012900000010000e00000b7075626c69632e6e616d65ff" "01000100000a00140012001d00170018001901000101010201030104003300260024001d" - "00203356719e88b539645438f645916aeeffe93c38803a59d6997938aa98eefbcf64002b" + "00201c8017d6970f3a92ac1c9919c3a26788052f84599fb0c3cb7bd381304148724e002b" "0003020304000d0018001604030503060302030804080508060401050106010201002d00" - "020101001c00024001fe0800b30001000320a10698ccbd4bd86df91f617e58dd2ca96b8b" - "a5f058dd5c5ab1ca9750ef9d28c700208412c945c53624bcace5eda0dc1ad300a1620e86" - "5a0f4a27755a3477b115b65b006abf1dfd77ddc1b80c5976732174a5fe7ebcf9ff1a548b" - "097daa12a37f3e32a613a0798544ba1d96239431bc807ddd9055ac3fb3e32b2eb42cec30" - "e915357418a953027d73020fd739287414205349eeff376dd464750ca70a965141a88800" - "6a043fe1d6d882d9a2c2f6f3"; + "020101001c00024001fe09009b0001000308fde4163c5c6e8bb60020f7347d34f125e866" + "76b1cdc43455c6c00918a3c8a961335e1b9aa864da2b5313006a21e6ad81533e90cea24e" + "c2c3656f6b53114b4c63bf89462696f1c8ad4e1193d87062a5537edbe83c9b35c41e9763" + "1d2333270854758ee02548afb7f2264f904474465415a5085024487f22b017208e250ca4" + "7902d61d98fbd1cb8afc0a14dcd70a68343cf67c258758d9"; ReplayChWithMalformedInner(ch, kTlsAlertProtocolVersion, SSL_ERROR_UNSUPPORTED_VERSION, SSL_ERROR_PROTOCOL_VERSION_ALERT); } -// Replay a CH for which the ECH Inner lacks the required -// empty ECH extension. +// Replay a CH for which CHInner lacks the required ech_is_inner extension. TEST_F(TlsConnectStreamTls13Ech, EchInnerMissingEmptyEch) { std::string ch = - "0100017103032bf866cbd6d4abdec8ce23107eaef9af51b644043953e3b70f2f28f1898e" - "87880000061301130313020100014200000010000e00000b7075626c69632e6e616d65ff" + "010001540303033b3284790ada882445bfb38b8af3509659033c931e6ae97febbaa62b19" + "b4ac0000061301130313020100012500000010000e00000b7075626c69632e6e616d65ff" "01000100000a00140012001d00170018001901000101010201030104003300260024001d" - "00208f614d3017575332ca009a42d33bcaf876b4ba6d44b052e8019c31f6f1559e41002b" + "00209d1ed410ccb05ce9e424f52b1be3599bcc1efb0913ae14a24d9a69cbfbc39744002b" "0003020304000d0018001604030503060302030804080508060401050106010201002d00" - "020101001c000240010015000100fe0800af0001000320a10698ccbd4bd86df91f617e58" - "dd2ca96b8ba5f058dd5c5ab1ca9750ef9d28c70020da1d5d9f183a5d5e49892e38eaae5e" - "9e3e6c5d404a5fdb672ca37f9cebabd57400660ea1d61917cc1049aab22506078ccecfc4" - "16a364a1beaa8915b250bb86ac2c725698c3c641830c4aa4e8b7f50152b5732b29b1ac43" - "45c97fc018855fd68e5600d0ef188e905b69997c3711b0ec0114a857177df728c7b84f52" - "2923f932838f7f15bb22644fd4"; - ReplayChWithMalformedInner(ch, kTlsAlertDecodeError, + "020101001c00024001fe0900970001000308fde4163c5c6e8bb600206321bdc543a23d47" + "7a7104ba69177cb722927c6c485117df4a077b8e82167f0b0066103d9aac7e5fc4ef990b" + "2ce38593589f7f6ba043847d7db6c9136adb811f63b956d56e6ca8cbe6864e3fc43a3bc5" + "94a332d4d63833e411c89ef14af63b5cd18c7adee99ffd1ad3112449ea18d6650bbaca66" + "528f7e4146fafbf338c27cf89b145a55022b26a3"; + ReplayChWithMalformedInner(ch, kTlsAlertIllegalParameter, SSL_ERROR_MISSING_ECH_EXTENSION, - SSL_ERROR_DECODE_ERROR_ALERT); + SSL_ERROR_ILLEGAL_PARAMETER_ALERT); +} + +// Replay a CH for which CHInner contains both an ECH and ech_is_inner +// extension. +TEST_F(TlsConnectStreamTls13Ech, InnerWithEchAndEchIsInner) { + std::string ch = + "0100015c030383fb49c98b62bcdf04cbbae418dd684f8f9512f40fca6861ba40555269a9" + "789f0000061301130313020100012d00000010000e00000b7075626c69632e6e616d65ff" + "01000100000a00140012001d00170018001901000101010201030104003300260024001d" + "00201e3d35a6755b7dddf7e481359429e9677baaa8dd99569c2bf0b0f7ea56e68b12002b" + "0003020304000d0018001604030503060302030804080508060401050106010201002d00" + "020101001c00024001fe09009f0001000308fde4163c5c6e8bb6002090110b89c1ba6618" + "942ea7aae8c472c22e97f10bef7dd490bee50cc108082b48006eed016fa2b3e3419cf5ef" + "9b41ab9ecffa84a4b60e2f4cc710cf31c739d1f6f88b48207aaf7ccabdd744a25a8f2a38" + "029d1b133e9d990681cf08c07a255d9242b3a002bc0865935cbb609b2b1996fab0626cb0" + "2ece6544bbde0d3218333ffd95c383a41854b76b1a254bb346a2702b"; + ReplayChWithMalformedInner(ch, kTlsAlertIllegalParameter, + SSL_ERROR_RX_MALFORMED_CLIENT_HELLO, + SSL_ERROR_ILLEGAL_PARAMETER_ALERT); +} + +TEST_F(TlsConnectStreamTls13, OuterWithEchAndEchIsInner) { + static uint8_t empty_buf[1] = {0}; + DataBuffer empty(empty_buf, 0); + + EnsureTlsSetup(); + EXPECT_EQ(SECSuccess, SSL_EnableTls13GreaseEch(client_->ssl_fd(), PR_TRUE)); + MakeTlsFilter<TlsExtensionAppender>(client_, kTlsHandshakeClientHello, + ssl_tls13_ech_is_inner_xtn, empty); + ConnectExpectAlert(server_, kTlsAlertIllegalParameter); + client_->CheckErrorCode(SSL_ERROR_ILLEGAL_PARAMETER_ALERT); + server_->CheckErrorCode(SSL_ERROR_RX_UNEXPECTED_EXTENSION); +} + +// Apply two ECHConfigs on the server. They are identical with the exception +// of the public key: the first ECHConfig contains a public key for which we +// lack the private value. Use an SSLInt function to zero all the config_ids +// (client and server), then confirm that trial decryption works. +TEST_F(TlsConnectStreamTls13Ech, EchConfigsTrialDecrypt) { + ScopedSECKEYPublicKey pub; + ScopedSECKEYPrivateKey priv; + EnsureTlsSetup(); + ImportFixedEchKeypair(pub, priv); + + const std::string two_configs_str = + "007EFE09003B000B7075626C69632E6E616D650020111111111111111111111111111111" + "1111111111111111111111111111111111002000040001000100640000fe09003B000B70" + "75626C69632E6E616D6500208756E2580C07C1D2FFCB662F5FADC6D6FF13DA85ABD7ADFE" + "CF984AAA102C1269002000040001000100640000"; + const std::string second_config_str = + "003FFE09003B000B7075626C69632E6E616D6500208756E2580C07C1D2FFCB662F5FADC6" + "D6FF13DA85ABD7ADFECF984AAA102C1269002000040001000100640000"; + std::vector<uint8_t> two_configs = hex_string_to_bytes(two_configs_str); + std::vector<uint8_t> second_config = hex_string_to_bytes(second_config_str); + ASSERT_EQ(SECSuccess, + SSL_SetServerEchConfigs(server_->ssl_fd(), pub.get(), priv.get(), + two_configs.data(), two_configs.size())); + ASSERT_EQ(SECSuccess, + SSL_SetClientEchConfigs(client_->ssl_fd(), second_config.data(), + second_config.size())); + + ASSERT_EQ(SECSuccess, SSLInt_ZeroEchConfigIds(client_->ssl_fd())); + ASSERT_EQ(SECSuccess, SSLInt_ZeroEchConfigIds(server_->ssl_fd())); + client_->ExpectEch(); + server_->ExpectEch(); + Connect(); } // An empty config_id should prompt an alert. We don't support @@ -769,16 +849,17 @@ SSLHelloRetryRequestAction RetryEchHello(PRBool firstHello, TEST_F(TlsConnectStreamTls13, EchAcceptWithHrr) { ScopedSECKEYPublicKey pub; ScopedSECKEYPrivateKey priv; - DataBuffer record; + DataBuffer echconfig; ConfigureSelfEncrypt(); EnsureTlsSetup(); TlsConnectTestBase::GenerateEchConfig(HpkeDhKemX25519Sha256, kDefaultSuites, - kPublicName, 100, record, pub, priv); + kPublicName, 100, echconfig, pub, priv); ASSERT_EQ(SECSuccess, SSL_SetServerEchConfigs(server_->ssl_fd(), pub.get(), priv.get(), - record.data(), record.len())); - ASSERT_EQ(SECSuccess, SSL_SetClientEchConfigs(client_->ssl_fd(), - record.data(), record.len())); + echconfig.data(), echconfig.len())); + ASSERT_EQ(SECSuccess, + SSL_SetClientEchConfigs(client_->ssl_fd(), echconfig.data(), + echconfig.len())); client_->ExpectEch(); server_->ExpectEch(); client_->SetAuthCertificateCallback(AuthCompleteSuccess); @@ -795,7 +876,7 @@ TEST_F(TlsConnectStreamTls13, EchAcceptWithHrr) { MakeNewServer(); ASSERT_EQ(SECSuccess, SSL_SetServerEchConfigs(server_->ssl_fd(), pub.get(), priv.get(), - record.data(), record.len())); + echconfig.data(), echconfig.len())); client_->ExpectEch(); server_->ExpectEch(); client_->SetAuthCertificateCallback(AuthCompleteSuccess); @@ -805,6 +886,42 @@ TEST_F(TlsConnectStreamTls13, EchAcceptWithHrr) { SendReceive(); } +// Send GREASE ECH in CH1. CH2 must send exactly the same GREASE ECH contents. +TEST_F(TlsConnectStreamTls13, GreaseEchHrrMatches) { + ConfigureSelfEncrypt(); + EnsureTlsSetup(); + size_t cb_called = 0; + EXPECT_EQ(SECSuccess, SSL_HelloRetryRequestCallback( + server_->ssl_fd(), RetryEchHello, &cb_called)); + + EXPECT_EQ(SECSuccess, SSL_EnableTls13GreaseEch(client_->ssl_fd(), + PR_TRUE)); // GREASE + auto capture = MakeTlsFilter<TlsExtensionCapture>( + client_, ssl_tls13_encrypted_client_hello_xtn); + + // Start the handshake. + client_->StartConnect(); + server_->StartConnect(); + client_->Handshake(); // Send CH1 + EXPECT_TRUE(capture->captured()); + DataBuffer ch1_grease = capture->extension(); + + server_->Handshake(); + MakeNewServer(); + capture = MakeTlsFilter<TlsExtensionCapture>( + client_, ssl_tls13_encrypted_client_hello_xtn); + + EXPECT_FALSE(capture->captured()); + client_->Handshake(); // Send CH2 + EXPECT_TRUE(capture->captured()); + EXPECT_EQ(ch1_grease, capture->extension()); + + EXPECT_EQ(1U, cb_called); + server_->StartConnect(); + Handshake(); + CheckConnected(); +} + // Fail to decrypt CH2. Unlike CH1, this generates an alert. TEST_F(TlsConnectStreamTls13, EchFailDecryptCH2) { EnsureTlsSetup(); @@ -848,9 +965,9 @@ TEST_F(TlsConnectStreamTls13, EchHrrChangeCh2OfferingYN) { MakeNewServer(); EXPECT_EQ(SECSuccess, SSL_EnableTls13GreaseEch(client_->ssl_fd(), PR_FALSE)); // Don't GREASE - ExpectAlert(server_, kTlsAlertIllegalParameter); + ExpectAlert(server_, kTlsAlertMissingExtension); Handshake(); - client_->CheckErrorCode(SSL_ERROR_ILLEGAL_PARAMETER_ALERT); + client_->CheckErrorCode(SSL_ERROR_MISSING_EXTENSION_ALERT); server_->CheckErrorCode(SSL_ERROR_BAD_2ND_CLIENT_HELLO); EXPECT_EQ(1U, cb_called); } @@ -858,20 +975,47 @@ TEST_F(TlsConnectStreamTls13, EchHrrChangeCh2OfferingYN) { TEST_F(TlsConnectStreamTls13, EchHrrChangeCh2OfferingNY) { ConfigureSelfEncrypt(); EnsureTlsSetup(); + SetupEch(client_, server_); size_t cb_called = 0; EXPECT_EQ(SECSuccess, SSL_HelloRetryRequestCallback( server_->ssl_fd(), RetryEchHello, &cb_called)); - EXPECT_EQ(SECSuccess, SSL_EnableTls13GreaseEch(client_->ssl_fd(), - PR_FALSE)); // Don't GREASE + MakeTlsFilter<TlsExtensionDropper>(client_, + ssl_tls13_encrypted_client_hello_xtn); // Start the handshake. client_->StartConnect(); server_->StartConnect(); client_->Handshake(); server_->Handshake(); MakeNewServer(); - EXPECT_EQ(SECSuccess, SSL_EnableTls13GreaseEch(client_->ssl_fd(), - PR_TRUE)); // Send GREASE + client_->ClearFilter(); // Let the second ECH offering through. + ExpectAlert(server_, kTlsAlertIllegalParameter); + Handshake(); + client_->CheckErrorCode(SSL_ERROR_ILLEGAL_PARAMETER_ALERT); + server_->CheckErrorCode(SSL_ERROR_BAD_2ND_CLIENT_HELLO); + EXPECT_EQ(1U, cb_called); +} + +// Change the ECHCipherSuite between CH1 and CH2. Expect alert. +TEST_F(TlsConnectStreamTls13, EchHrrChangeCipherSuite) { + ConfigureSelfEncrypt(); + EnsureTlsSetup(); + SetupEch(client_, server_); + + size_t cb_called = 0; + EXPECT_EQ(SECSuccess, SSL_HelloRetryRequestCallback( + server_->ssl_fd(), RetryEchHello, &cb_called)); + // Start the handshake and trigger HRR. + client_->StartConnect(); + server_->StartConnect(); + client_->Handshake(); + server_->Handshake(); + MakeNewServer(); + + // Damage the first byte of the ciphersuite (offset 0) + MakeTlsFilter<TlsExtensionDamager>(client_, + ssl_tls13_encrypted_client_hello_xtn, 0); + ExpectAlert(server_, kTlsAlertIllegalParameter); Handshake(); client_->CheckErrorCode(SSL_ERROR_ILLEGAL_PARAMETER_ALERT); @@ -884,17 +1028,18 @@ TEST_F(TlsConnectStreamTls13, EchHrrChangeCh2OfferingNY) { TEST_F(TlsConnectStreamTls13, EchAcceptWithHrrAndPsk) { ScopedSECKEYPublicKey pub; ScopedSECKEYPrivateKey priv; - DataBuffer record; + DataBuffer echconfig; ConfigureSelfEncrypt(); EnsureTlsSetup(); TlsConnectTestBase::GenerateEchConfig(HpkeDhKemX25519Sha256, kDefaultSuites, - kPublicName, 100, record, pub, priv); + kPublicName, 100, echconfig, pub, priv); ASSERT_EQ(SECSuccess, SSL_SetServerEchConfigs(server_->ssl_fd(), pub.get(), priv.get(), - record.data(), record.len())); - ASSERT_EQ(SECSuccess, SSL_SetClientEchConfigs(client_->ssl_fd(), - record.data(), record.len())); + echconfig.data(), echconfig.len())); + ASSERT_EQ(SECSuccess, + SSL_SetClientEchConfigs(client_->ssl_fd(), echconfig.data(), + echconfig.len())); client_->ExpectEch(); server_->ExpectEch(); @@ -922,7 +1067,7 @@ TEST_F(TlsConnectStreamTls13, EchAcceptWithHrrAndPsk) { MakeNewServer(); ASSERT_EQ(SECSuccess, SSL_SetServerEchConfigs(server_->ssl_fd(), pub.get(), priv.get(), - record.data(), record.len())); + echconfig.data(), echconfig.len())); client_->ExpectEch(); server_->ExpectEch(); EXPECT_EQ(SECSuccess, @@ -940,7 +1085,7 @@ TEST_F(TlsConnectStreamTls13, EchAcceptWithHrrAndPsk) { TEST_F(TlsConnectStreamTls13Ech, EchRejectWithHrr) { ScopedSECKEYPublicKey pub; ScopedSECKEYPrivateKey priv; - DataBuffer record; + DataBuffer echconfig; ConfigureSelfEncrypt(); EnsureTlsSetup(); SetupForEchRetry(); @@ -966,19 +1111,20 @@ TEST_F(TlsConnectStreamTls13Ech, EchRejectWithHrr) { EXPECT_EQ(1U, cb_called); } -// Reject ECH on CH1 and (HRR) CH2. PSKs are no longer allowed -// in CHOuter, but can still make sure the handshake succeeds. -// (prompting ech_required at the completion). +// Reject ECH on CH1 and CH2. PSKs are no longer allowed +// in CHOuter, but we can still make sure the handshake succeeds. +// This prompts an ech_required alert when the handshake completes. TEST_F(TlsConnectStreamTls13, EchRejectWithHrrAndPsk) { ScopedSECKEYPublicKey pub; ScopedSECKEYPrivateKey priv; - DataBuffer record; + DataBuffer echconfig; ConfigureSelfEncrypt(); EnsureTlsSetup(); TlsConnectTestBase::GenerateEchConfig(HpkeDhKemX25519Sha256, kDefaultSuites, - kPublicName, 100, record, pub, priv); - ASSERT_EQ(SECSuccess, SSL_SetClientEchConfigs(client_->ssl_fd(), - record.data(), record.len())); + kPublicName, 100, echconfig, pub, priv); + ASSERT_EQ(SECSuccess, + SSL_SetClientEchConfigs(client_->ssl_fd(), echconfig.data(), + echconfig.len())); size_t cb_called = 0; EXPECT_EQ(SECSuccess, SSL_HelloRetryRequestCallback( @@ -1113,7 +1259,7 @@ TEST_F(TlsConnectStreamTls13, EchRejectUnknownCriticalExtension) { EnsureTlsSetup(); ScopedSECKEYPublicKey pub; ScopedSECKEYPrivateKey priv; - DataBuffer record; + DataBuffer echconfig; DataBuffer crit_rec; DataBuffer len_buf; uint64_t tmp; @@ -1124,9 +1270,9 @@ TEST_F(TlsConnectStreamTls13, EchRejectUnknownCriticalExtension) { DataBuffer non_crit_exts(extensions, sizeof(extensions)); TlsConnectTestBase::GenerateEchConfig(HpkeDhKemX25519Sha256, kSuiteChaCha, - kPublicName, 100, record, pub, priv); - record.Truncate(record.len() - 2); // Eat the empty extensions. - crit_rec.Assign(record); + kPublicName, 100, echconfig, pub, priv); + echconfig.Truncate(echconfig.len() - 2); // Eat the empty extensions. + crit_rec.Assign(echconfig); ASSERT_TRUE(crit_rec.Read(0, 2, &tmp)); len_buf.Write(0, tmp + crit_exts.len() - 2, 2); // two bytes of length crit_rec.Splice(len_buf, 0, 2); @@ -1138,13 +1284,13 @@ TEST_F(TlsConnectStreamTls13, EchRejectUnknownCriticalExtension) { crit_rec.Splice(len_buf, 4, 2); len_buf.Truncate(0); - ASSERT_TRUE(record.Read(0, 2, &tmp)); + ASSERT_TRUE(echconfig.Read(0, 2, &tmp)); len_buf.Write(0, tmp + non_crit_exts.len() - 2, 2); - record.Append(non_crit_exts); - record.Splice(len_buf, 0, 2); - ASSERT_TRUE(record.Read(4, 2, &tmp)); + echconfig.Append(non_crit_exts); + echconfig.Splice(len_buf, 0, 2); + ASSERT_TRUE(echconfig.Read(4, 2, &tmp)); len_buf.Write(0, tmp + non_crit_exts.len() - 2, 2); - record.Splice(len_buf, 4, 2); + echconfig.Splice(len_buf, 4, 2); EXPECT_EQ(SECFailure, SSL_SetClientEchConfigs(client_->ssl_fd(), crit_rec.data(), @@ -1162,8 +1308,9 @@ TEST_F(TlsConnectStreamTls13, EchRejectUnknownCriticalExtension) { // Now try a variant with non-critical extensions, it should work. Reset(); EnsureTlsSetup(); - EXPECT_EQ(SECSuccess, SSL_SetClientEchConfigs(client_->ssl_fd(), - record.data(), record.len())); + EXPECT_EQ(SECSuccess, + SSL_SetClientEchConfigs(client_->ssl_fd(), echconfig.data(), + echconfig.len())); filter = MakeTlsFilter<TlsExtensionCapture>( client_, ssl_tls13_encrypted_client_hello_xtn); StartConnect(); @@ -1577,6 +1724,34 @@ TEST_F(TlsConnectStreamTls13, EchOuterExtensionsInCHOuter) { server_->CheckErrorCode(SSL_ERROR_RX_MALFORMED_CLIENT_HELLO); } +// At draft-09: If a CH containing the ech_is_inner extension is received, the +// server acts as backend server in split-mode by responding with the ECH +// acceptance signal. The signal value itself depends on the handshake secret, +// which we've broken by appending ech_is_inner. For now, just check that the +// server negotiates ech_is_inner (which is what triggers sending the signal). +TEST_F(TlsConnectStreamTls13, EchBackendAcceptance) { + DataBuffer ch_buf; + static uint8_t empty_buf[1] = {0}; + DataBuffer empty(empty_buf, 0); + + EnsureTlsSetup(); + StartConnect(); + EXPECT_EQ(SECSuccess, SSL_EnableTls13GreaseEch(client_->ssl_fd(), PR_FALSE)); + MakeTlsFilter<TlsExtensionAppender>(client_, kTlsHandshakeClientHello, + ssl_tls13_ech_is_inner_xtn, empty); + + EXPECT_EQ(SECSuccess, SSL_EnableTls13BackendEch(server_->ssl_fd(), PR_TRUE)); + client_->Handshake(); + server_->Handshake(); + + ExpectAlert(client_, kTlsAlertBadRecordMac); + client_->Handshake(); + EXPECT_EQ(TlsAgent::STATE_ERROR, client_->state()); + EXPECT_EQ(PR_TRUE, SSLInt_ExtensionNegotiated(server_->ssl_fd(), + ssl_tls13_ech_is_inner_xtn)); + server_->ExpectReceiveAlert(kTlsAlertCloseNotify, kTlsAlertWarning); +} + INSTANTIATE_TEST_SUITE_P(EchAgentTest, TlsAgentEchTest, ::testing::Combine(TlsConnectTestBase::kTlsVariantsAll, TlsConnectTestBase::kTlsV13)); 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 |