diff --git a/automation/abi-check/expected-report-libssl3.so.txt b/automation/abi-check/expected-report-libssl3.so.txt index e69de29bb2..5d9c15b2de 100644 --- a/automation/abi-check/expected-report-libssl3.so.txt +++ b/automation/abi-check/expected-report-libssl3.so.txt @@ -0,0 +1,19 @@ + +2 functions with some indirect sub-type change: + + [C] 'function SECStatus SSL_GetChannelInfo(PRFileDesc*, SSLChannelInfo*, PRUintn)' at sslinfo.c:14:1 has some indirect sub-type changes: + parameter 2 of type 'SSLChannelInfo*' has sub-type changes: + in pointed to type 'typedef SSLChannelInfo' at sslt.h:378:1: + underlying type 'struct SSLChannelInfoStr' at sslt.h:299:1 changed: + type size hasn't changed + 1 data member insertion: + 'PRBool SSLChannelInfoStr::echAccepted', at offset 992 (in bits) at sslt.h:374:1 + + [C] 'function SECStatus SSL_GetPreliminaryChannelInfo(PRFileDesc*, SSLPreliminaryChannelInfo*, PRUintn)' at sslinfo.c:122:1 has some indirect sub-type changes: + parameter 2 of type 'SSLPreliminaryChannelInfo*' has sub-type changes: + in pointed to type 'typedef SSLPreliminaryChannelInfo' at sslt.h:446:1: + underlying type 'struct SSLPreliminaryChannelInfoStr' at sslt.h:386:1 changed: + type size changed from 288 to 384 (in bits) + 2 data member insertions: + 'PRBool SSLPreliminaryChannelInfoStr::echAccepted', at offset 288 (in bits) at sslt.h:439:1 + 'const char* SSLPreliminaryChannelInfoStr::echPublicName', at offset 320 (in bits) at sslt.h:442:1 diff --git a/cmd/tstclnt/tstclnt.c b/cmd/tstclnt/tstclnt.c index c37df118eb..5207bc0629 100644 --- a/cmd/tstclnt/tstclnt.c +++ b/cmd/tstclnt/tstclnt.c @@ -231,7 +231,7 @@ PrintUsageHeader() " [-r N] [-w passwd] [-W pwfile] [-q [-t seconds]]\n" " [-I groups] [-J signatureschemes]\n" " [-A requestfile] [-L totalconnections] [-P {client,server}]\n" - " [-N encryptedSniKeys] [-Q] [-z externalPsk]\n" + " [-N echConfigs] [-Q] [-z externalPsk]\n" "\n", progName); } @@ -316,7 +316,7 @@ PrintParameterUsage() fprintf(stderr, "%-20s Enable alternative TLS 1.3 handshake\n", "-X alt-server-hello"); fprintf(stderr, "%-20s Use DTLS\n", "-P {client, server}"); fprintf(stderr, "%-20s Exit after handshake\n", "-Q"); - fprintf(stderr, "%-20s Encrypted SNI Keys\n", "-N"); + fprintf(stderr, "%-20s Use Encrypted Client Hello with the given Base64-encoded ECHConfigs\n", "-N"); fprintf(stderr, "%-20s Enable post-handshake authentication\n" "%-20s for TLS 1.3; need to specify -n\n", "-E", ""); @@ -1010,7 +1010,7 @@ PRBool stopAfterHandshake = PR_FALSE; PRBool requestToExit = PR_FALSE; char *versionString = NULL; PRBool handshakeComplete = PR_FALSE; -char *encryptedSNIKeys = NULL; +char *echConfigs = NULL; PRBool enablePostHandshakeAuth = PR_FALSE; PRBool enableDelegatedCredentials = PR_FALSE; const secuExporter *enabledExporters = NULL; @@ -1263,6 +1263,30 @@ importPsk(PRFileDesc *s) return rv; } +static SECStatus +printEchRetryConfigs(PRFileDesc *s) +{ + if (PORT_GetError() == SSL_ERROR_ECH_RETRY_WITH_ECH) { + SECItem retries = { siBuffer, NULL, 0 }; + SECStatus rv = SSL_GetEchRetryConfigs(s, &retries); + if (rv != SECSuccess) { + SECU_PrintError(progName, "SSL_GetEchRetryConfigs failed"); + return SECFailure; + } + char *retriesBase64 = NSSBase64_EncodeItem(NULL, NULL, 0, &retries); + if (!retriesBase64) { + SECU_PrintError(progName, "NSSBase64_EncodeItem on retry_configs failed"); + SECITEM_FreeItem(&retries, PR_FALSE); + return SECFailure; + } + + fprintf(stderr, "Received ECH retry_configs: \n%s\n", retriesBase64); + PORT_Free(retriesBase64); + SECITEM_FreeItem(&retries, PR_FALSE); + } + return SECSuccess; +} + static int run() { @@ -1511,21 +1535,20 @@ run() } } - if (encryptedSNIKeys) { - SECItem esniKeysBin = { siBuffer, NULL, 0 }; + if (echConfigs) { + SECItem echConfigsBin = { siBuffer, NULL, 0 }; - if (!NSSBase64_DecodeBuffer(NULL, &esniKeysBin, encryptedSNIKeys, - strlen(encryptedSNIKeys))) { - SECU_PrintError(progName, "ESNIKeys record is invalid base64"); + if (!NSSBase64_DecodeBuffer(NULL, &echConfigsBin, echConfigs, + strlen(echConfigs))) { + SECU_PrintError(progName, "ECHConfigs record is invalid base64"); error = 1; goto done; } - rv = SSL_EnableESNI(s, esniKeysBin.data, esniKeysBin.len, - "dummy.invalid"); - SECITEM_FreeItem(&esniKeysBin, PR_FALSE); + rv = SSL_SetClientEchConfigs(s, echConfigsBin.data, echConfigsBin.len); + SECITEM_FreeItem(&echConfigsBin, PR_FALSE); if (rv < 0) { - SECU_PrintError(progName, "SSL_EnableESNI failed"); + SECU_PrintError(progName, "SSL_SetClientEchConfigs failed"); error = 1; goto done; } @@ -1702,6 +1725,9 @@ run() } else { error = writeBytesToServer(s, buf, nb); if (error) { + if (echConfigs) { + (void)printEchRetryConfigs(s); + } goto done; } pollset[SSOCK_FD].in_flags = PR_POLL_READ; @@ -1881,7 +1907,7 @@ main(int argc, char **argv) break; case 'N': - encryptedSNIKeys = PORT_Strdup(optstate->value); + echConfigs = PORT_Strdup(optstate->value); break; case 'P': @@ -2257,7 +2283,7 @@ main(int argc, char **argv) PORT_Free(pwdata.data); PORT_Free(host); PORT_Free(zeroRttData); - PORT_Free(encryptedSNIKeys); + PORT_Free(echConfigs); SECITEM_ZfreeItem(&psk, PR_FALSE); SECITEM_ZfreeItem(&pskLabel, PR_FALSE); diff --git a/cpputil/tls_parser.h b/cpputil/tls_parser.h index 6636b3c6a7..41d760ed00 100644 --- a/cpputil/tls_parser.h +++ b/cpputil/tls_parser.h @@ -56,6 +56,7 @@ const uint8_t kTlsAlertUnsupportedExtension = 110; const uint8_t kTlsAlertUnrecognizedName = 112; const uint8_t kTlsAlertCertificateRequired = 116; const uint8_t kTlsAlertNoApplicationProtocol = 120; +const uint8_t kTlsAlertEchRequired = 121; const uint8_t kTlsFakeChangeCipherSpec[] = { ssl_ct_change_cipher_spec, // Type diff --git a/gtests/ssl_gtest/libssl_internals.c b/gtests/ssl_gtest/libssl_internals.c index 854eca07f1..01d698e717 100644 --- a/gtests/ssl_gtest/libssl_internals.c +++ b/gtests/ssl_gtest/libssl_internals.c @@ -8,8 +8,10 @@ #include "libssl_internals.h" #include "nss.h" +#include "pk11hpke.h" #include "pk11pub.h" #include "pk11priv.h" +#include "tls13ech.h" #include "seccomon.h" #include "selfencrypt.h" #include "secmodti.h" @@ -481,3 +483,17 @@ SECStatus SSLInt_HasPendingHandshakeData(PRFileDesc *fd, PRBool *pending) { ssl_ReleaseSSL3HandshakeLock(ss); return SECSuccess; } + +SECStatus SSLInt_SetRawEchConfigForRetry(PRFileDesc *fd, const uint8_t *buf, + size_t len) { + sslSocket *ss = ssl_FindSocket(fd); + if (!ss) { + return SECFailure; + } + + 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); + return SECSuccess; +} diff --git a/gtests/ssl_gtest/libssl_internals.h b/gtests/ssl_gtest/libssl_internals.h index 4b076c2eed..a6a3239d59 100644 --- a/gtests/ssl_gtest/libssl_internals.h +++ b/gtests/ssl_gtest/libssl_internals.h @@ -49,5 +49,7 @@ SECStatus SSLInt_SetDCAdvertisedSigSchemes(PRFileDesc *fd, const SSLSignatureScheme *schemes, uint32_t num_sig_schemes); SECStatus SSLInt_RemoveServerCertificates(PRFileDesc *fd); +SECStatus SSLInt_SetRawEchConfigForRetry(PRFileDesc *fd, const uint8_t *buf, + size_t len); #endif // ndef libssl_internals_h_ diff --git a/gtests/ssl_gtest/manifest.mn b/gtests/ssl_gtest/manifest.mn index 2cfa7cdd25..af3081e8e4 100644 --- a/gtests/ssl_gtest/manifest.mn +++ b/gtests/ssl_gtest/manifest.mn @@ -58,7 +58,7 @@ CPPSRCS = \ tls_protect.cc \ tls_psk_unittest.cc \ tls_subcerts_unittest.cc \ - tls_esni_unittest.cc \ + tls_ech_unittest.cc \ $(SSLKEYLOGFILE_FILES) \ $(NULL) diff --git a/gtests/ssl_gtest/ssl_auth_unittest.cc b/gtests/ssl_gtest/ssl_auth_unittest.cc index 5c6eee7b62..2daed68ccc 100644 --- a/gtests/ssl_gtest/ssl_auth_unittest.cc +++ b/gtests/ssl_gtest/ssl_auth_unittest.cc @@ -660,6 +660,18 @@ TEST_P(TlsConnectGeneric, ClientAuthEcdsa) { CheckKeys(ssl_kea_ecdh, ssl_auth_ecdsa); } +#ifdef NSS_ENABLE_DRAFT_HPKE +TEST_P(TlsConnectGeneric, ClientAuthWithEch) { + Reset(TlsAgent::kServerEcdsa256); + EnsureTlsSetup(); + SetupEch(client_, server_); + client_->SetupClientAuth(); + server_->RequestClientAuth(true); + Connect(); + CheckKeys(ssl_kea_ecdh, ssl_auth_ecdsa); +} +#endif + TEST_P(TlsConnectGeneric, ClientAuthBigRsa) { Reset(TlsAgent::kServerRsa, TlsAgent::kRsa2048); client_->SetupClientAuth(); diff --git a/gtests/ssl_gtest/ssl_custext_unittest.cc b/gtests/ssl_gtest/ssl_custext_unittest.cc index 68c789a383..bb322430cb 100644 --- a/gtests/ssl_gtest/ssl_custext_unittest.cc +++ b/gtests/ssl_gtest/ssl_custext_unittest.cc @@ -67,8 +67,8 @@ static const uint16_t kManyExtensions[] = { ssl_tls13_certificate_authorities_xtn, ssl_next_proto_nego_xtn, ssl_renegotiation_info_xtn, - ssl_tls13_short_header_xtn, ssl_record_size_limit_xtn, + ssl_tls13_encrypted_client_hello_xtn, 1, 0xffff}; // The list here includes all extensions we expect to use (SSL_MAX_EXTENSIONS), diff --git a/gtests/ssl_gtest/ssl_extension_unittest.cc b/gtests/ssl_gtest/ssl_extension_unittest.cc index fb995953fb..019b6ea1d8 100644 --- a/gtests/ssl_gtest/ssl_extension_unittest.cc +++ b/gtests/ssl_gtest/ssl_extension_unittest.cc @@ -83,63 +83,6 @@ class TlsExtensionTruncator : public TlsExtensionFilter { size_t length_; }; -class TlsExtensionAppender : public TlsHandshakeFilter { - public: - TlsExtensionAppender(const std::shared_ptr& a, - uint8_t handshake_type, uint16_t ext, DataBuffer& data) - : TlsHandshakeFilter(a, {handshake_type}), extension_(ext), data_(data) {} - - virtual PacketFilter::Action FilterHandshake(const HandshakeHeader& header, - const DataBuffer& input, - DataBuffer* output) { - TlsParser parser(input); - if (!TlsExtensionFilter::FindExtensions(&parser, header)) { - return KEEP; - } - *output = input; - - // Increase the length of the extensions block. - if (!UpdateLength(output, parser.consumed(), 2)) { - return KEEP; - } - - // Extensions in Certificate are nested twice. Increase the size of the - // certificate list. - if (header.handshake_type() == kTlsHandshakeCertificate) { - TlsParser p2(input); - if (!p2.SkipVariable(1)) { - ADD_FAILURE(); - return KEEP; - } - if (!UpdateLength(output, p2.consumed(), 3)) { - return KEEP; - } - } - - size_t offset = output->len(); - offset = output->Write(offset, extension_, 2); - WriteVariable(output, offset, data_, 2); - - return CHANGE; - } - - private: - bool UpdateLength(DataBuffer* output, size_t offset, size_t size) { - uint32_t len; - if (!output->Read(offset, size, &len)) { - ADD_FAILURE(); - return false; - } - - len += 4 + data_.len(); - output->Write(offset, len, size); - return true; - } - - const uint16_t extension_; - const DataBuffer data_; -}; - class TlsExtensionTestBase : public TlsConnectTestBase { protected: TlsExtensionTestBase(SSLProtocolVariant variant, uint16_t version) @@ -1155,6 +1098,22 @@ 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. + return; + } + + 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)); @@ -1289,6 +1248,7 @@ TEST_P(TlsBogusExtensionTest13, AddBogusExtensionNewSessionTicket) { TEST_P(TlsConnectStream, IncludePadding) { EnsureTlsSetup(); + SSL_EnableTls13GreaseEch(client_->ssl_fd(), PR_FALSE); // Don't GREASE // This needs to be long enough to push a TLS 1.0 ClientHello over 255, but // short enough not to push a TLS 1.3 ClientHello over 511. diff --git a/gtests/ssl_gtest/ssl_gtest.gyp b/gtests/ssl_gtest/ssl_gtest.gyp index 5491a07250..f21f5fe801 100644 --- a/gtests/ssl_gtest/ssl_gtest.gyp +++ b/gtests/ssl_gtest/ssl_gtest.gyp @@ -55,7 +55,7 @@ 'tls_connect.cc', 'tls_filter.cc', 'tls_hkdf_unittest.cc', - 'tls_esni_unittest.cc', + 'tls_ech_unittest.cc', 'tls_protect.cc', 'tls_psk_unittest.cc', 'tls_subcerts_unittest.cc' diff --git a/gtests/ssl_gtest/ssl_tls13compat_unittest.cc b/gtests/ssl_gtest/ssl_tls13compat_unittest.cc index 645f84ff02..f65552cb1c 100644 --- a/gtests/ssl_gtest/ssl_tls13compat_unittest.cc +++ b/gtests/ssl_gtest/ssl_tls13compat_unittest.cc @@ -214,6 +214,31 @@ TEST_F(Tls13CompatTest, EnabledHrrZeroRtt) { CheckForCompatHandshake(); } +#ifdef NSS_ENABLE_DRAFT_HPKE +TEST_F(Tls13CompatTest, EnabledAcceptedEch) { + EnsureTlsSetup(); + SetupEch(client_, server_); + EnableCompatMode(); + InstallFilters(); + Connect(); + CheckForCompatHandshake(); +} + +TEST_F(Tls13CompatTest, EnabledRejectedEch) { + EnsureTlsSetup(); + // Configure ECH on the client only, and expect CCS. + SetupEch(client_, server_, HpkeDhKemX25519Sha256, false, true, false); + EnableCompatMode(); + InstallFilters(); + ExpectAlert(client_, kTlsAlertEchRequired); + ConnectExpectFailOneSide(TlsAgent::CLIENT); + client_->CheckErrorCode(SSL_ERROR_ECH_RETRY_WITHOUT_ECH); + CheckForCompatHandshake(); + // Reset expectations for the TlsAgent dtor. + server_->ExpectReceiveAlert(kTlsAlertCloseNotify, kTlsAlertWarning); +} +#endif + class TlsSessionIDEchoFilter : public TlsHandshakeFilter { public: TlsSessionIDEchoFilter(const std::shared_ptr& a) diff --git a/gtests/ssl_gtest/tls_agent.cc b/gtests/ssl_gtest/tls_agent.cc index 2eafc5bcb7..43a953bec4 100644 --- a/gtests/ssl_gtest/tls_agent.cc +++ b/gtests/ssl_gtest/tls_agent.cc @@ -74,6 +74,7 @@ TlsAgent::TlsAgent(const std::string& nm, Role rl, SSLProtocolVariant var) expected_version_(0), expected_cipher_suite_(0), expect_client_auth_(false), + expect_ech_(false), expect_psk_(ssl_psk_none), can_falsestart_hook_called_(false), sni_hook_called_(false), @@ -687,7 +688,9 @@ void TlsAgent::EnableFalseStart() { SetOption(SSL_ENABLE_FALSE_START, PR_TRUE); } -void TlsAgent::ExpectPsk() { expect_psk_ = ssl_psk_external; } +void TlsAgent::ExpectEch(bool expected) { expect_ech_ = expected; } + +void TlsAgent::ExpectPsk(SSLPskType psk) { expect_psk_ = psk; } void TlsAgent::ExpectResumption() { expect_psk_ = ssl_psk_resume; } @@ -820,7 +823,6 @@ void TlsAgent::CheckPreliminaryInfo() { SSL_GetPreliminaryChannelInfo(ssl_fd(), &preinfo, sizeof(preinfo))); EXPECT_EQ(sizeof(preinfo), preinfo.length); EXPECT_TRUE(preinfo.valuesSet & ssl_preinfo_version); - EXPECT_TRUE(preinfo.valuesSet & ssl_preinfo_cipher_suite); // A version of 0 is invalid and indicates no expectation. This value is // initialized to 0 so that tests that don't explicitly set an expected @@ -932,6 +934,7 @@ void TlsAgent::Connected() { EXPECT_EQ(expect_psk_ == ssl_psk_resume, info_.resumed == PR_TRUE); EXPECT_EQ(expect_psk_, info_.pskType); + EXPECT_EQ(expect_ech_, info_.echAccepted); // Preliminary values are exposed through callbacks during the handshake. // If either expected values were set or the callbacks were called, check diff --git a/gtests/ssl_gtest/tls_agent.h b/gtests/ssl_gtest/tls_agent.h index f9bb26aee5..05470ba3d8 100644 --- a/gtests/ssl_gtest/tls_agent.h +++ b/gtests/ssl_gtest/tls_agent.h @@ -158,7 +158,9 @@ class TlsAgent : public PollTarget { void SetServerKeyBits(uint16_t bits); void ExpectReadWriteError(); void EnableFalseStart(); - void ExpectPsk(); + void ExpectEch(bool expected = true); + bool GetEchExpected() const { return expect_ech_; } + void ExpectPsk(SSLPskType psk = ssl_psk_external); void ExpectResumption(); void SkipVersionChecks(); void SetSignatureSchemes(const SSLSignatureScheme* schemes, size_t count); @@ -186,6 +188,7 @@ class TlsAgent : public PollTarget { void EnableExtendedMasterSecret(); void CheckExtendedMasterSecret(bool expected); void CheckEarlyDataAccepted(bool expected); + void CheckEchAccepted(bool expected); void SetDowngradeCheckVersion(uint16_t version); void CheckSecretsDestroyed(); void ConfigNamedGroups(const std::vector& groups); @@ -426,6 +429,7 @@ class TlsAgent : public PollTarget { uint16_t expected_version_; uint16_t expected_cipher_suite_; bool expect_client_auth_; + bool expect_ech_; SSLPskType expect_psk_; bool can_falsestart_hook_called_; bool sni_hook_called_; diff --git a/gtests/ssl_gtest/tls_connect.cc b/gtests/ssl_gtest/tls_connect.cc index 9b7f9b6d8c..6456bff563 100644 --- a/gtests/ssl_gtest/tls_connect.cc +++ b/gtests/ssl_gtest/tls_connect.cc @@ -248,6 +248,91 @@ void TlsConnectTestBase::ResetAntiReplay(PRTime window) { anti_replay_.reset(p_anti_replay); } +void TlsConnectTestBase::MakeEcKeyParams(SECItem* params, SSLNamedGroup group) { + auto groupDef = ssl_LookupNamedGroup(group); + ASSERT_NE(nullptr, groupDef); + + auto oidData = SECOID_FindOIDByTag(groupDef->oidTag); + ASSERT_NE(nullptr, oidData); + ASSERT_NE(nullptr, + SECITEM_AllocItem(nullptr, params, (2 + oidData->oid.len))); + params->data[0] = SEC_ASN1_OBJECT_ID; + params->data[1] = oidData->oid.len; + memcpy(params->data + 2, oidData->oid.data, oidData->oid.len); +} + +void TlsConnectTestBase::GenerateEchConfig( + HpkeKemId kem_id, const std::vector& cipher_suites, + const std::string& public_name, uint16_t max_name_len, DataBuffer& record, + ScopedSECKEYPublicKey& pubKey, ScopedSECKEYPrivateKey& privKey) { + bool gen_keys = !pubKey && !privKey; + SECKEYECParams ecParams = {siBuffer, NULL, 0}; + MakeEcKeyParams(&ecParams, ssl_grp_ec_curve25519); + + SECKEYPublicKey* pub = nullptr; + SECKEYPrivateKey* priv = nullptr; + + if (gen_keys) { + priv = SECKEY_CreateECPrivateKey(&ecParams, &pub, nullptr); + } else { + priv = privKey.get(); + pub = pubKey.get(); + } + ASSERT_NE(nullptr, priv); + SECITEM_FreeItem(&ecParams, PR_FALSE); + PRUint8 encoded[1024]; + unsigned int encoded_len = 0; + SECStatus rv = SSL_EncodeEchConfig( + public_name.c_str(), cipher_suites.data(), cipher_suites.size(), kem_id, + pub, max_name_len, encoded, &encoded_len, sizeof(encoded)); + EXPECT_EQ(SECSuccess, rv); + EXPECT_GT(encoded_len, 0U); + + if (gen_keys) { + pubKey.reset(pub); + privKey.reset(priv); + } + record.Truncate(0); + record.Write(0, encoded, encoded_len); +} + +void TlsConnectTestBase::SetupEch(std::shared_ptr& client, + std::shared_ptr& server, + HpkeKemId kem_id, bool expect_ech, + bool set_client_config, + bool set_server_config) { + EXPECT_TRUE(set_server_config || set_client_config); + ScopedSECKEYPublicKey pub; + ScopedSECKEYPrivateKey priv; + DataBuffer record; + static const std::vector kDefaultSuites = { + (static_cast(HpkeKdfHkdfSha256) << 16) | + HpkeAeadChaCha20Poly1305, + (static_cast(HpkeKdfHkdfSha256) << 16) | HpkeAeadAes128Gcm}; + + GenerateEchConfig(kem_id, kDefaultSuites, "public.name", 100, record, pub, + priv); + ASSERT_NE(0U, record.len()); + SECStatus rv; + if (set_server_config) { + rv = SSL_SetServerEchConfigs(server->ssl_fd(), pub.get(), priv.get(), + record.data(), record.len()); + ASSERT_EQ(SECSuccess, rv); + } + if (set_client_config) { + rv = SSL_SetClientEchConfigs(client->ssl_fd(), record.data(), record.len()); + ASSERT_EQ(SECSuccess, rv); + } + + /* Filter expect_ech, which typically defaults to true. Parameterized tests + * running DTLS or TLS < 1.3 should expect only a non-ECH result. */ + bool expect = expect_ech && variant_ != ssl_variant_datagram && + version_ >= SSL_LIBRARY_VERSION_TLS_1_3 && set_client_config && + set_server_config; + client->ExpectEch(expect); + server->ExpectEch(expect); +} + void TlsConnectTestBase::Reset() { // Take a copy of the names because they are about to disappear. std::string server_name = server_->name(); diff --git a/gtests/ssl_gtest/tls_connect.h b/gtests/ssl_gtest/tls_connect.h index 3a43d6bca1..6acb809771 100644 --- a/gtests/ssl_gtest/tls_connect.h +++ b/gtests/ssl_gtest/tls_connect.h @@ -146,6 +146,19 @@ class TlsConnectTestBase : public ::testing::Test { void SaveAlgorithmPolicy(); void RestoreAlgorithmPolicy(); + static void MakeEcKeyParams(SECItem* params, SSLNamedGroup group); + static void GenerateEchConfig(HpkeKemId kem_id, + const std::vector& cipher_suites, + const std::string& public_name, + uint16_t max_name_len, DataBuffer& record, + ScopedSECKEYPublicKey& pubKey, + ScopedSECKEYPrivateKey& privKey); + void SetupEch(std::shared_ptr& client, + std::shared_ptr& server, + HpkeKemId kem_id = HpkeDhKemX25519Sha256, + bool expect_ech = true, bool set_client_config = true, + bool set_server_config = true); + protected: SSLProtocolVariant variant_; std::shared_ptr client_; diff --git a/gtests/ssl_gtest/tls_ech_unittest.cc b/gtests/ssl_gtest/tls_ech_unittest.cc new file mode 100644 index 0000000000..473804323c --- /dev/null +++ b/gtests/ssl_gtest/tls_ech_unittest.cc @@ -0,0 +1,1604 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// TODO: Add padding/maxNameLen tests after support is added in bug 1677181. + +#include "secerr.h" +#include "ssl.h" + +#include "gtest_utils.h" +#include "pk11pub.h" +#include "tls_agent.h" +#include "tls_connect.h" +#include "util.h" + +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()); + if (err == 0) { + ASSERT_EQ(SECSuccess, rv); + } else { + ASSERT_EQ(SECFailure, rv); + ASSERT_EQ(err, PORT_GetError()); + } + } +}; + +#ifdef NSS_ENABLE_DRAFT_HPKE +#include "cpputil.h" // Unused function error if included without HPKE. + +static std::string kPublicName("public.name"); + +static const std::vector kDefaultSuites = { + (static_cast(HpkeKdfHkdfSha256) << 16) | HpkeAeadChaCha20Poly1305, + (static_cast(HpkeKdfHkdfSha256) << 16) | HpkeAeadAes128Gcm}; +static const std::vector kSuiteChaCha = { + (static_cast(HpkeKdfHkdfSha256) << 16) | + HpkeAeadChaCha20Poly1305}; +static const std::vector kSuiteAes = { + (static_cast(HpkeKdfHkdfSha256) << 16) | HpkeAeadAes128Gcm}; +std::vector kBogusSuite = {0xfefefefe}; +static const std::vector kUnknownFirstSuite = { + 0xfefefefe, + (static_cast(HpkeKdfHkdfSha256) << 16) | HpkeAeadAes128Gcm}; + +class TlsConnectStreamTls13Ech : public TlsConnectTestBase { + public: + TlsConnectStreamTls13Ech() + : TlsConnectTestBase(ssl_variant_stream, SSL_LIBRARY_VERSION_TLS_1_3) {} + + void ReplayChWithMalformedInner(const std::string& ch, uint8_t server_alert, + uint32_t server_code, uint32_t client_code) { + std::vector ch_vec = hex_string_to_bytes(ch); + DataBuffer ch_buf; + ScopedSECKEYPublicKey pub; + ScopedSECKEYPrivateKey priv; + EnsureTlsSetup(); + ImportFixedEchKeypair(pub, priv); + SetMutualEchConfigs(pub, priv); + + TlsAgentTestBase::MakeRecord(variant_, ssl_ct_handshake, + SSL_LIBRARY_VERSION_TLS_1_3, ch_vec.data(), + ch_vec.size(), &ch_buf, 0); + StartConnect(); + client_->SendDirect(ch_buf); + ExpectAlert(server_, server_alert); + server_->Handshake(); + server_->CheckErrorCode(server_code); + client_->ExpectReceiveAlert(server_alert, kTlsAlertFatal); + client_->Handshake(); + client_->CheckErrorCode(client_code); + } + + // Setup Client/Server with mismatched AEADs + void SetupForEchRetry() { + ScopedSECKEYPublicKey server_pub; + ScopedSECKEYPrivateKey server_priv; + ScopedSECKEYPublicKey client_pub; + ScopedSECKEYPrivateKey client_priv; + DataBuffer server_rec; + DataBuffer client_rec; + TlsConnectTestBase::GenerateEchConfig(HpkeDhKemX25519Sha256, kSuiteChaCha, + kPublicName, 100, server_rec, + server_pub, server_priv); + ASSERT_EQ(SECSuccess, + SSL_SetServerEchConfigs(server_->ssl_fd(), server_pub.get(), + server_priv.get(), server_rec.data(), + server_rec.len())); + + TlsConnectTestBase::GenerateEchConfig(HpkeDhKemX25519Sha256, kSuiteAes, + kPublicName, 100, client_rec, + client_pub, client_priv); + ASSERT_EQ(SECSuccess, + SSL_SetClientEchConfigs(client_->ssl_fd(), client_rec.data(), + client_rec.len())); + } + + // Parse a captured SNI extension and validate the contained name. + void CheckSniExtension(const DataBuffer& data, + const std::string expected_name) { + TlsParser parser(data.data(), data.len()); + uint32_t tmp; + ASSERT_TRUE(parser.Read(&tmp, 2)); + ASSERT_EQ(parser.remaining(), tmp); + ASSERT_TRUE(parser.Read(&tmp, 1)); + ASSERT_EQ(0U, tmp); /* sni_nametype_hostname */ + DataBuffer name; + ASSERT_TRUE(parser.ReadVariable(&name, 2)); + ASSERT_EQ(0U, parser.remaining()); + // Manual comparison to silence coverity false-positives. + ASSERT_EQ(name.len(), kPublicName.length()); + ASSERT_EQ(0, + memcmp(kPublicName.c_str(), name.data(), kPublicName.length())); + } + + void DoEchRetry(const ScopedSECKEYPublicKey& server_pub, + const ScopedSECKEYPrivateKey& server_priv, + const DataBuffer& server_rec) { + StackSECItem retry_configs; + ASSERT_EQ(SECSuccess, + SSL_GetEchRetryConfigs(client_->ssl_fd(), &retry_configs)); + ASSERT_NE(0U, retry_configs.len); + + // Reset expectations for the TlsAgent dtor. + server_->ExpectReceiveAlert(kTlsAlertCloseNotify, kTlsAlertWarning); + Reset(); + EnsureTlsSetup(); + ASSERT_EQ(SECSuccess, + SSL_SetServerEchConfigs(server_->ssl_fd(), server_pub.get(), + server_priv.get(), server_rec.data(), + server_rec.len())); + ASSERT_EQ(SECSuccess, + SSL_SetClientEchConfigs(client_->ssl_fd(), retry_configs.data, + retry_configs.len)); + client_->ExpectEch(); + server_->ExpectEch(); + 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()); + if (!slot) { + ADD_FAILURE() << "No slot"; + return; + } + std::vector pkcs8_r = hex_string_to_bytes(kFixedServerPubkey); + SECItem pkcs8_r_item = {siBuffer, toUcharPtr(pkcs8_r.data()), + static_cast(pkcs8_r.size())}; + + SECKEYPrivateKey* tmp_priv = nullptr; + ASSERT_EQ(SECSuccess, PK11_ImportDERPrivateKeyInfoAndReturnKey( + slot.get(), &pkcs8_r_item, nullptr, nullptr, + false, false, KU_ALL, &tmp_priv, nullptr)); + priv.reset(tmp_priv); + SECKEYPublicKey* tmp_pub = SECKEY_ConvertToPublicKey(tmp_priv); + pub.reset(tmp_pub); + ASSERT_NE(nullptr, tmp_pub); + } + + void SetMutualEchConfigs(ScopedSECKEYPublicKey& pub, + ScopedSECKEYPrivateKey& priv) { + DataBuffer record; + TlsConnectTestBase::GenerateEchConfig(HpkeDhKemX25519Sha256, kDefaultSuites, + kPublicName, 100, record, 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())); + } +}; + +static void CheckCertVerifyPublicName(TlsAgent* agent) { + agent->UpdatePreliminaryChannelInfo(); + EXPECT_NE(0U, (agent->pre_info().valuesSet & ssl_preinfo_ech)); + EXPECT_EQ(agent->GetEchExpected(), agent->pre_info().echAccepted); + + // Check that echPublicName is only exposed in the rejection + // case, so that the application can use it for CertVerfiy. + if (agent->GetEchExpected()) { + EXPECT_EQ(nullptr, agent->pre_info().echPublicName); + } else { + EXPECT_NE(nullptr, agent->pre_info().echPublicName); + if (agent->pre_info().echPublicName) { + EXPECT_EQ(0, + strcmp(kPublicName.c_str(), agent->pre_info().echPublicName)); + } + } +} + +static SECStatus AuthCompleteSuccess(TlsAgent* agent, PRBool, PRBool) { + CheckCertVerifyPublicName(agent); + return SECSuccess; +} + +static SECStatus AuthCompleteFail(TlsAgent* agent, PRBool, PRBool) { + CheckCertVerifyPublicName(agent); + return SECFailure; +} + +TEST_P(TlsAgentEchTest, EchConfigsSupportedYesNo) { + if (variant_ == ssl_variant_datagram) { + return; + } + + // ECHConfig 2 cipher_suites are unsupported. + const std::string mixed = + "0086FE08003F000B7075626C69632E6E616D6500203BB6D46C201B820F1AE4AFD4DEC304" + "444156E4E04D1BF0FFDA7783B6B457F75600200008000100030001000100640000FE0800" + "3F000B7075626C69632E6E616D6500203BB6D46C201B820F1AE4AFD4DEC304444156E4E0" + "4D1BF0FFDA7783B6B457F756002000080001FFFFFFFF000100640000"; + std::vector config = hex_string_to_bytes(mixed); + DataBuffer record(config.data(), config.size()); + + EnsureInit(); + EXPECT_EQ(SECSuccess, SSL_EnableTls13GreaseEch(agent_->ssl_fd(), + PR_FALSE)); // Don't GREASE + InstallEchConfig(record, 0); + auto filter = MakeTlsFilter( + agent_, ssl_tls13_encrypted_client_hello_xtn); + agent_->Handshake(); + ASSERT_EQ(TlsAgent::STATE_CONNECTING, agent_->state()); + ASSERT_TRUE(filter->captured()); +} + +TEST_P(TlsAgentEchTest, EchConfigsSupportedNoYes) { + if (variant_ == ssl_variant_datagram) { + return; + } + + // ECHConfig 1 cipher_suites are unsupported. + const std::string mixed = + "0086FE08003F000B7075626C69632E6E616D6500203BB6D46C201B820F1AE4AFD4DEC304" + "444156E4E04D1BF0FFDA7783B6B457F756002000080001FFFFFFFF000100640000FE0800" + "3F000B7075626C69632E6E616D6500203BB6D46C201B820F1AE4AFD4DEC304444156E4E0" + "4D1BF0FFDA7783B6B457F75600200008000100030001000100640000"; + std::vector config = hex_string_to_bytes(mixed); + DataBuffer record(config.data(), config.size()); + + EnsureInit(); + EXPECT_EQ(SECSuccess, SSL_EnableTls13GreaseEch(agent_->ssl_fd(), + PR_FALSE)); // Don't GREASE + InstallEchConfig(record, 0); + auto filter = MakeTlsFilter( + agent_, ssl_tls13_encrypted_client_hello_xtn); + agent_->Handshake(); + ASSERT_EQ(TlsAgent::STATE_CONNECTING, agent_->state()); + ASSERT_TRUE(filter->captured()); +} + +TEST_P(TlsAgentEchTest, EchConfigsSupportedNoNo) { + if (variant_ == ssl_variant_datagram) { + return; + } + + // ECHConfig 1 and 2 cipher_suites are unsupported. + const std::string unsupported = + "0086FE08003F000B7075626C69632E6E616D6500203BB6D46C201B820F1AE4AFD4DEC304" + "444156E4E04D1BF0FFDA7783B6B457F756002000080001FFFF0001FFFF00640000FE0800" + "3F000B7075626C69632E6E616D6500203BB6D46C201B820F1AE4AFD4DEC304444156E4E0" + "4D1BF0FFDA7783B6B457F75600200008FFFF0003FFFF000100640000"; + std::vector config = hex_string_to_bytes(unsupported); + DataBuffer record(config.data(), config.size()); + + EnsureInit(); + EXPECT_EQ(SECSuccess, SSL_EnableTls13GreaseEch(agent_->ssl_fd(), + PR_FALSE)); // Don't GREASE + InstallEchConfig(record, SEC_ERROR_INVALID_ARGS); + auto filter = MakeTlsFilter( + agent_, ssl_tls13_encrypted_client_hello_xtn); + agent_->Handshake(); + ASSERT_EQ(TlsAgent::STATE_CONNECTING, agent_->state()); + ASSERT_FALSE(filter->captured()); +} + +TEST_P(TlsAgentEchTest, ShortEchConfig) { + EnsureInit(); + ScopedSECKEYPublicKey pub; + ScopedSECKEYPrivateKey priv; + DataBuffer record; + TlsConnectTestBase::GenerateEchConfig(HpkeDhKemX25519Sha256, kDefaultSuites, + kPublicName, 100, record, pub, priv); + record.Truncate(record.len() - 1); + InstallEchConfig(record, SEC_ERROR_BAD_DATA); + EXPECT_EQ(SECSuccess, SSL_EnableTls13GreaseEch(agent_->ssl_fd(), + PR_FALSE)); // Don't GREASE + auto filter = MakeTlsFilter( + agent_, ssl_tls13_encrypted_client_hello_xtn); + agent_->Handshake(); + ASSERT_EQ(TlsAgent::STATE_CONNECTING, agent_->state()); + ASSERT_FALSE(filter->captured()); +} + +TEST_P(TlsAgentEchTest, LongEchConfig) { + EnsureInit(); + ScopedSECKEYPublicKey pub; + ScopedSECKEYPrivateKey priv; + DataBuffer record; + TlsConnectTestBase::GenerateEchConfig(HpkeDhKemX25519Sha256, kDefaultSuites, + kPublicName, 100, record, pub, priv); + record.Write(record.len(), 1, 1); // Append one byte + InstallEchConfig(record, SEC_ERROR_BAD_DATA); + EXPECT_EQ(SECSuccess, SSL_EnableTls13GreaseEch(agent_->ssl_fd(), + PR_FALSE)); // Don't GREASE + auto filter = MakeTlsFilter( + agent_, ssl_tls13_encrypted_client_hello_xtn); + agent_->Handshake(); + ASSERT_EQ(TlsAgent::STATE_CONNECTING, agent_->state()); + ASSERT_FALSE(filter->captured()); +} + +TEST_P(TlsAgentEchTest, UnsupportedEchConfigVersion) { + EnsureInit(); + ScopedSECKEYPublicKey pub; + ScopedSECKEYPrivateKey priv; + DataBuffer record; + 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); + EXPECT_EQ(SECSuccess, SSL_EnableTls13GreaseEch(agent_->ssl_fd(), + PR_FALSE)); // Don't GREASE + auto filter = MakeTlsFilter( + agent_, ssl_tls13_encrypted_client_hello_xtn); + agent_->Handshake(); + ASSERT_EQ(TlsAgent::STATE_CONNECTING, agent_->state()); + ASSERT_FALSE(filter->captured()); +} + +TEST_P(TlsAgentEchTest, UnsupportedHpkeKem) { + EnsureInit(); + ScopedSECKEYPublicKey pub; + ScopedSECKEYPrivateKey priv; + DataBuffer record; + // SSL_EncodeEchConfig encodes without validation. + TlsConnectTestBase::GenerateEchConfig(static_cast(0xff), + kDefaultSuites, kPublicName, 100, + record, pub, priv); + InstallEchConfig(record, SEC_ERROR_INVALID_ARGS); + EXPECT_EQ(SECSuccess, SSL_EnableTls13GreaseEch(agent_->ssl_fd(), + PR_FALSE)); // Don't GREASE + auto filter = MakeTlsFilter( + agent_, ssl_tls13_encrypted_client_hello_xtn); + agent_->Handshake(); + ASSERT_EQ(TlsAgent::STATE_CONNECTING, agent_->state()); + ASSERT_FALSE(filter->captured()); +} + +TEST_P(TlsAgentEchTest, EchRejectIgnoreAllUnknownSuites) { + EnsureInit(); + ScopedSECKEYPublicKey pub; + ScopedSECKEYPrivateKey priv; + DataBuffer record; + TlsConnectTestBase::GenerateEchConfig(HpkeDhKemX25519Sha256, kBogusSuite, + kPublicName, 100, record, pub, priv); + InstallEchConfig(record, SEC_ERROR_INVALID_ARGS); + EXPECT_EQ(SECSuccess, SSL_EnableTls13GreaseEch(agent_->ssl_fd(), + PR_FALSE)); // Don't GREASE + auto filter = MakeTlsFilter( + agent_, ssl_tls13_encrypted_client_hello_xtn); + agent_->Handshake(); + ASSERT_FALSE(filter->captured()); +} + +TEST_F(TlsConnectStreamTls13, EchAcceptIgnoreSingleUnknownSuite) { + EnsureTlsSetup(); + DataBuffer record; + 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())); + ASSERT_EQ(SECSuccess, + SSL_SetServerEchConfigs(server_->ssl_fd(), pub.get(), priv.get(), + record.data(), record.len())); + + client_->ExpectEch(); + server_->ExpectEch(); + Connect(); +} + +TEST_P(TlsAgentEchTest, ApiInvalidArgs) { + EnsureInit(); + // SetClient + EXPECT_EQ(SECFailure, SSL_SetClientEchConfigs(agent_->ssl_fd(), nullptr, 1)); + + EXPECT_EQ(SECFailure, + SSL_SetClientEchConfigs(agent_->ssl_fd(), + reinterpret_cast(1), 0)); + + // SetServer + EXPECT_EQ(SECFailure, + SSL_SetServerEchConfigs(agent_->ssl_fd(), nullptr, + reinterpret_cast(1), + reinterpret_cast(1), 1)); + EXPECT_EQ(SECFailure, + SSL_SetServerEchConfigs( + agent_->ssl_fd(), reinterpret_cast(1), + nullptr, reinterpret_cast(1), 1)); + EXPECT_EQ(SECFailure, + SSL_SetServerEchConfigs( + agent_->ssl_fd(), reinterpret_cast(1), + reinterpret_cast(1), nullptr, 1)); + EXPECT_EQ(SECFailure, + SSL_SetServerEchConfigs(agent_->ssl_fd(), + reinterpret_cast(1), + reinterpret_cast(1), + reinterpret_cast(1), 0)); + + // GetRetries + EXPECT_EQ(SECFailure, SSL_GetEchRetryConfigs(agent_->ssl_fd(), nullptr)); + + // EncodeEchConfig + EXPECT_EQ(SECFailure, + SSL_EncodeEchConfig(nullptr, reinterpret_cast(1), 1, + static_cast(1), + reinterpret_cast(1), 1, + reinterpret_cast(1), + reinterpret_cast(1), 1)); + + EXPECT_EQ(SECFailure, + SSL_EncodeEchConfig("name", nullptr, 1, static_cast(1), + reinterpret_cast(1), 1, + reinterpret_cast(1), + reinterpret_cast(1), 1)); + EXPECT_EQ(SECFailure, + SSL_EncodeEchConfig("name", reinterpret_cast(1), 0, + static_cast(1), + reinterpret_cast(1), 1, + reinterpret_cast(1), + reinterpret_cast(1), 1)); + + EXPECT_EQ(SECFailure, + SSL_EncodeEchConfig("name", reinterpret_cast(1), 1, + static_cast(1), nullptr, 1, + reinterpret_cast(1), + reinterpret_cast(1), 1)); + + EXPECT_EQ(SECFailure, + SSL_EncodeEchConfig(nullptr, reinterpret_cast(1), 1, + static_cast(1), + reinterpret_cast(1), 0, + reinterpret_cast(1), + reinterpret_cast(1), 1)); + + EXPECT_EQ(SECFailure, + SSL_EncodeEchConfig("name", reinterpret_cast(1), 1, + static_cast(1), + reinterpret_cast(1), 1, + nullptr, reinterpret_cast(1), 1)); + + EXPECT_EQ(SECFailure, + SSL_EncodeEchConfig("name", reinterpret_cast(1), 1, + static_cast(1), + reinterpret_cast(1), 1, + reinterpret_cast(1), nullptr, 1)); +} + +TEST_P(TlsAgentEchTest, NoEarlyRetryConfigs) { + EnsureInit(); + StackSECItem retry_configs; + EXPECT_EQ(SECFailure, + SSL_GetEchRetryConfigs(agent_->ssl_fd(), &retry_configs)); + EXPECT_EQ(SSL_ERROR_HANDSHAKE_NOT_COMPLETED, PORT_GetError()); + + ScopedSECKEYPublicKey pub; + ScopedSECKEYPrivateKey priv; + DataBuffer record; + TlsConnectTestBase::GenerateEchConfig(HpkeDhKemX25519Sha256, kDefaultSuites, + kPublicName, 100, record, pub, priv); + InstallEchConfig(record, 0); + + EXPECT_EQ(SECFailure, + SSL_GetEchRetryConfigs(agent_->ssl_fd(), &retry_configs)); + EXPECT_EQ(SSL_ERROR_HANDSHAKE_NOT_COMPLETED, PORT_GetError()); +} + +TEST_P(TlsAgentEchTest, NoSniSoNoEch) { + EnsureInit(); + ScopedSECKEYPublicKey pub; + ScopedSECKEYPrivateKey priv; + DataBuffer record; + TlsConnectTestBase::GenerateEchConfig(HpkeDhKemX25519Sha256, kDefaultSuites, + kPublicName, 100, record, pub, priv); + SSL_SetURL(agent_->ssl_fd(), ""); + InstallEchConfig(record, 0); + SSL_SetURL(agent_->ssl_fd(), ""); + EXPECT_EQ(SECSuccess, SSL_EnableTls13GreaseEch(agent_->ssl_fd(), + PR_FALSE)); // Don't GREASE + auto filter = MakeTlsFilter( + agent_, ssl_tls13_encrypted_client_hello_xtn); + agent_->Handshake(); + ASSERT_FALSE(filter->captured()); +} + +TEST_P(TlsAgentEchTest, NoEchConfigSoNoEch) { + EnsureInit(); + ScopedSECKEYPublicKey pub; + ScopedSECKEYPrivateKey priv; + DataBuffer record; + EXPECT_EQ(SECSuccess, SSL_EnableTls13GreaseEch(agent_->ssl_fd(), + PR_FALSE)); // Don't GREASE + auto filter = MakeTlsFilter( + agent_, ssl_tls13_encrypted_client_hello_xtn); + agent_->Handshake(); + ASSERT_FALSE(filter->captured()); +} + +TEST_P(TlsAgentEchTest, EchConfigDuplicateExtensions) { + EnsureInit(); + ScopedSECKEYPublicKey pub; + ScopedSECKEYPrivateKey priv; + DataBuffer record; + TlsConnectTestBase::GenerateEchConfig(HpkeDhKemX25519Sha256, kDefaultSuites, + kPublicName, 100, record, 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); + uint32_t len; + ASSERT_TRUE(record.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); + new_len.Truncate(0); + + ASSERT_TRUE(record.Read(4, 2, &len)); + len += buf.len() - 2; + ASSERT_TRUE(new_len.Write(0, len, 2)); + record.Splice(new_len, 4, 2); + + InstallEchConfig(record, SEC_ERROR_EXTENSION_VALUE_INVALID); + EXPECT_EQ(SECSuccess, SSL_EnableTls13GreaseEch(agent_->ssl_fd(), + PR_FALSE)); // Don't GREASE + auto filter = MakeTlsFilter( + agent_, ssl_tls13_encrypted_client_hello_xtn); + agent_->Handshake(); + ASSERT_EQ(TlsAgent::STATE_CONNECTING, agent_->state()); + ASSERT_FALSE(filter->captured()); +} + +// Test an encoded ClientHelloInner containing an extra extensionType +// in outer_extensions, for which there is no corresponding (uncompressed) +// extension in ClientHelloOuter. +TEST_F(TlsConnectStreamTls13Ech, EchOuterExtensionsReferencesMissing) { + std::string ch = + "01000170030374d616d97efe591bf9bee4496bcc1118145b4dd02f7d1ff979fd0cf61749" + "a91e0000061301130313020100014100000010000e00000b7075626c69632e6e616d65ff" + "01000100000a00140012001d00170018001901000101010201030104003300260024001d" + "00204f346f86351b077492c83564c909d1aaab4f6f3ee2566af0e90a4684c793805d002b" + "0003020304000d0018001604030503060302030804080508060401050106010201002d00" + "020101001c00024001fe0800b30001000320a10698ccbd4bd86df91f617e58dd2ca96b8b" + "a5f058dd5c5ab1ca9750ef9d28c70020924764b36fe5d4a985f9857ceb75edb10b5f4b5b" + "f9d59290db70743e3c582163006acea5d7785cc506ecf5c859a9cad18f2b1df1a32231fe" + "0330471ee0e88ece9047e6491a381bfabed58f7fc542f0ba78eb55030bcfe1d400f67275" + "eac8619d1e4237e9d6176dd4eb54f3f25865686756f313a4ba47901c83e5ad5413609d39" + "816346b940115fd68e534609"; + ReplayChWithMalformedInner(ch, kTlsAlertIllegalParameter, + SSL_ERROR_RX_MALFORMED_ECH_EXTENSION, + SSL_ERROR_ILLEGAL_PARAMETER_ALERT); +} + +// Drop supported_versions from CHInner, make sure we don't negotiate 1.2+ECH. +TEST_F(TlsConnectStreamTls13Ech, EchVersion12Inner) { + std::string ch = + "0100017003034dd5bf4c12835e9be21f983953720e3595b3a8eeb4a44467678caceb7727" + "3be90000061301130313020100014100000010000e00000b7075626c69632e6e616d65ff" + "01000100000a00140012001d00170018001901000101010201030104003300260024001d" + "0020af7b976cdf69ffcd494ca5a93ae3ecde692b09be518ee033aad908c45b82c368002b" + "0003020304000d0018001604030503060302030804080508060401050106010201002d00" + "020101001c0002400100150003000000fe0800ac0001000320a10698ccbd4bd86df91f61" + "7e58dd2ca96b8ba5f058dd5c5ab1ca9750ef9d28c70020f5ece4c187b76f7e3d467c7506" + "215e73c27c918cd863c0e80d76a7987ec274320063e037492868eff5296a22dc50885e9d" + "f6964a5e26546f1bada043f8834988dfea5394b4c45a4d0b3afc52142d33f94161135a63" + "ed3c1b63f60d8133fb1cff17e1f9ced6c871984e412ed8ddb0f487c4d09d7aea80488004" + "c45a17cd3b5cdca316155fdb"; + ReplayChWithMalformedInner(ch, kTlsAlertProtocolVersion, + SSL_ERROR_UNSUPPORTED_VERSION, + SSL_ERROR_PROTOCOL_VERSION_ALERT); +} + +// Use CHInner supported_versions to negotiate 1.2. +TEST_F(TlsConnectStreamTls13Ech, EchVersion12InnerSupportedVersions) { + std::string ch = + "010001700303845c298db4017d2ed2584284b90e4ecba57a63663560c57aa0b1ac51203d" + "c8560000061301130313020100014100000010000e00000b7075626c69632e6e616d65ff" + "01000100000a00140012001d00170018001901000101010201030104003300260024001d" + "00203356719e88b539645438f645916aeeffe93c38803a59d6997938aa98eefbcf64002b" + "0003020304000d0018001604030503060302030804080508060401050106010201002d00" + "020101001c00024001fe0800b30001000320a10698ccbd4bd86df91f617e58dd2ca96b8b" + "a5f058dd5c5ab1ca9750ef9d28c700208412c945c53624bcace5eda0dc1ad300a1620e86" + "5a0f4a27755a3477b115b65b006abf1dfd77ddc1b80c5976732174a5fe7ebcf9ff1a548b" + "097daa12a37f3e32a613a0798544ba1d96239431bc807ddd9055ac3fb3e32b2eb42cec30" + "e915357418a953027d73020fd739287414205349eeff376dd464750ca70a965141a88800" + "6a043fe1d6d882d9a2c2f6f3"; + 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. +TEST_F(TlsConnectStreamTls13Ech, EchInnerMissingEmptyEch) { + std::string ch = + "0100017103032bf866cbd6d4abdec8ce23107eaef9af51b644043953e3b70f2f28f1898e" + "87880000061301130313020100014200000010000e00000b7075626c69632e6e616d65ff" + "01000100000a00140012001d00170018001901000101010201030104003300260024001d" + "00208f614d3017575332ca009a42d33bcaf876b4ba6d44b052e8019c31f6f1559e41002b" + "0003020304000d0018001604030503060302030804080508060401050106010201002d00" + "020101001c000240010015000100fe0800af0001000320a10698ccbd4bd86df91f617e58" + "dd2ca96b8ba5f058dd5c5ab1ca9750ef9d28c70020da1d5d9f183a5d5e49892e38eaae5e" + "9e3e6c5d404a5fdb672ca37f9cebabd57400660ea1d61917cc1049aab22506078ccecfc4" + "16a364a1beaa8915b250bb86ac2c725698c3c641830c4aa4e8b7f50152b5732b29b1ac43" + "45c97fc018855fd68e5600d0ef188e905b69997c3711b0ec0114a857177df728c7b84f52" + "2923f932838f7f15bb22644fd4"; + ReplayChWithMalformedInner(ch, kTlsAlertDecodeError, + SSL_ERROR_MISSING_ECH_EXTENSION, + SSL_ERROR_DECODE_ERROR_ALERT); +} + +// An empty config_id should prompt an alert. We don't support +// Optional Configuration Identifiers. +TEST_F(TlsConnectStreamTls13, EchRejectEmptyConfigId) { + static const uint8_t junk[16] = {0}; + DataBuffer junk_buf(junk, sizeof(junk)); + DataBuffer ech_xtn; + ech_xtn.Write(ech_xtn.len(), HpkeKdfHkdfSha256, 2); + ech_xtn.Write(ech_xtn.len(), HpkeAeadAes128Gcm, 2); + ech_xtn.Write(ech_xtn.len(), 0U, 1); // empty config_id + ech_xtn.Write(ech_xtn.len(), junk_buf.len(), 2); // enc + ech_xtn.Append(junk_buf); + ech_xtn.Write(ech_xtn.len(), junk_buf.len(), 2); // payload + ech_xtn.Append(junk_buf); + + EnsureTlsSetup(); + EXPECT_EQ(SECSuccess, SSL_EnableTls13GreaseEch(client_->ssl_fd(), + PR_FALSE)); // Don't GREASE + MakeTlsFilter(client_, kTlsHandshakeClientHello, + ssl_tls13_encrypted_client_hello_xtn, + ech_xtn); + ConnectExpectAlert(server_, kTlsAlertDecodeError); + client_->CheckErrorCode(SSL_ERROR_DECODE_ERROR_ALERT); + server_->CheckErrorCode(SSL_ERROR_RX_MALFORMED_ECH_EXTENSION); +} + +TEST_F(TlsConnectStreamTls13Ech, EchAcceptBasic) { + EnsureTlsSetup(); + SetupEch(client_, server_); + client_->SetAuthCertificateCallback(AuthCompleteSuccess); + + auto c_filter_sni = + MakeTlsFilter(client_, ssl_server_name_xtn); + client_->SetAuthCertificateCallback(AuthCompleteSuccess); + + Connect(); + ASSERT_TRUE(c_filter_sni->captured()); + CheckSniExtension(c_filter_sni->extension(), kPublicName); +} + +TEST_F(TlsConnectStreamTls13, EchAcceptWithResume) { + EnsureTlsSetup(); + SetupEch(client_, server_); + client_->SetAuthCertificateCallback(AuthCompleteSuccess); + ConfigureSessionCache(RESUME_BOTH, RESUME_TICKET); + Connect(); + SendReceive(); // Need to read so that we absorb the session ticket. + CheckKeys(); + + Reset(); + EnsureTlsSetup(); + SetupEch(client_, server_); + ExpectResumption(RESUME_TICKET); + auto filter = + MakeTlsFilter(client_, ssl_tls13_pre_shared_key_xtn); + StartConnect(); + Handshake(); + CheckConnected(); + // Make sure that the PSK extension is only in CHInner. + ASSERT_FALSE(filter->captured()); +} + +TEST_F(TlsConnectStreamTls13, EchAcceptWithExternalPsk) { + EnsureTlsSetup(); + SetupEch(client_, server_); + client_->SetAuthCertificateCallback(AuthCompleteSuccess); + + ScopedPK11SlotInfo slot(PK11_GetInternalSlot()); + ASSERT_TRUE(!!slot); + ScopedPK11SymKey key( + PK11_KeyGen(slot.get(), CKM_HKDF_KEY_GEN, nullptr, 16, nullptr)); + ASSERT_TRUE(!!key); + AddPsk(key, std::string("foo"), ssl_hash_sha256); + + // Not permitted in outer. + auto filter = + MakeTlsFilter(client_, ssl_tls13_pre_shared_key_xtn); + StartConnect(); + Handshake(); + CheckConnected(); + SendReceive(); + CheckKeys(ssl_kea_ecdh, ssl_grp_ec_curve25519, ssl_auth_psk, ssl_sig_none); + // Make sure that the PSK extension is only in CHInner. + ASSERT_FALSE(filter->captured()); +} + +// If an earlier version is negotiated, False Start must be disabled. +TEST_F(TlsConnectStreamTls13, EchDowngradeNoFalseStart) { + EnsureTlsSetup(); + SetupEch(client_, server_, HpkeDhKemX25519Sha256, false, true, false); + MakeTlsFilter(client_, + ssl_tls13_encrypted_client_hello_xtn); + client_->EnableFalseStart(); + client_->SetVersionRange(SSL_LIBRARY_VERSION_TLS_1_2, + SSL_LIBRARY_VERSION_TLS_1_3); + server_->SetVersionRange(SSL_LIBRARY_VERSION_TLS_1_2, + SSL_LIBRARY_VERSION_TLS_1_2); + + StartConnect(); + client_->Handshake(); + server_->Handshake(); + client_->Handshake(); + EXPECT_FALSE(client_->can_falsestart_hook_called()); + + // Make sure the write is blocked. + client_->ExpectReadWriteError(); + client_->SendData(10); +} + +SSLHelloRetryRequestAction RetryEchHello(PRBool firstHello, + const PRUint8* clientToken, + unsigned int clientTokenLen, + PRUint8* appToken, + unsigned int* appTokenLen, + unsigned int appTokenMax, void* arg) { + auto* called = reinterpret_cast(arg); + ++*called; + + EXPECT_EQ(0U, clientTokenLen); + return firstHello ? ssl_hello_retry_request : ssl_hello_retry_accept; +} + +// Generate HRR on CH1 Inner +TEST_F(TlsConnectStreamTls13, EchAcceptWithHrr) { + ScopedSECKEYPublicKey pub; + ScopedSECKEYPrivateKey priv; + DataBuffer record; + ConfigureSelfEncrypt(); + EnsureTlsSetup(); + TlsConnectTestBase::GenerateEchConfig(HpkeDhKemX25519Sha256, kDefaultSuites, + kPublicName, 100, record, 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())); + client_->ExpectEch(); + server_->ExpectEch(); + client_->SetAuthCertificateCallback(AuthCompleteSuccess); + + size_t cb_called = 0; + EXPECT_EQ(SECSuccess, SSL_HelloRetryRequestCallback( + server_->ssl_fd(), RetryEchHello, &cb_called)); + + // Start the handshake. + client_->StartConnect(); + server_->StartConnect(); + client_->Handshake(); + server_->Handshake(); + MakeNewServer(); + ASSERT_EQ(SECSuccess, + SSL_SetServerEchConfigs(server_->ssl_fd(), pub.get(), priv.get(), + record.data(), record.len())); + client_->ExpectEch(); + server_->ExpectEch(); + client_->SetAuthCertificateCallback(AuthCompleteSuccess); + Handshake(); + EXPECT_EQ(1U, cb_called); + CheckConnected(); + SendReceive(); +} + +// Fail to decrypt CH2. Unlike CH1, this generates an alert. +TEST_F(TlsConnectStreamTls13, EchFailDecryptCH2) { + EnsureTlsSetup(); + SetupEch(client_, server_); + size_t cb_called = 0; + EXPECT_EQ(SECSuccess, SSL_HelloRetryRequestCallback( + server_->ssl_fd(), RetryEchHello, &cb_called)); + + client_->StartConnect(); + server_->StartConnect(); + client_->Handshake(); + server_->Handshake(); + EXPECT_EQ(1U, cb_called); + // Stop the callback from being called in future handshakes. + EXPECT_EQ(SECSuccess, + SSL_HelloRetryRequestCallback(server_->ssl_fd(), nullptr, nullptr)); + + MakeTlsFilter(client_, + ssl_tls13_encrypted_client_hello_xtn, 80); + ExpectAlert(server_, kTlsAlertDecryptError); + Handshake(); + client_->CheckErrorCode(SSL_ERROR_DECRYPT_ERROR_ALERT); + server_->CheckErrorCode(SSL_ERROR_RX_MALFORMED_ECH_EXTENSION); +} + +// Change the ECH advertisement between CH1 and CH2. Use GREASE for simplicity. +TEST_F(TlsConnectStreamTls13, EchHrrChangeCh2OfferingYN) { + ConfigureSelfEncrypt(); + EnsureTlsSetup(); + size_t cb_called = 0; + EXPECT_EQ(SECSuccess, SSL_HelloRetryRequestCallback( + server_->ssl_fd(), RetryEchHello, &cb_called)); + + // Start the handshake, send GREASE ECH. + EXPECT_EQ(SECSuccess, SSL_EnableTls13GreaseEch(client_->ssl_fd(), + PR_TRUE)); // GREASE + client_->StartConnect(); + server_->StartConnect(); + client_->Handshake(); + server_->Handshake(); + MakeNewServer(); + EXPECT_EQ(SECSuccess, SSL_EnableTls13GreaseEch(client_->ssl_fd(), + PR_FALSE)); // Don't GREASE + ExpectAlert(server_, kTlsAlertIllegalParameter); + Handshake(); + client_->CheckErrorCode(SSL_ERROR_ILLEGAL_PARAMETER_ALERT); + server_->CheckErrorCode(SSL_ERROR_BAD_2ND_CLIENT_HELLO); + EXPECT_EQ(1U, cb_called); +} + +TEST_F(TlsConnectStreamTls13, EchHrrChangeCh2OfferingNY) { + 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_FALSE)); // Don't GREASE + // Start the handshake. + client_->StartConnect(); + server_->StartConnect(); + client_->Handshake(); + server_->Handshake(); + MakeNewServer(); + EXPECT_EQ(SECSuccess, SSL_EnableTls13GreaseEch(client_->ssl_fd(), + PR_TRUE)); // Send GREASE + ExpectAlert(server_, kTlsAlertIllegalParameter); + Handshake(); + client_->CheckErrorCode(SSL_ERROR_ILLEGAL_PARAMETER_ALERT); + server_->CheckErrorCode(SSL_ERROR_BAD_2ND_CLIENT_HELLO); + EXPECT_EQ(1U, cb_called); +} + +// Configure an external PSK. Generate an HRR off CH1Inner (which contains +// the PSK extension). Use the same PSK in CH2 and connect. +TEST_F(TlsConnectStreamTls13, EchAcceptWithHrrAndPsk) { + ScopedSECKEYPublicKey pub; + ScopedSECKEYPrivateKey priv; + DataBuffer record; + ConfigureSelfEncrypt(); + EnsureTlsSetup(); + + TlsConnectTestBase::GenerateEchConfig(HpkeDhKemX25519Sha256, kDefaultSuites, + kPublicName, 100, record, 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())); + client_->ExpectEch(); + server_->ExpectEch(); + + size_t cb_called = 0; + EXPECT_EQ(SECSuccess, SSL_HelloRetryRequestCallback( + server_->ssl_fd(), RetryEchHello, &cb_called)); + + static const uint8_t key_buf[16] = {0}; + SECItem key_item = {siBuffer, const_cast(&key_buf[0]), + sizeof(key_buf)}; + const char* label = "foo"; + ScopedPK11SlotInfo slot(PK11_GetInternalSlot()); + ASSERT_TRUE(!!slot); + ScopedPK11SymKey key(PK11_ImportSymKey(slot.get(), CKM_HKDF_KEY_GEN, + PK11_OriginUnwrap, CKA_DERIVE, + &key_item, nullptr)); + ASSERT_TRUE(!!key); + AddPsk(key, std::string(label), ssl_hash_sha256); + + // Start the handshake. + client_->StartConnect(); + server_->StartConnect(); + client_->Handshake(); + server_->Handshake(); + MakeNewServer(); + ASSERT_EQ(SECSuccess, + SSL_SetServerEchConfigs(server_->ssl_fd(), pub.get(), priv.get(), + record.data(), record.len())); + client_->ExpectEch(); + server_->ExpectEch(); + EXPECT_EQ(SECSuccess, + SSL_AddExternalPsk0Rtt(server_->ssl_fd(), key.get(), + reinterpret_cast(label), + strlen(label), ssl_hash_sha256, 0, 1000)); + server_->ExpectPsk(); + Handshake(); + EXPECT_EQ(1U, cb_called); + CheckConnected(); + SendReceive(); +} + +// Generate an HRR on CHOuter. Reject ECH on the second CH. +TEST_F(TlsConnectStreamTls13Ech, EchRejectWithHrr) { + ScopedSECKEYPublicKey pub; + ScopedSECKEYPrivateKey priv; + DataBuffer record; + ConfigureSelfEncrypt(); + EnsureTlsSetup(); + SetupForEchRetry(); + + size_t cb_called = 0; + EXPECT_EQ(SECSuccess, SSL_HelloRetryRequestCallback( + server_->ssl_fd(), RetryEchHello, &cb_called)); + client_->SetAuthCertificateCallback(AuthCompleteSuccess); + + // Start the handshake. + client_->StartConnect(); + server_->StartConnect(); + client_->Handshake(); + server_->Handshake(); + MakeNewServer(); + client_->ExpectEch(false); + server_->ExpectEch(false); + ExpectAlert(client_, kTlsAlertEchRequired); + Handshake(); + client_->CheckErrorCode(SSL_ERROR_ECH_RETRY_WITHOUT_ECH); + server_->ExpectReceiveAlert(kTlsAlertEchRequired, kTlsAlertFatal); + server_->Handshake(); + 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). +TEST_F(TlsConnectStreamTls13, EchRejectWithHrrAndPsk) { + ScopedSECKEYPublicKey pub; + ScopedSECKEYPrivateKey priv; + DataBuffer record; + ConfigureSelfEncrypt(); + EnsureTlsSetup(); + TlsConnectTestBase::GenerateEchConfig(HpkeDhKemX25519Sha256, kDefaultSuites, + kPublicName, 100, record, pub, priv); + ASSERT_EQ(SECSuccess, SSL_SetClientEchConfigs(client_->ssl_fd(), + record.data(), record.len())); + + size_t cb_called = 0; + EXPECT_EQ(SECSuccess, SSL_HelloRetryRequestCallback( + server_->ssl_fd(), RetryEchHello, &cb_called)); + + // Add a PSK to both endpoints. + static const uint8_t key_buf[16] = {0}; + SECItem key_item = {siBuffer, const_cast(&key_buf[0]), + sizeof(key_buf)}; + const char* label = "foo"; + ScopedPK11SlotInfo slot(PK11_GetInternalSlot()); + ASSERT_TRUE(!!slot); + ScopedPK11SymKey key(PK11_ImportSymKey(slot.get(), CKM_HKDF_KEY_GEN, + PK11_OriginUnwrap, CKA_DERIVE, + &key_item, nullptr)); + ASSERT_TRUE(!!key); + AddPsk(key, std::string(label), ssl_hash_sha256); + client_->ExpectPsk(ssl_psk_none); + + // Start the handshake. + client_->StartConnect(); + server_->StartConnect(); + client_->Handshake(); + server_->Handshake(); + MakeNewServer(); + client_->ExpectEch(false); + server_->ExpectEch(false); + EXPECT_EQ(SECSuccess, + SSL_AddExternalPsk0Rtt(server_->ssl_fd(), key.get(), + reinterpret_cast(label), + strlen(label), ssl_hash_sha256, 0, 1000)); + // Don't call ExpectPsk + ExpectAlert(client_, kTlsAlertEchRequired); + Handshake(); + client_->CheckErrorCode(SSL_ERROR_ECH_RETRY_WITHOUT_ECH); + server_->ExpectReceiveAlert(kTlsAlertEchRequired, kTlsAlertFatal); + server_->Handshake(); + EXPECT_EQ(1U, cb_called); +} + +// ECH (both connections), resumption rejected. +TEST_F(TlsConnectStreamTls13, EchRejectResume) { + EnsureTlsSetup(); + ConfigureSessionCache(RESUME_BOTH, RESUME_TICKET); + SetupEch(client_, server_); + Connect(); + SendReceive(); + + Reset(); + ClearServerCache(); // Invalidate the ticket + ConfigureSessionCache(RESUME_BOTH, RESUME_NONE); + ExpectResumption(RESUME_NONE); + SetupEch(client_, server_); + Connect(); + SendReceive(); +} + +// ECH (both connections) + 0-RTT +TEST_F(TlsConnectStreamTls13, EchZeroRttBoth) { + EnsureTlsSetup(); + SetupEch(client_, server_); + SetupForZeroRtt(); + client_->Set0RttEnabled(true); + server_->Set0RttEnabled(true); + SetupEch(client_, server_); + ExpectResumption(RESUME_TICKET); + ZeroRttSendReceive(true, true); + Handshake(); + ExpectEarlyDataAccepted(true); + CheckConnected(); + SendReceive(); +} + +// ECH (first connection only) + 0-RTT +TEST_F(TlsConnectStreamTls13, EchZeroRttFirst) { + EnsureTlsSetup(); + SetupEch(client_, server_); + SetupForZeroRtt(); + client_->Set0RttEnabled(true); + server_->Set0RttEnabled(true); + ExpectResumption(RESUME_TICKET); + ZeroRttSendReceive(true, true); + Handshake(); + ExpectEarlyDataAccepted(true); + CheckConnected(); + SendReceive(); +} + +// ECH (second connection only) + 0-RTT +TEST_F(TlsConnectStreamTls13, EchZeroRttSecond) { + EnsureTlsSetup(); + SetupForZeroRtt(); // Get a ticket + client_->Set0RttEnabled(true); + server_->Set0RttEnabled(true); + SetupEch(client_, server_); + ExpectResumption(RESUME_TICKET); + ZeroRttSendReceive(true, true); + Handshake(); + ExpectEarlyDataAccepted(true); + CheckConnected(); + SendReceive(); +} + +// ECH (first connection only, reject on second) + 0-RTT +TEST_F(TlsConnectStreamTls13, EchZeroRttRejectSecond) { + EnsureTlsSetup(); + SetupEch(client_, server_); + SetupForZeroRtt(); + client_->Set0RttEnabled(true); + server_->Set0RttEnabled(true); + + // Setup ECH only on the client. + SetupEch(client_, server_, HpkeDhKemX25519Sha256, false, true, false); + client_->SetAuthCertificateCallback(AuthCompleteSuccess); + + ExpectResumption(RESUME_NONE); + ExpectAlert(client_, kTlsAlertEchRequired); + ZeroRttSendReceive(true, false); + server_->Handshake(); + client_->Handshake(); + client_->CheckErrorCode(SSL_ERROR_ECH_RETRY_WITHOUT_ECH); + + ExpectEarlyDataAccepted(false); + server_->ExpectReceiveAlert(kTlsAlertEchRequired, kTlsAlertFatal); + server_->Handshake(); + // Reset expectations for the TlsAgent dtor. + server_->ExpectReceiveAlert(kTlsAlertCloseNotify, kTlsAlertWarning); +} + +// Test a critical extension in ECHConfig +TEST_F(TlsConnectStreamTls13, EchRejectUnknownCriticalExtension) { + EnsureTlsSetup(); + ScopedSECKEYPublicKey pub; + ScopedSECKEYPrivateKey priv; + DataBuffer record; + DataBuffer crit_rec; + DataBuffer len_buf; + uint64_t tmp; + + static const uint8_t crit_extensions[] = {0x00, 0x04, 0xff, 0xff, 0x00, 0x00}; + static const uint8_t extensions[] = {0x00, 0x04, 0x7f, 0xff, 0x00, 0x00}; + DataBuffer crit_exts(crit_extensions, sizeof(crit_extensions)); + 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); + 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); + len_buf.Truncate(0); + + ASSERT_TRUE(crit_rec.Read(4, 2, &tmp)); + len_buf.Write(0, tmp + crit_exts.len() - 2, 2); // two bytes of length + crit_rec.Append(crit_exts); + crit_rec.Splice(len_buf, 4, 2); + len_buf.Truncate(0); + + ASSERT_TRUE(record.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)); + len_buf.Write(0, tmp + non_crit_exts.len() - 2, 2); + record.Splice(len_buf, 4, 2); + + EXPECT_EQ(SECFailure, + SSL_SetClientEchConfigs(client_->ssl_fd(), crit_rec.data(), + crit_rec.len())); + EXPECT_EQ(SEC_ERROR_UNKNOWN_CRITICAL_EXTENSION, PORT_GetError()); + EXPECT_EQ(SECSuccess, SSL_EnableTls13GreaseEch(client_->ssl_fd(), + PR_FALSE)); // Don't GREASE + auto filter = MakeTlsFilter( + client_, ssl_tls13_encrypted_client_hello_xtn); + StartConnect(); + client_->Handshake(); + ASSERT_EQ(TlsAgent::STATE_CONNECTING, client_->state()); + ASSERT_FALSE(filter->captured()); + + // 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())); + filter = MakeTlsFilter( + client_, ssl_tls13_encrypted_client_hello_xtn); + StartConnect(); + client_->Handshake(); + ASSERT_EQ(TlsAgent::STATE_CONNECTING, client_->state()); + ASSERT_TRUE(filter->captured()); +} + +// Secure disable without ECH +TEST_F(TlsConnectStreamTls13, EchRejectAuthCertSuccessNoRetries) { + EnsureTlsSetup(); + SetupEch(client_, server_, HpkeDhKemX25519Sha256, false, true, false); + client_->SetAuthCertificateCallback(AuthCompleteSuccess); + ExpectAlert(client_, kTlsAlertEchRequired); + ConnectExpectFailOneSide(TlsAgent::CLIENT); + client_->CheckErrorCode(SSL_ERROR_ECH_RETRY_WITHOUT_ECH); + server_->ExpectReceiveAlert(kTlsAlertEchRequired, kTlsAlertFatal); + server_->Handshake(); + // Reset expectations for the TlsAgent dtor. + server_->ExpectReceiveAlert(kTlsAlertCloseNotify, kTlsAlertWarning); +} + +// When authenticating to the public name, the client MUST NOT +// send a certificate in response to a certificate request. +TEST_F(TlsConnectStreamTls13, EchRejectSuppressClientCert) { + EnsureTlsSetup(); + SetupEch(client_, server_, HpkeDhKemX25519Sha256, false, true, false); + client_->SetAuthCertificateCallback(AuthCompleteSuccess); + client_->SetupClientAuth(); + server_->RequestClientAuth(true); + auto cert_capture = + MakeTlsFilter(client_, kTlsHandshakeCertificate); + cert_capture->EnableDecryption(); + + StartConnect(); + client_->ExpectSendAlert(kTlsAlertEchRequired); + server_->ExpectSendAlert(kTlsAlertCertificateRequired); + ConnectExpectFail(); + + static const uint8_t empty_cert[4] = {0}; + EXPECT_EQ(DataBuffer(empty_cert, sizeof(empty_cert)), cert_capture->buffer()); +} + +// Secure disable with incompatible ECHConfig +TEST_F(TlsConnectStreamTls13, EchRejectAuthCertSuccessIncompatibleRetries) { + EnsureTlsSetup(); + ScopedSECKEYPublicKey server_pub; + ScopedSECKEYPrivateKey server_priv; + ScopedSECKEYPublicKey client_pub; + ScopedSECKEYPrivateKey client_priv; + DataBuffer server_rec; + DataBuffer client_rec; + + TlsConnectTestBase::GenerateEchConfig(HpkeDhKemX25519Sha256, kSuiteChaCha, + kPublicName, 100, server_rec, + server_pub, server_priv); + ASSERT_EQ(SECSuccess, + SSL_SetServerEchConfigs(server_->ssl_fd(), server_pub.get(), + server_priv.get(), server_rec.data(), + server_rec.len())); + + TlsConnectTestBase::GenerateEchConfig(HpkeDhKemX25519Sha256, kSuiteAes, + kPublicName, 100, client_rec, + client_pub, client_priv); + ASSERT_EQ(SECSuccess, + SSL_SetClientEchConfigs(client_->ssl_fd(), client_rec.data(), + client_rec.len())); + + // Change the first ECHConfig version to one we don't understand. + server_rec.Write(2, 0xfefe, 2); + // Skip the ECHConfigs length, the server sender will re-encode. + ASSERT_EQ(SECSuccess, SSLInt_SetRawEchConfigForRetry(server_->ssl_fd(), + &server_rec.data()[2], + server_rec.len() - 2)); + + client_->SetAuthCertificateCallback(AuthCompleteSuccess); + ExpectAlert(client_, kTlsAlertEchRequired); + ConnectExpectFailOneSide(TlsAgent::CLIENT); + client_->CheckErrorCode(SSL_ERROR_ECH_RETRY_WITHOUT_ECH); + server_->ExpectReceiveAlert(kTlsAlertEchRequired, kTlsAlertFatal); + server_->Handshake(); + // Reset expectations for the TlsAgent dtor. + server_->ExpectReceiveAlert(kTlsAlertCloseNotify, kTlsAlertWarning); +} + +// Check that an otherwise-accepted ECH fails expectedly +// with a bad certificate. +TEST_F(TlsConnectStreamTls13, EchRejectAuthCertFail) { + EnsureTlsSetup(); + SetupEch(client_, server_); + client_->SetAuthCertificateCallback(AuthCompleteFail); + ConnectExpectAlert(client_, kTlsAlertBadCertificate); + client_->CheckErrorCode(SSL_ERROR_BAD_CERTIFICATE); + server_->CheckErrorCode(SSL_ERROR_BAD_CERT_ALERT); + EXPECT_EQ(TlsAgent::STATE_ERROR, client_->state()); +} + +TEST_F(TlsConnectStreamTls13Ech, EchShortClientEncryptedCH) { + EnsureTlsSetup(); + SetupForEchRetry(); + auto filter = MakeTlsFilter( + client_, ssl_tls13_encrypted_client_hello_xtn, 1); + ConnectExpectAlert(server_, kTlsAlertDecodeError); + client_->CheckErrorCode(SSL_ERROR_DECODE_ERROR_ALERT); + server_->CheckErrorCode(SSL_ERROR_RX_MALFORMED_ECH_EXTENSION); +} + +TEST_F(TlsConnectStreamTls13Ech, EchLongClientEncryptedCH) { + EnsureTlsSetup(); + SetupForEchRetry(); + auto filter = MakeTlsFilter( + client_, ssl_tls13_encrypted_client_hello_xtn, 1000); + ConnectExpectAlert(server_, kTlsAlertDecodeError); + client_->CheckErrorCode(SSL_ERROR_DECODE_ERROR_ALERT); + server_->CheckErrorCode(SSL_ERROR_RX_MALFORMED_ECH_EXTENSION); +} + +TEST_F(TlsConnectStreamTls13Ech, EchShortServerEncryptedCH) { + EnsureTlsSetup(); + SetupForEchRetry(); + auto filter = MakeTlsFilter( + server_, ssl_tls13_encrypted_client_hello_xtn, 1); + filter->EnableDecryption(); + ConnectExpectAlert(client_, kTlsAlertDecodeError); + client_->CheckErrorCode(SSL_ERROR_RX_MALFORMED_ECH_CONFIG); + server_->CheckErrorCode(SSL_ERROR_DECODE_ERROR_ALERT); +} + +TEST_F(TlsConnectStreamTls13Ech, EchLongServerEncryptedCH) { + EnsureTlsSetup(); + SetupForEchRetry(); + auto filter = MakeTlsFilter( + server_, ssl_tls13_encrypted_client_hello_xtn, 1000); + filter->EnableDecryption(); + ConnectExpectAlert(client_, kTlsAlertDecodeError); + client_->CheckErrorCode(SSL_ERROR_RX_MALFORMED_ECH_CONFIG); + server_->CheckErrorCode(SSL_ERROR_DECODE_ERROR_ALERT); +} + +// Check that if authCertificate fails, retry_configs +// are not available to the application. +TEST_F(TlsConnectStreamTls13Ech, EchInsecureFallbackNoRetries) { + EnsureTlsSetup(); + StackSECItem retry_configs; + SetupForEchRetry(); + + // Use the filter to make sure retry_configs are sent. + auto filter = MakeTlsFilter( + server_, ssl_tls13_encrypted_client_hello_xtn); + filter->EnableDecryption(); + + client_->SetAuthCertificateCallback(AuthCompleteFail); + ConnectExpectAlert(client_, kTlsAlertBadCertificate); + client_->CheckErrorCode(SSL_ERROR_BAD_CERTIFICATE); + server_->CheckErrorCode(SSL_ERROR_BAD_CERT_ALERT); + EXPECT_EQ(TlsAgent::STATE_ERROR, client_->state()); + EXPECT_EQ(SECFailure, + SSL_GetEchRetryConfigs(client_->ssl_fd(), &retry_configs)); + EXPECT_EQ(SSL_ERROR_HANDSHAKE_NOT_COMPLETED, PORT_GetError()); + ASSERT_EQ(0U, retry_configs.len); + EXPECT_TRUE(filter->captured()); +} + +// Test that mismatched ECHConfigContents triggers a retry. +TEST_F(TlsConnectStreamTls13Ech, EchMismatchHpkeCiphersRetry) { + EnsureTlsSetup(); + ScopedSECKEYPublicKey server_pub; + ScopedSECKEYPrivateKey server_priv; + ScopedSECKEYPublicKey client_pub; + ScopedSECKEYPrivateKey client_priv; + DataBuffer server_rec; + DataBuffer client_rec; + + TlsConnectTestBase::GenerateEchConfig(HpkeDhKemX25519Sha256, kSuiteChaCha, + kPublicName, 100, server_rec, + server_pub, server_priv); + TlsConnectTestBase::GenerateEchConfig(HpkeDhKemX25519Sha256, kSuiteAes, + kPublicName, 100, client_rec, + client_pub, client_priv); + + ASSERT_EQ(SECSuccess, + SSL_SetServerEchConfigs(server_->ssl_fd(), server_pub.get(), + server_priv.get(), server_rec.data(), + server_rec.len())); + ASSERT_EQ(SECSuccess, + SSL_SetClientEchConfigs(client_->ssl_fd(), client_rec.data(), + client_rec.len())); + + client_->SetAuthCertificateCallback(AuthCompleteSuccess); + ExpectAlert(client_, kTlsAlertEchRequired); + ConnectExpectFailOneSide(TlsAgent::CLIENT); + client_->CheckErrorCode(SSL_ERROR_ECH_RETRY_WITH_ECH); + server_->ExpectReceiveAlert(kTlsAlertEchRequired, kTlsAlertFatal); + server_->Handshake(); + DoEchRetry(server_pub, server_priv, server_rec); +} + +// Test that mismatched ECH server keypair triggers a retry. +TEST_F(TlsConnectStreamTls13Ech, EchMismatchKeysRetry) { + EnsureTlsSetup(); + ScopedSECKEYPublicKey server_pub; + ScopedSECKEYPrivateKey server_priv; + ScopedSECKEYPublicKey client_pub; + ScopedSECKEYPrivateKey client_priv; + DataBuffer server_rec; + DataBuffer client_rec; + TlsConnectTestBase::GenerateEchConfig(HpkeDhKemX25519Sha256, kDefaultSuites, + kPublicName, 100, server_rec, + server_pub, server_priv); + TlsConnectTestBase::GenerateEchConfig(HpkeDhKemX25519Sha256, kDefaultSuites, + kPublicName, 100, client_rec, + client_pub, client_priv); + ASSERT_EQ(SECSuccess, + SSL_SetServerEchConfigs(server_->ssl_fd(), server_pub.get(), + server_priv.get(), server_rec.data(), + server_rec.len())); + ASSERT_EQ(SECSuccess, + SSL_SetClientEchConfigs(client_->ssl_fd(), client_rec.data(), + client_rec.len())); + + client_->SetAuthCertificateCallback(AuthCompleteSuccess); + client_->ExpectSendAlert(kTlsAlertEchRequired); + ConnectExpectFailOneSide(TlsAgent::CLIENT); + client_->CheckErrorCode(SSL_ERROR_ECH_RETRY_WITH_ECH); + server_->ExpectReceiveAlert(kTlsAlertEchRequired, kTlsAlertFatal); + server_->Handshake(); + DoEchRetry(server_pub, server_priv, server_rec); +} + +// Check that the client validates any server response to GREASE ECH +TEST_F(TlsConnectStreamTls13, EchValidateGreaseResponse) { + EnsureTlsSetup(); + ScopedSECKEYPublicKey server_pub; + ScopedSECKEYPrivateKey server_priv; + DataBuffer server_rec; + TlsConnectTestBase::GenerateEchConfig(HpkeDhKemX25519Sha256, kDefaultSuites, + kPublicName, 100, server_rec, + server_pub, server_priv); + ASSERT_EQ(SECSuccess, + SSL_SetServerEchConfigs(server_->ssl_fd(), server_pub.get(), + server_priv.get(), server_rec.data(), + server_rec.len())); + + // Damage the length and expect an alert. + auto filter = MakeTlsFilter( + server_, ssl_tls13_encrypted_client_hello_xtn, 0); + filter->EnableDecryption(); + EXPECT_EQ(SECSuccess, SSL_EnableTls13GreaseEch(client_->ssl_fd(), + PR_TRUE)); // GREASE + ConnectExpectAlert(client_, kTlsAlertDecodeError); + client_->CheckErrorCode(SSL_ERROR_RX_MALFORMED_ECH_CONFIG); + server_->CheckErrorCode(SSL_ERROR_DECODE_ERROR_ALERT); + + // If the retry_config contains an unknown version, it should be ignored. + Reset(); + EnsureTlsSetup(); + ASSERT_EQ(SECSuccess, + SSL_SetServerEchConfigs(server_->ssl_fd(), server_pub.get(), + server_priv.get(), server_rec.data(), + server_rec.len())); + server_rec.Write(2, 0xfefe, 2); + // Skip the ECHConfigs length, the server sender will re-encode. + ASSERT_EQ(SECSuccess, SSLInt_SetRawEchConfigForRetry(server_->ssl_fd(), + &server_rec.data()[2], + server_rec.len() - 2)); + EXPECT_EQ(SECSuccess, SSL_EnableTls13GreaseEch(client_->ssl_fd(), + PR_TRUE)); // GREASE + Connect(); + + // Lastly, if we DO support the retry_config, GREASE ECH should ignore it. + Reset(); + EnsureTlsSetup(); + server_rec.Write(2, ssl_tls13_encrypted_client_hello_xtn, 2); + ASSERT_EQ(SECSuccess, + SSL_SetServerEchConfigs(server_->ssl_fd(), server_pub.get(), + server_priv.get(), server_rec.data(), + server_rec.len())); + EXPECT_EQ(SECSuccess, SSL_EnableTls13GreaseEch(client_->ssl_fd(), + PR_TRUE)); // GREASE + Connect(); +} + +// Test a tampered CHInner (decrypt failure). +// Expect negotiation on outer, which fails due to the tampered transcript. +TEST_F(TlsConnectStreamTls13, EchBadCiphertext) { + EnsureTlsSetup(); + SetupEch(client_, server_); + /* Target the payload: + struct { + ECHCipherSuite suite; // 4B + opaque config_id<0..255>; // 32B + opaque enc<1..2^16-1>; // 32B for X25519 + opaque payload<1..2^16-1>; + } ClientEncryptedCH; + */ + MakeTlsFilter(client_, + ssl_tls13_encrypted_client_hello_xtn, 80); + client_->ExpectSendAlert(kTlsAlertBadRecordMac); + server_->ExpectSendAlert(kTlsAlertBadRecordMac); + ConnectExpectFail(); +} + +// Test a tampered CHOuter (decrypt failure on AAD). +// Expect negotiation on outer, which fails due to the tampered transcript. +TEST_F(TlsConnectStreamTls13, EchOuterBinding) { + EnsureTlsSetup(); + SetupEch(client_, server_); + client_->SetAuthCertificateCallback(AuthCompleteSuccess); + client_->SetVersionRange(SSL_LIBRARY_VERSION_TLS_1_2, + SSL_LIBRARY_VERSION_TLS_1_3); + + static const uint8_t supported_vers_13[] = {0x02, 0x03, 0x04}; + DataBuffer buf(supported_vers_13, sizeof(supported_vers_13)); + MakeTlsFilter(client_, ssl_tls13_supported_versions_xtn, + buf); + client_->ExpectSendAlert(kTlsAlertBadRecordMac); + server_->ExpectSendAlert(kTlsAlertBadRecordMac); + ConnectExpectFail(); +} + +// Test a bad (unknown) ECHCipherSuite. +// Expect negotiation on outer, which fails due to the tampered transcript. +TEST_F(TlsConnectStreamTls13, EchBadCiphersuite) { + EnsureTlsSetup(); + SetupEch(client_, server_); + /* Make KDF unknown */ + MakeTlsFilter(client_, + ssl_tls13_encrypted_client_hello_xtn, 0); + client_->ExpectSendAlert(kTlsAlertBadRecordMac); + server_->ExpectSendAlert(kTlsAlertBadRecordMac); + ConnectExpectFail(); + + Reset(); + EnsureTlsSetup(); + SetupEch(client_, server_); + /* Make AEAD unknown */ + MakeTlsFilter(client_, + ssl_tls13_encrypted_client_hello_xtn, 3); + client_->ExpectSendAlert(kTlsAlertBadRecordMac); + server_->ExpectSendAlert(kTlsAlertBadRecordMac); + ConnectExpectFail(); +} + +// Connect to a 1.2 server, it should ignore ECH. +TEST_F(TlsConnectStreamTls13, EchToTls12Server) { + EnsureTlsSetup(); + SetupEch(client_, server_); + client_->SetVersionRange(SSL_LIBRARY_VERSION_TLS_1_2, + SSL_LIBRARY_VERSION_TLS_1_3); + server_->SetVersionRange(SSL_LIBRARY_VERSION_TLS_1_2, + SSL_LIBRARY_VERSION_TLS_1_2); + + client_->ExpectEch(false); + server_->ExpectEch(false); + Connect(); +} + +TEST_F(TlsConnectStreamTls13, NoEchFromTls12Client) { + EnsureTlsSetup(); + SetupEch(client_, server_); + client_->SetVersionRange(SSL_LIBRARY_VERSION_TLS_1_2, + SSL_LIBRARY_VERSION_TLS_1_2); + server_->SetVersionRange(SSL_LIBRARY_VERSION_TLS_1_2, + SSL_LIBRARY_VERSION_TLS_1_3); + auto filter = MakeTlsFilter( + client_, ssl_tls13_encrypted_client_hello_xtn); + client_->ExpectEch(false); + server_->ExpectEch(false); + SetExpectedVersion(SSL_LIBRARY_VERSION_TLS_1_2); + Connect(); + ASSERT_FALSE(filter->captured()); +} + +TEST_F(TlsConnectStreamTls13, EchOuterWith12Max) { + EnsureTlsSetup(); + SetupEch(client_, server_); + client_->SetVersionRange(SSL_LIBRARY_VERSION_TLS_1_2, + SSL_LIBRARY_VERSION_TLS_1_3); + server_->SetVersionRange(SSL_LIBRARY_VERSION_TLS_1_2, + SSL_LIBRARY_VERSION_TLS_1_3); + + static const uint8_t supported_vers_12[] = {0x02, 0x03, 0x03}; + DataBuffer buf(supported_vers_12, sizeof(supported_vers_12)); + + StartConnect(); + MakeTlsFilter(client_, ssl_tls13_supported_versions_xtn, + buf); + + // Server should ignore the extension if 1.2 is negotiated. + // Here the CHInner is not modified, so if Accepted we'd connect. + auto filter = MakeTlsFilter( + server_, ssl_tls13_encrypted_client_hello_xtn); + client_->ExpectEch(false); + server_->ExpectEch(false); + ConnectExpectAlert(server_, kTlsAlertDecryptError); + client_->CheckErrorCode(SSL_ERROR_DECRYPT_ERROR_ALERT); + server_->CheckErrorCode(SSL_ERROR_BAD_HANDSHAKE_HASH_VALUE); + ASSERT_FALSE(filter->captured()); +} + +TEST_F(TlsConnectStreamTls13, EchOuterExtensionsInCHOuter) { + EnsureTlsSetup(); + uint8_t outer[2] = {0}; + DataBuffer outer_buf(outer, sizeof(outer)); + MakeTlsFilter(client_, kTlsHandshakeClientHello, + ssl_tls13_outer_extensions_xtn, + outer_buf); + + ConnectExpectAlert(server_, kTlsAlertUnsupportedExtension); + client_->CheckErrorCode(SSL_ERROR_UNSUPPORTED_EXTENSION_ALERT); + server_->CheckErrorCode(SSL_ERROR_RX_MALFORMED_CLIENT_HELLO); +} + +INSTANTIATE_TEST_CASE_P(EchAgentTest, TlsAgentEchTest, + ::testing::Combine(TlsConnectTestBase::kTlsVariantsAll, + TlsConnectTestBase::kTlsV13)); +#else + +TEST_P(TlsAgentEchTest, NoEchWithoutHpke) { + EnsureInit(); + uint8_t non_null[1]; + SECKEYPublicKey pub; + SECKEYPrivateKey priv; + ASSERT_EQ(SECFailure, SSL_SetClientEchConfigs(agent_->ssl_fd(), non_null, + sizeof(non_null))); + ASSERT_EQ(SSL_ERROR_FEATURE_DISABLED, PORT_GetError()); + + ASSERT_EQ(SECFailure, SSL_SetServerEchConfigs(agent_->ssl_fd(), &pub, &priv, + non_null, sizeof(non_null))); + ASSERT_EQ(SSL_ERROR_FEATURE_DISABLED, PORT_GetError()); +} + +INSTANTIATE_TEST_CASE_P(EchAgentTest, TlsAgentEchTest, + ::testing::Combine(TlsConnectTestBase::kTlsVariantsAll, + TlsConnectTestBase::kTlsV13)); + +#endif // NSS_ENABLE_DRAFT_HPKE +} // namespace nss_test diff --git a/gtests/ssl_gtest/tls_esni_unittest.cc b/gtests/ssl_gtest/tls_esni_unittest.cc deleted file mode 100644 index 0a02d06836..0000000000 --- a/gtests/ssl_gtest/tls_esni_unittest.cc +++ /dev/null @@ -1,494 +0,0 @@ -/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ -/* vim: set ts=2 et sw=2 tw=80: */ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this file, - * You can obtain one at http://mozilla.org/MPL/2.0/. */ - -#include "secerr.h" -#include "ssl.h" - -#include "gtest_utils.h" -#include "tls_agent.h" -#include "tls_connect.h" - -namespace nss_test { - -static const char* kDummySni("dummy.invalid"); - -std::vector kDefaultSuites = {TLS_AES_256_GCM_SHA384, - TLS_AES_128_GCM_SHA256}; -std::vector kChaChaSuite = {TLS_CHACHA20_POLY1305_SHA256}; -std::vector kBogusSuites = {0}; -std::vector kTls12Suites = { - TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256}; - -static void NamedGroup2ECParams(SSLNamedGroup group, SECItem* params) { - auto groupDef = ssl_LookupNamedGroup(group); - ASSERT_NE(nullptr, groupDef); - - auto oidData = SECOID_FindOIDByTag(groupDef->oidTag); - ASSERT_NE(nullptr, oidData); - ASSERT_NE(nullptr, - SECITEM_AllocItem(nullptr, params, (2 + oidData->oid.len))); - - /* - * params->data needs to contain the ASN encoding of an object ID (OID) - * representing the named curve. The actual OID is in - * oidData->oid.data so we simply prepend 0x06 and OID length - */ - params->data[0] = SEC_ASN1_OBJECT_ID; - params->data[1] = oidData->oid.len; - memcpy(params->data + 2, oidData->oid.data, oidData->oid.len); -} - -/* Checksum is a 4-byte array. */ -static void UpdateEsniKeysChecksum(DataBuffer* buf) { - SECStatus rv; - PRUint8 sha256[32]; - - /* Stomp the checksum. */ - PORT_Memset(buf->data() + 2, 0, 4); - - rv = PK11_HashBuf(ssl3_HashTypeToOID(ssl_hash_sha256), sha256, buf->data(), - buf->len()); - ASSERT_EQ(SECSuccess, rv); - buf->Write(2, sha256, 4); -} - -static void GenerateEsniKey(PRTime now, SSLNamedGroup group, - std::vector& cipher_suites, - DataBuffer* record, - ScopedSECKEYPublicKey* pubKey = nullptr, - ScopedSECKEYPrivateKey* privKey = nullptr) { - SECKEYECParams ecParams = {siBuffer, NULL, 0}; - NamedGroup2ECParams(group, &ecParams); - - SECKEYPublicKey* pub = nullptr; - SECKEYPrivateKey* priv = SECKEY_CreateECPrivateKey(&ecParams, &pub, nullptr); - ASSERT_NE(nullptr, priv); - SECITEM_FreeItem(&ecParams, PR_FALSE); - PRUint8 encoded[1024]; - unsigned int encoded_len = 0; - - SECStatus rv = SSL_EncodeESNIKeys( - &cipher_suites[0], cipher_suites.size(), group, pub, 100, - (now / PR_USEC_PER_SEC) - 1, (now / PR_USEC_PER_SEC) + 10, encoded, - &encoded_len, sizeof(encoded)); - ASSERT_EQ(SECSuccess, rv); - ASSERT_GT(encoded_len, 0U); - - if (pubKey) { - pubKey->reset(pub); - } else { - SECKEY_DestroyPublicKey(pub); - } - if (privKey) { - privKey->reset(priv); - } else { - SECKEY_DestroyPrivateKey(priv); - } - record->Truncate(0); - record->Write(0, encoded, encoded_len); -} - -static void SetupEsni(PRTime now, const std::shared_ptr& client, - const std::shared_ptr& server, - SSLNamedGroup group = ssl_grp_ec_curve25519) { - ScopedSECKEYPublicKey pub; - ScopedSECKEYPrivateKey priv; - DataBuffer record; - - GenerateEsniKey(now, ssl_grp_ec_curve25519, kDefaultSuites, &record, &pub, - &priv); - SECStatus rv = SSL_SetESNIKeyPair(server->ssl_fd(), priv.get(), record.data(), - record.len()); - ASSERT_EQ(SECSuccess, rv); - - rv = SSL_EnableESNI(client->ssl_fd(), record.data(), record.len(), kDummySni); - ASSERT_EQ(SECSuccess, rv); -} - -static void CheckSniExtension(const DataBuffer& data) { - TlsParser parser(data.data(), data.len()); - uint32_t tmp; - ASSERT_TRUE(parser.Read(&tmp, 2)); - ASSERT_EQ(parser.remaining(), tmp); - ASSERT_TRUE(parser.Read(&tmp, 1)); - ASSERT_EQ(0U, tmp); /* sni_nametype_hostname */ - DataBuffer name; - ASSERT_TRUE(parser.ReadVariable(&name, 2)); - ASSERT_EQ(0U, parser.remaining()); - DataBuffer expected(reinterpret_cast(kDummySni), - strlen(kDummySni)); - ASSERT_EQ(expected, name); -} - -class TlsAgentEsniTest : public TlsAgentTestClient13 { - public: - void SetUp() override { now_ = PR_Now(); } - - protected: - PRTime now() const { return now_; } - - void InstallEsni(const DataBuffer& record, PRErrorCode err = 0) { - SECStatus rv = SSL_EnableESNI(agent_->ssl_fd(), record.data(), record.len(), - kDummySni); - if (err == 0) { - ASSERT_EQ(SECSuccess, rv); - } else { - ASSERT_EQ(SECFailure, rv); - ASSERT_EQ(err, PORT_GetError()); - } - } - - private: - PRTime now_ = 0; -}; - -TEST_P(TlsAgentEsniTest, EsniInstall) { - EnsureInit(); - DataBuffer record; - GenerateEsniKey(now(), ssl_grp_ec_curve25519, kDefaultSuites, &record); - InstallEsni(record); -} - -// The next set of tests fail at setup time. -TEST_P(TlsAgentEsniTest, EsniInvalidHash) { - EnsureInit(); - DataBuffer record; - GenerateEsniKey(time(0), ssl_grp_ec_curve25519, kDefaultSuites, &record); - record.data()[2]++; - InstallEsni(record, SSL_ERROR_RX_MALFORMED_ESNI_KEYS); -} - -TEST_P(TlsAgentEsniTest, EsniInvalidVersion) { - EnsureInit(); - DataBuffer record; - GenerateEsniKey(now(), ssl_grp_ec_curve25519, kDefaultSuites, &record); - record.Write(0, 0xffff, 2); - InstallEsni(record, SSL_ERROR_UNSUPPORTED_VERSION); -} - -TEST_P(TlsAgentEsniTest, EsniShort) { - EnsureInit(); - DataBuffer record; - GenerateEsniKey(now(), ssl_grp_ec_curve25519, kDefaultSuites, &record); - record.Truncate(record.len() - 1); - UpdateEsniKeysChecksum(&record); - InstallEsni(record, SSL_ERROR_RX_MALFORMED_ESNI_KEYS); -} - -TEST_P(TlsAgentEsniTest, EsniLong) { - EnsureInit(); - DataBuffer record; - GenerateEsniKey(now(), ssl_grp_ec_curve25519, kDefaultSuites, &record); - record.Write(record.len(), 1, 1); - UpdateEsniKeysChecksum(&record); - InstallEsni(record, SSL_ERROR_RX_MALFORMED_ESNI_KEYS); -} - -TEST_P(TlsAgentEsniTest, EsniExtensionMismatch) { - EnsureInit(); - DataBuffer record; - GenerateEsniKey(now(), ssl_grp_ec_curve25519, kDefaultSuites, &record); - record.Write(record.len() - 1, 1, 1); - UpdateEsniKeysChecksum(&record); - InstallEsni(record, SSL_ERROR_RX_MALFORMED_ESNI_KEYS); -} - -// The following tests fail by ignoring the Esni block. -TEST_P(TlsAgentEsniTest, EsniUnknownGroup) { - EnsureInit(); - DataBuffer record; - GenerateEsniKey(now(), ssl_grp_ec_curve25519, kDefaultSuites, &record); - record.Write(8, 0xffff, 2); // Fake group - UpdateEsniKeysChecksum(&record); - InstallEsni(record, 0); - auto filter = - MakeTlsFilter(agent_, ssl_tls13_encrypted_sni_xtn); - agent_->Handshake(); - ASSERT_EQ(TlsAgent::STATE_CONNECTING, agent_->state()); - ASSERT_TRUE(!filter->captured()); -} - -TEST_P(TlsAgentEsniTest, EsniUnknownCS) { - EnsureInit(); - DataBuffer record; - GenerateEsniKey(now(), ssl_grp_ec_curve25519, kBogusSuites, &record); - InstallEsni(record, 0); - auto filter = - MakeTlsFilter(agent_, ssl_tls13_encrypted_sni_xtn); - agent_->Handshake(); - ASSERT_EQ(TlsAgent::STATE_CONNECTING, agent_->state()); - ASSERT_TRUE(!filter->captured()); -} - -TEST_P(TlsAgentEsniTest, EsniInvalidCS) { - EnsureInit(); - DataBuffer record; - GenerateEsniKey(now(), ssl_grp_ec_curve25519, kTls12Suites, &record); - UpdateEsniKeysChecksum(&record); - InstallEsni(record, 0); - auto filter = - MakeTlsFilter(agent_, ssl_tls13_encrypted_sni_xtn); - agent_->Handshake(); - ASSERT_EQ(TlsAgent::STATE_CONNECTING, agent_->state()); - ASSERT_TRUE(!filter->captured()); -} - -TEST_P(TlsAgentEsniTest, EsniNotReady) { - EnsureInit(); - DataBuffer record; - GenerateEsniKey(now() + 1000, ssl_grp_ec_curve25519, kDefaultSuites, &record); - InstallEsni(record, 0); - auto filter = - MakeTlsFilter(agent_, ssl_tls13_encrypted_sni_xtn); - agent_->Handshake(); - ASSERT_TRUE(!filter->captured()); -} - -TEST_P(TlsAgentEsniTest, EsniExpired) { - EnsureInit(); - DataBuffer record; - GenerateEsniKey(now() - 1000, ssl_grp_ec_curve25519, kDefaultSuites, &record); - InstallEsni(record, 0); - auto filter = - MakeTlsFilter(agent_, ssl_tls13_encrypted_sni_xtn); - agent_->Handshake(); - ASSERT_TRUE(!filter->captured()); -} - -TEST_P(TlsAgentEsniTest, NoSniSoNoEsni) { - EnsureInit(); - DataBuffer record; - GenerateEsniKey(now(), ssl_grp_ec_curve25519, kDefaultSuites, &record); - SSL_SetURL(agent_->ssl_fd(), ""); - InstallEsni(record, 0); - auto filter = - MakeTlsFilter(agent_, ssl_tls13_encrypted_sni_xtn); - agent_->Handshake(); - ASSERT_TRUE(!filter->captured()); -} - -static int32_t SniCallback(TlsAgent* agent, const SECItem* srvNameAddr, - PRUint32 srvNameArrSize) { - EXPECT_EQ(1U, srvNameArrSize); - SECItem expected = { - siBuffer, reinterpret_cast(const_cast("server")), - 6}; - EXPECT_TRUE(!SECITEM_CompareItem(&expected, &srvNameAddr[0])); - return SECSuccess; -} - -TEST_P(TlsConnectTls13, ConnectEsni) { - EnsureTlsSetup(); - SetupEsni(now(), client_, server_); - auto cFilterSni = - MakeTlsFilter(client_, ssl_server_name_xtn); - auto cFilterEsni = - MakeTlsFilter(client_, ssl_tls13_encrypted_sni_xtn); - client_->SetFilter(std::make_shared( - ChainedPacketFilterInit({cFilterSni, cFilterEsni}))); - auto sfilter = - MakeTlsFilter(server_, ssl_server_name_xtn); - sfilter->EnableDecryption(); - server_->SetSniCallback(SniCallback); - Connect(); - CheckSniExtension(cFilterSni->extension()); - ASSERT_TRUE(cFilterEsni->captured()); - // Check that our most preferred suite got chosen. - uint32_t suite; - ASSERT_TRUE(cFilterEsni->extension().Read(0, 2, &suite)); - ASSERT_EQ(TLS_AES_128_GCM_SHA256, static_cast(suite)); - ASSERT_TRUE(!sfilter->captured()); -} - -TEST_P(TlsConnectTls13, ConnectEsniHrr) { - EnsureTlsSetup(); - const std::vector groups = {ssl_grp_ec_secp384r1}; - server_->ConfigNamedGroups(groups); - SetupEsni(now(), client_, server_); - auto hrr_capture = MakeTlsFilter( - server_, kTlsHandshakeHelloRetryRequest); - auto filter = - MakeTlsFilter(client_, ssl_server_name_xtn); - auto filter2 = - MakeTlsFilter(client_, ssl_server_name_xtn, true); - auto efilter = - MakeTlsFilter(client_, ssl_tls13_encrypted_sni_xtn); - auto efilter2 = MakeTlsFilter( - client_, ssl_tls13_encrypted_sni_xtn, true); - - client_->SetFilter(std::make_shared( - ChainedPacketFilterInit({filter, filter2, efilter, efilter2}))); - server_->SetSniCallback(SniCallback); - Connect(); - CheckSniExtension(filter->extension()); - CheckSniExtension(filter2->extension()); - ASSERT_TRUE(efilter->captured()); - ASSERT_TRUE(efilter2->captured()); - ASSERT_NE(efilter->extension(), efilter2->extension()); - EXPECT_NE(0UL, hrr_capture->buffer().len()); -} - -TEST_P(TlsConnectTls13, ConnectEsniNoDummy) { - EnsureTlsSetup(); - ScopedSECKEYPublicKey pub; - ScopedSECKEYPrivateKey priv; - DataBuffer record; - - GenerateEsniKey(now(), ssl_grp_ec_curve25519, kDefaultSuites, &record, &pub, - &priv); - SECStatus rv = SSL_SetESNIKeyPair(server_->ssl_fd(), priv.get(), - record.data(), record.len()); - ASSERT_EQ(SECSuccess, rv); - rv = SSL_EnableESNI(client_->ssl_fd(), record.data(), record.len(), ""); - ASSERT_EQ(SECSuccess, rv); - - auto cfilter = - MakeTlsFilter(client_, ssl_server_name_xtn); - auto sfilter = - MakeTlsFilter(server_, ssl_server_name_xtn); - server_->SetSniCallback(SniCallback); - Connect(); - ASSERT_TRUE(!cfilter->captured()); - ASSERT_TRUE(!sfilter->captured()); -} - -TEST_P(TlsConnectTls13, ConnectEsniNullDummy) { - EnsureTlsSetup(); - ScopedSECKEYPublicKey pub; - ScopedSECKEYPrivateKey priv; - DataBuffer record; - - GenerateEsniKey(now(), ssl_grp_ec_curve25519, kDefaultSuites, &record, &pub, - &priv); - SECStatus rv = SSL_SetESNIKeyPair(server_->ssl_fd(), priv.get(), - record.data(), record.len()); - ASSERT_EQ(SECSuccess, rv); - rv = SSL_EnableESNI(client_->ssl_fd(), record.data(), record.len(), nullptr); - ASSERT_EQ(SECSuccess, rv); - - auto cfilter = - MakeTlsFilter(client_, ssl_server_name_xtn); - auto sfilter = - MakeTlsFilter(server_, ssl_server_name_xtn); - server_->SetSniCallback(SniCallback); - Connect(); - ASSERT_TRUE(!cfilter->captured()); - ASSERT_TRUE(!sfilter->captured()); -} - -/* Tell the client that it supports AES but the server that it supports ChaCha - */ -TEST_P(TlsConnectTls13, ConnectEsniCSMismatch) { - EnsureTlsSetup(); - ScopedSECKEYPublicKey pub; - ScopedSECKEYPrivateKey priv; - DataBuffer record; - - GenerateEsniKey(now(), ssl_grp_ec_curve25519, kDefaultSuites, &record, &pub, - &priv); - PRUint8 encoded[1024]; - unsigned int encoded_len = 0; - - SECStatus rv = SSL_EncodeESNIKeys( - &kChaChaSuite[0], kChaChaSuite.size(), ssl_grp_ec_curve25519, pub.get(), - 100, (now() / PR_USEC_PER_SEC) - 1, (now() / PR_USEC_PER_SEC) + 10, - encoded, &encoded_len, sizeof(encoded)); - ASSERT_EQ(SECSuccess, rv); - ASSERT_LT(0U, encoded_len); - rv = SSL_SetESNIKeyPair(server_->ssl_fd(), priv.get(), encoded, encoded_len); - ASSERT_EQ(SECSuccess, rv); - rv = SSL_EnableESNI(client_->ssl_fd(), record.data(), record.len(), ""); - ASSERT_EQ(SECSuccess, rv); - ConnectExpectAlert(server_, illegal_parameter); - server_->CheckErrorCode(SSL_ERROR_RX_MALFORMED_CLIENT_HELLO); -} - -TEST_P(TlsConnectTls13, ConnectEsniP256) { - EnsureTlsSetup(); - SetupEsni(now(), client_, server_, ssl_grp_ec_secp256r1); - auto cfilter = - MakeTlsFilter(client_, ssl_server_name_xtn); - auto sfilter = - MakeTlsFilter(server_, ssl_server_name_xtn); - server_->SetSniCallback(SniCallback); - Connect(); - CheckSniExtension(cfilter->extension()); - ASSERT_TRUE(!sfilter->captured()); -} - -TEST_P(TlsConnectTls13, ConnectMismatchedEsniKeys) { - EnsureTlsSetup(); - SetupEsni(now(), client_, server_); - // Now install a new set of keys on the client, so we have a mismatch. - DataBuffer record; - GenerateEsniKey(now(), ssl_grp_ec_curve25519, kDefaultSuites, &record); - - SECStatus rv = - SSL_EnableESNI(client_->ssl_fd(), record.data(), record.len(), kDummySni); - ASSERT_EQ(SECSuccess, rv); - ConnectExpectAlert(server_, illegal_parameter); - server_->CheckErrorCode(SSL_ERROR_RX_MALFORMED_CLIENT_HELLO); -} - -TEST_P(TlsConnectTls13, ConnectDamagedEsniExtensionCH) { - EnsureTlsSetup(); - SetupEsni(now(), client_, server_); - auto filter = MakeTlsFilter( - client_, ssl_tls13_encrypted_sni_xtn, 50); // in the ciphertext - ConnectExpectAlert(server_, illegal_parameter); - server_->CheckErrorCode(SSL_ERROR_RX_MALFORMED_CLIENT_HELLO); -} - -TEST_P(TlsConnectTls13, ConnectRemoveEsniExtensionEE) { - EnsureTlsSetup(); - SetupEsni(now(), client_, server_); - auto filter = - MakeTlsFilter(server_, ssl_tls13_encrypted_sni_xtn); - filter->EnableDecryption(); - ConnectExpectAlert(client_, missing_extension); - client_->CheckErrorCode(SSL_ERROR_MISSING_ESNI_EXTENSION); -} - -TEST_P(TlsConnectTls13, ConnectShortEsniExtensionEE) { - EnsureTlsSetup(); - SetupEsni(now(), client_, server_); - DataBuffer shortNonce; - auto filter = MakeTlsFilter( - server_, ssl_tls13_encrypted_sni_xtn, shortNonce); - filter->EnableDecryption(); - ConnectExpectAlert(client_, illegal_parameter); - client_->CheckErrorCode(SSL_ERROR_RX_MALFORMED_ESNI_EXTENSION); -} - -TEST_P(TlsConnectTls13, ConnectBogusEsniExtensionEE) { - EnsureTlsSetup(); - SetupEsni(now(), client_, server_); - const uint8_t bogusNonceBuf[16] = {0}; - DataBuffer bogusNonce(bogusNonceBuf, sizeof(bogusNonceBuf)); - auto filter = MakeTlsFilter( - server_, ssl_tls13_encrypted_sni_xtn, bogusNonce); - filter->EnableDecryption(); - ConnectExpectAlert(client_, illegal_parameter); - client_->CheckErrorCode(SSL_ERROR_RX_MALFORMED_ESNI_EXTENSION); -} - -// ESNI is a commitment to doing TLS 1.3 or above. -// The TLS 1.2 server ignores ESNI and processes the dummy SNI. -// The client then aborts when it sees the server did TLS 1.2. -TEST_P(TlsConnectTls13, EsniButTLS12Server) { - EnsureTlsSetup(); - SetupEsni(now(), client_, server_); - client_->SetVersionRange(SSL_LIBRARY_VERSION_TLS_1_2, - SSL_LIBRARY_VERSION_TLS_1_3); - server_->SetVersionRange(SSL_LIBRARY_VERSION_TLS_1_2, - SSL_LIBRARY_VERSION_TLS_1_2); - ConnectExpectAlert(client_, kTlsAlertProtocolVersion); - client_->CheckErrorCode(SSL_ERROR_UNSUPPORTED_VERSION); - server_->CheckErrorCode(SSL_ERROR_PROTOCOL_VERSION_ALERT); - ASSERT_FALSE(SSLInt_ExtensionNegotiated(server_->ssl_fd(), - ssl_tls13_encrypted_sni_xtn)); -} -} // namespace nss_test diff --git a/gtests/ssl_gtest/tls_filter.cc b/gtests/ssl_gtest/tls_filter.cc index 074a8bf291..2ba97287bc 100644 --- a/gtests/ssl_gtest/tls_filter.cc +++ b/gtests/ssl_gtest/tls_filter.cc @@ -17,6 +17,7 @@ extern "C" { #include "gtest_utils.h" #include "tls_agent.h" #include "tls_filter.h" +#include "tls_parser.h" #include "tls_protect.h" namespace nss_test { @@ -1031,6 +1032,69 @@ PacketFilter::Action TlsExtensionReplacer::FilterExtension( return CHANGE; } +PacketFilter::Action TlsExtensionResizer::FilterExtension( + uint16_t extension_type, const DataBuffer& input, DataBuffer* output) { + if (extension_type != extension_) { + return KEEP; + } + + if (input.len() <= length_) { + DataBuffer buf(length_ - input.len()); + output->Append(buf); + return CHANGE; + } + + output->Assign(input.data(), length_); + return CHANGE; +} + +PacketFilter::Action TlsExtensionAppender::FilterHandshake( + const HandshakeHeader& header, const DataBuffer& input, + DataBuffer* output) { + TlsParser parser(input); + if (!TlsExtensionFilter::FindExtensions(&parser, header)) { + return KEEP; + } + *output = input; + + // Increase the length of the extensions block. + if (!UpdateLength(output, parser.consumed(), 2)) { + return KEEP; + } + + // Extensions in Certificate are nested twice. Increase the size of the + // certificate list. + if (header.handshake_type() == kTlsHandshakeCertificate) { + TlsParser p2(input); + if (!p2.SkipVariable(1)) { + ADD_FAILURE(); + return KEEP; + } + if (!UpdateLength(output, p2.consumed(), 3)) { + return KEEP; + } + } + + size_t offset = output->len(); + offset = output->Write(offset, extension_, 2); + WriteVariable(output, offset, data_, 2); + + return CHANGE; +} + +bool TlsExtensionAppender::UpdateLength(DataBuffer* output, size_t offset, + size_t size) { + uint32_t len; + if (!output->Read(offset, size, &len)) { + ADD_FAILURE(); + return false; + } + + len += 4 + data_.len(); + output->Write(offset, len, size); + return true; +} + PacketFilter::Action TlsExtensionDropper::FilterExtension( uint16_t extension_type, const DataBuffer& input, DataBuffer* output) { if (extension_type == extension_) { diff --git a/gtests/ssl_gtest/tls_filter.h b/gtests/ssl_gtest/tls_filter.h index 5300075eaf..7f0a11e390 100644 --- a/gtests/ssl_gtest/tls_filter.h +++ b/gtests/ssl_gtest/tls_filter.h @@ -11,6 +11,7 @@ #include #include #include +#include "pk11pub.h" #include "sslt.h" #include "sslproto.h" #include "test_io.h" @@ -525,6 +526,37 @@ class TlsExtensionReplacer : public TlsExtensionFilter { const DataBuffer data_; }; +class TlsExtensionResizer : public TlsExtensionFilter { + public: + TlsExtensionResizer(const std::shared_ptr& a, uint16_t extension, + size_t length) + : TlsExtensionFilter(a), extension_(extension), length_(length) {} + PacketFilter::Action FilterExtension(uint16_t extension_type, + const DataBuffer& input, + DataBuffer* output) override; + + private: + uint16_t extension_; + size_t length_; +}; + +class TlsExtensionAppender : public TlsHandshakeFilter { + public: + TlsExtensionAppender(const std::shared_ptr& a, + uint8_t handshake_type, uint16_t ext, DataBuffer& data) + : TlsHandshakeFilter(a, {handshake_type}), extension_(ext), data_(data) {} + + virtual PacketFilter::Action FilterHandshake(const HandshakeHeader& header, + const DataBuffer& input, + DataBuffer* output); + + private: + bool UpdateLength(DataBuffer* output, size_t offset, size_t size); + + const uint16_t extension_; + const DataBuffer data_; +}; + class TlsExtensionDropper : public TlsExtensionFilter { public: TlsExtensionDropper(const std::shared_ptr& a, uint16_t extension) diff --git a/lib/ssl/SSLerrs.h b/lib/ssl/SSLerrs.h index f9c36b7c1a..3c585c7d4f 100644 --- a/lib/ssl/SSLerrs.h +++ b/lib/ssl/SSLerrs.h @@ -585,3 +585,18 @@ ER3(SSL_ERROR_DC_EXPIRED, (SSL_ERROR_BASE + 185), ER3(SSL_ERROR_DC_INAPPROPRIATE_VALIDITY_PERIOD, (SSL_ERROR_BASE + 186), "SSL received a delegated credential with excessive TTL.") + +ER3(SSL_ERROR_FEATURE_DISABLED, (SSL_ERROR_BASE + 187), + "The requested feature is disabled.") + +ER3(SSL_ERROR_ECH_RETRY_WITH_ECH, (SSL_ERROR_BASE + 188), + "TLS ECH was rejected, but verification succeeded and compatible retry_configs are available.") + +ER3(SSL_ERROR_ECH_RETRY_WITHOUT_ECH, (SSL_ERROR_BASE + 189), + "TLS ECH was rejected, but verification succeeded and no compatible retry_configs were found.") + +ER3(SSL_ERROR_ECH_FAILED, (SSL_ERROR_BASE + 190), + "TLS ECH was rejected and verification failed.") + +ER3(SSL_ERROR_ECH_REQUIRED_ALERT, (SSL_ERROR_BASE + 191), + "SSL peer reported ECH required.") diff --git a/lib/ssl/manifest.mn b/lib/ssl/manifest.mn index 5b3584ba98..d3e38b85b1 100644 --- a/lib/ssl/manifest.mn +++ b/lib/ssl/manifest.mn @@ -60,7 +60,7 @@ CSRCS = \ sslcert.c \ sslgrp.c \ sslprimitive.c \ - tls13esni.c \ + tls13ech.c \ tls13subcerts.c \ $(NULL) diff --git a/lib/ssl/ssl.gyp b/lib/ssl/ssl.gyp index 5c84a1f035..c3c7928058 100644 --- a/lib/ssl/ssl.gyp +++ b/lib/ssl/ssl.gyp @@ -44,7 +44,7 @@ 'ssltrace.c', 'sslver.c', 'tls13con.c', - 'tls13esni.c', + 'tls13ech.c', 'tls13exthandle.c', 'tls13hashstate.c', 'tls13hkdf.c', diff --git a/lib/ssl/ssl3con.c b/lib/ssl/ssl3con.c index dd49343bd2..bf5704c7ff 100644 --- a/lib/ssl/ssl3con.c +++ b/lib/ssl/ssl3con.c @@ -21,6 +21,8 @@ #include "sslerr.h" #include "ssl3ext.h" #include "ssl3exthandle.h" +#include "tls13ech.h" +#include "tls13exthandle.h" #include "tls13psk.h" #include "tls13subcerts.h" #include "prtime.h" @@ -62,7 +64,6 @@ static SECStatus ssl3_HandlePostHelloHandshakeMessage(sslSocket *ss, PRUint8 *b, PRUint32 length); static SECStatus ssl3_FlushHandshakeMessages(sslSocket *ss, PRInt32 flags); - static CK_MECHANISM_TYPE ssl3_GetHashMechanismByHashType(SSLHashType hashType); static CK_MECHANISM_TYPE ssl3_GetMgfMechanismByHashType(SSLHashType hash); PRBool ssl_IsRsaPssSignatureScheme(SSLSignatureScheme scheme); @@ -1167,7 +1168,7 @@ ssl_ClientReadVersion(sslSocket *ss, PRUint8 **b, unsigned int *len, return SECSuccess; } -static SECStatus +SECStatus ssl3_GetNewRandom(SSL3Random random) { SECStatus rv; @@ -3109,6 +3110,9 @@ ssl3_HandleAlert(sslSocket *ss, sslBuffer *buf) case bad_certificate_hash_value: error = SSL_ERROR_BAD_CERT_HASH_VALUE_ALERT; break; + case ech_required: + error = SSL_ERROR_ECH_REQUIRED_ALERT; + break; default: error = SSL_ERROR_RX_UNKNOWN_ALERT; break; @@ -3737,6 +3741,27 @@ ssl3_DeriveConnectionKeys(sslSocket *ss, PK11SymKey *masterSecret) return SECFailure; } +void +ssl3_CoalesceEchHandshakeHashes(sslSocket *ss) +{ + /* |sha| contains the CHOuter transcript, which is the singular + * transcript if not doing ECH. If the server responded with 1.2, + * contexts are not yet initialized. */ + if (ss->ssl3.hs.echAccepted) { + if (ss->ssl3.hs.sha) { + PORT_Assert(ss->ssl3.hs.shaEchInner); + PK11_DestroyContext(ss->ssl3.hs.sha, PR_TRUE); + ss->ssl3.hs.sha = ss->ssl3.hs.shaEchInner; + ss->ssl3.hs.shaEchInner = NULL; + } + } else { + if (ss->ssl3.hs.shaEchInner) { + PK11_DestroyContext(ss->ssl3.hs.shaEchInner, PR_TRUE); + ss->ssl3.hs.shaEchInner = NULL; + } + } +} + /* ssl3_InitHandshakeHashes creates handshake hash contexts and hashes in * buffered messages in ss->ssl3.hs.messages. Called from * ssl3_NegotiateCipherSuite(), tls13_HandleClientHelloPart2(), @@ -3781,6 +3806,18 @@ ssl3_InitHandshakeHashes(sslSocket *ss) return SECFailure; } + /* Alternate transcript hash used in Encrypted Client Hello. */ + if (!ss->sec.isServer && ss->ssl3.hs.echHpkeCtx) { + ss->ssl3.hs.shaEchInner = PK11_CreateDigestContext(hash_oid->offset); + if (ss->ssl3.hs.shaEchInner == NULL) { + ssl_MapLowLevelError(SSL_ERROR_SHA_DIGEST_FAILURE); + return SECFailure; + } + if (PK11_DigestBegin(ss->ssl3.hs.shaEchInner) != SECSuccess) { + ssl_MapLowLevelError(SSL_ERROR_DIGEST_FAILURE); + return SECFailure; + } + } } else { /* Both ss->ssl3.hs.md5 and ss->ssl3.hs.sha should be NULL or * created successfully. */ @@ -3817,6 +3854,15 @@ ssl3_InitHandshakeHashes(sslSocket *ss) } sslBuffer_Clear(&ss->ssl3.hs.messages); } + if (ss->ssl3.hs.shaEchInner && + ss->ssl3.hs.echInnerMessages.len > 0) { + if (PK11_DigestOp(ss->ssl3.hs.shaEchInner, ss->ssl3.hs.echInnerMessages.buf, + ss->ssl3.hs.echInnerMessages.len) != SECSuccess) { + ssl_MapLowLevelError(SSL_ERROR_DIGEST_FAILURE); + return SECFailure; + } + sslBuffer_Clear(&ss->ssl3.hs.echInnerMessages); + } return SECSuccess; } @@ -3828,6 +3874,7 @@ ssl3_RestartHandshakeHashes(sslSocket *ss) SSL_GETPID(), ss->fd)); ss->ssl3.hs.hashType = handshake_hash_unknown; ss->ssl3.hs.messages.len = 0; + ss->ssl3.hs.echInnerMessages.len = 0; if (ss->ssl3.hs.md5) { PK11_DestroyContext(ss->ssl3.hs.md5, PR_TRUE); ss->ssl3.hs.md5 = NULL; @@ -3836,12 +3883,46 @@ ssl3_RestartHandshakeHashes(sslSocket *ss) PK11_DestroyContext(ss->ssl3.hs.sha, PR_TRUE); ss->ssl3.hs.sha = NULL; } + if (ss->ssl3.hs.shaEchInner) { + PK11_DestroyContext(ss->ssl3.hs.shaEchInner, PR_TRUE); + ss->ssl3.hs.shaEchInner = NULL; + } if (ss->ssl3.hs.shaPostHandshake) { PK11_DestroyContext(ss->ssl3.hs.shaPostHandshake, PR_TRUE); ss->ssl3.hs.shaPostHandshake = NULL; } } +/* 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. */ +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) +{ + 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 */ @@ -3872,7 +3953,14 @@ ssl3_UpdateHandshakeHashes(sslSocket *ss, const unsigned char *b, unsigned int l if (ss->ssl3.hs.hashType == handshake_hash_unknown || ss->ssl3.hs.hashType == handshake_hash_record) { - return sslBuffer_Append(&ss->ssl3.hs.messages, b, l); + rv = sslBuffer_Append(&ss->ssl3.hs.messages, b, l); + if (rv != SECSuccess) { + return SECFailure; + } + if (!ss->sec.isServer && ss->ssl3.hs.echHpkeCtx) { + return ssl3_UpdateInnerHandshakeHashes(ss, b, l); + } + return SECSuccess; } PRINT_BUF(90, (ss, "handshake hash input:", b, l)); @@ -4882,7 +4970,7 @@ ssl_SetClientHelloSpecVersion(sslSocket *ss, ssl3CipherSpec *spec) ssl_ReleaseSpecWriteLock(ss); } -static SECStatus +SECStatus ssl3_InsertChHeaderSize(const sslSocket *ss, sslBuffer *preamble, const sslBuffer *extensions) { SECStatus rv; @@ -4958,15 +5046,16 @@ ssl3_AppendCipherSuites(sslSocket *ss, PRBool fallbackSCSV, sslBuffer *buf) return sslBuffer_InsertLength(buf, offset, 2); } -static SECStatus +SECStatus ssl3_CreateClientHelloPreamble(sslSocket *ss, const sslSessionID *sid, - PRBool realSid, PRUint16 version, + PRBool realSid, PRUint16 version, PRBool isEchInner, const sslBuffer *extensions, sslBuffer *preamble) { SECStatus rv; sslBuffer constructed = SSL_BUFFER_EMPTY; + const PRUint8 *client_random = isEchInner ? ss->ssl3.hs.client_inner_random : ss->ssl3.hs.client_random; PORT_Assert(sid); - PRBool fallbackSCSV = ss->opt.enableFallbackSCSV && + PRBool fallbackSCSV = ss->opt.enableFallbackSCSV && !isEchInner && (!realSid || version < sid->version); rv = sslBuffer_AppendNumber(&constructed, ssl_hs_client_hello, 1); @@ -5018,12 +5107,12 @@ ssl3_CreateClientHelloPreamble(sslSocket *ss, const sslSessionID *sid, goto loser; } - rv = sslBuffer_Append(&constructed, ss->ssl3.hs.client_random, SSL3_RANDOM_LENGTH); + rv = sslBuffer_Append(&constructed, client_random, SSL3_RANDOM_LENGTH); if (rv != SECSuccess) { goto loser; } - if (sid->version < SSL_LIBRARY_VERSION_TLS_1_3) { + if (sid->version < SSL_LIBRARY_VERSION_TLS_1_3 && !isEchInner) { rv = sslBuffer_AppendVariable(&constructed, sid->u.ssl3.sessionID, sid->u.ssl3.sessionIDLength, 1); } else if (ss->opt.enableTls13CompatMode && !IS_DTLS(ss)) { @@ -5373,49 +5462,72 @@ ssl3_SendClientHello(sslSocket *ss, sslClientHelloType type) } rv = ssl3_CreateClientHelloPreamble(ss, sid, requestingResume, version, - &extensionBuf, &chBuf); + PR_FALSE, &extensionBuf, &chBuf); if (rv != SECSuccess) { goto loser; /* err set by ssl3_CreateClientHelloPreamble. */ } - if (extensionBuf.len) { - rv = ssl_InsertPaddingExtension(ss, chBuf.len, &extensionBuf); - if (rv != SECSuccess) { - goto loser; /* err set by ssl_InsertPaddingExtension. */ - } + if (!ss->ssl3.hs.echHpkeCtx) { + if (extensionBuf.len) { + rv = tls13_MaybeGreaseEch(ss, chBuf.len, &extensionBuf); + if (rv != SECSuccess) { + goto loser; /* err set by tls13_MaybeGreaseEch. */ + } + rv = ssl_InsertPaddingExtension(ss, chBuf.len, &extensionBuf); + if (rv != SECSuccess) { + goto loser; /* err set by ssl_InsertPaddingExtension. */ + } - rv = ssl3_InsertChHeaderSize(ss, &chBuf, &extensionBuf); - if (rv != SECSuccess) { - goto loser; /* err set by ssl3_InsertChHeaderSize. */ + rv = ssl3_InsertChHeaderSize(ss, &chBuf, &extensionBuf); + if (rv != SECSuccess) { + goto loser; /* err set by ssl3_InsertChHeaderSize. */ + } + + /* If we are sending a PSK binder, replace the dummy value. */ + if (ssl3_ExtensionAdvertised(ss, ssl_tls13_pre_shared_key_xtn)) { + rv = tls13_WriteExtensionsWithBinder(ss, &extensionBuf, &chBuf); + } else { + rv = sslBuffer_AppendNumber(&chBuf, extensionBuf.len, 2); + if (rv != SECSuccess) { + goto loser; + } + rv = sslBuffer_AppendBuffer(&chBuf, &extensionBuf); + } + if (rv != SECSuccess) { + goto loser; /* err set by sslBuffer_Append*. */ + } } - /* If we are sending a PSK binder, replace the dummy value. */ - if (ssl3_ExtensionAdvertised(ss, ssl_tls13_pre_shared_key_xtn)) { - rv = tls13_WriteExtensionsWithBinder(ss, &extensionBuf, &chBuf); - } else { - rv = sslBuffer_AppendNumber(&chBuf, extensionBuf.len, 2); + /* If we already have a message in place, we need to enqueue it. + * This empties the buffer. This is a convenient place to call + * dtls_StageHandshakeMessage to mark the message boundary. */ + if (IS_DTLS(ss)) { + rv = dtls_StageHandshakeMessage(ss); if (rv != SECSuccess) { goto loser; } - rv = sslBuffer_AppendBuffer(&chBuf, &extensionBuf); } + rv = ssl3_AppendHandshake(ss, chBuf.buf, chBuf.len); + } else { + rv = tls13_ConstructClientHelloWithEch(ss, sid, !requestingResume, &chBuf, &extensionBuf); if (rv != SECSuccess) { - goto loser; /* err set by sslBuffer_Append*. */ + goto loser; /* code set */ } - } - sslBuffer_Clear(&extensionBuf); - - /* If we already have a message in place, we need to enqueue it. - * This empties the buffer. This is a convenient place to call - * dtls_StageHandshakeMessage to mark the message boundary. */ - if (IS_DTLS(ss)) { - rv = dtls_StageHandshakeMessage(ss); + rv = ssl3_UpdateOuterHandshakeHashes(ss, chBuf.buf, chBuf.len); if (rv != SECSuccess) { - return rv; + goto loser; /* code set */ + } + + if (IS_DTLS(ss)) { + rv = dtls_StageHandshakeMessage(ss); + if (rv != SECSuccess) { + return rv; + } } + /* By default, all messagess are added to both the inner and + * outer transcripts. For CH (or CH2 if HRR), that's problematic. */ + rv = ssl3_AppendHandshakeSuppressHash(ss, chBuf.buf, chBuf.len); } - rv = ssl3_AppendHandshake(ss, chBuf.buf, chBuf.len); - sslBuffer_Clear(&chBuf); if (rv != SECSuccess) { goto loser; } @@ -5450,6 +5562,8 @@ ssl3_SendClientHello(sslSocket *ss, sslClientHelloType type) } ss->ssl3.hs.ws = wait_server_hello; + sslBuffer_Clear(&chBuf); + sslBuffer_Clear(&extensionBuf); return SECSuccess; loser: @@ -6866,17 +6980,15 @@ ssl3_HandleServerHello(sslSocket *ss, PRUint8 *b, PRUint32 length) /* There are three situations in which the server must pick * TLS 1.3. * - * 1. We offered ESNI. - * 2. We received HRR - * 3. We sent early app data. + * 1. We received HRR + * 2. We sent early app data + * 3. ECH was accepted (checked in MaybeHandleEchSignal) + * + * If we offered ECH and the server negotiated a lower version, + * authenticate to the public name for secure disablement. * */ if (ss->version < SSL_LIBRARY_VERSION_TLS_1_3) { - if (ss->xtnData.esniPrivateKey) { - desc = protocol_version; - errCode = SSL_ERROR_UNSUPPORTED_VERSION; - goto alert_loser; - } if (isHelloRetry || ss->ssl3.hs.helloRetry) { /* SSL3_SendAlert() will uncache the SID. */ desc = illegal_parameter; @@ -6952,6 +7064,11 @@ 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) { @@ -7681,6 +7798,12 @@ ssl3_CompleteHandleCertificateRequest(sslSocket *ss, { SECStatus rv; + /* Should not send a client cert when (non-GREASE) ECH is rejected. */ + if (ss->ssl3.hs.echHpkeCtx && !ss->ssl3.hs.echAccepted) { + PORT_Assert(ssl3_ExtensionAdvertised(ss, ssl_tls13_encrypted_client_hello_xtn)); + goto send_no_certificate; + } + if (ss->getClientAuthData != NULL) { PORT_Assert((ss->ssl3.hs.preliminaryInfo & ssl_preinfo_all) == ssl_preinfo_all); @@ -7763,49 +7886,53 @@ ssl3_CheckFalseStart(sslSocket *ss) SSL_TRC(3, ("%d: SSL[%d]: no false start callback so no false start", SSL_GETPID(), ss->fd)); } else { - PRBool maybeFalseStart = PR_TRUE; SECStatus rv; rv = ssl_CheckServerRandom(ss); if (rv != SECSuccess) { SSL_TRC(3, ("%d: SSL[%d]: no false start due to possible downgrade", SSL_GETPID(), ss->fd)); - maybeFalseStart = PR_FALSE; + goto no_false_start; } /* An attacker can control the selected ciphersuite so we only wish to * do False Start in the case that the selected ciphersuite is * sufficiently strong that the attack can gain no advantage. * Therefore we always require an 80-bit cipher. */ - if (maybeFalseStart) { - ssl_GetSpecReadLock(ss); - maybeFalseStart = ss->ssl3.cwSpec->cipherDef->secret_key_size >= 10; - ssl_ReleaseSpecReadLock(ss); + ssl_GetSpecReadLock(ss); + PRBool weakCipher = ss->ssl3.cwSpec->cipherDef->secret_key_size < 10; + ssl_ReleaseSpecReadLock(ss); + if (weakCipher) { + SSL_TRC(3, ("%d: SSL[%d]: no false start due to weak cipher", + SSL_GETPID(), ss->fd)); + goto no_false_start; } - if (!maybeFalseStart) { - SSL_TRC(3, ("%d: SSL[%d]: no false start due to weak cipher", + if (ssl3_ExtensionAdvertised(ss, ssl_tls13_encrypted_client_hello_xtn)) { + SSL_TRC(3, ("%d: SSL[%d]: no false start due to lower version after ECH", SSL_GETPID(), ss->fd)); + goto no_false_start; + } + + PORT_Assert((ss->ssl3.hs.preliminaryInfo & ssl_preinfo_all) == + ssl_preinfo_all); + rv = (ss->canFalseStartCallback)(ss->fd, + ss->canFalseStartCallbackData, + &ss->ssl3.hs.canFalseStart); + if (rv == SECSuccess) { + SSL_TRC(3, ("%d: SSL[%d]: false start callback returned %s", + SSL_GETPID(), ss->fd, + ss->ssl3.hs.canFalseStart ? "TRUE" + : "FALSE")); } else { - PORT_Assert((ss->ssl3.hs.preliminaryInfo & ssl_preinfo_all) == - ssl_preinfo_all); - rv = (ss->canFalseStartCallback)(ss->fd, - ss->canFalseStartCallbackData, - &ss->ssl3.hs.canFalseStart); - if (rv == SECSuccess) { - SSL_TRC(3, ("%d: SSL[%d]: false start callback returned %s", - SSL_GETPID(), ss->fd, - ss->ssl3.hs.canFalseStart ? "TRUE" - : "FALSE")); - } else { - SSL_TRC(3, ("%d: SSL[%d]: false start callback failed (%s)", - SSL_GETPID(), ss->fd, - PR_ErrorToName(PR_GetError()))); - } - return rv; + SSL_TRC(3, ("%d: SSL[%d]: false start callback failed (%s)", + SSL_GETPID(), ss->fd, + PR_ErrorToName(PR_GetError()))); } + return rv; } +no_false_start: ss->ssl3.hs.canFalseStart = PR_FALSE; return SECSuccess; } @@ -8393,12 +8520,9 @@ ssl3_ServerCallSNICallback(sslSocket *ss) } /* Need to tell the client that application has picked * the name from the offered list and reconfigured the socket. - * Don't do this if we negotiated ESNI. */ - if (!ssl3_ExtensionNegotiated(ss, ssl_tls13_encrypted_sni_xtn)) { - ssl3_RegisterExtensionSender(ss, &ss->xtnData, ssl_server_name_xtn, - ssl_SendEmptyExtension); - } + ssl3_RegisterExtensionSender(ss, &ss->xtnData, ssl_server_name_xtn, + ssl_SendEmptyExtension); } else { /* Callback returned index outside of the boundary. */ PORT_Assert((unsigned int)ret < ss->xtnData.sniNameArrSize); @@ -8504,6 +8628,13 @@ 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; } @@ -8608,6 +8739,73 @@ ssl3_HandleClientHelloPreamble(sslSocket *ss, PRUint8 **b, PRUint32 *length, SEC return SECSuccess; } +static SECStatus +ssl3_ValidatePreambleWithVersion(sslSocket *ss, const SECItem *sidBytes, const SECItem *comps, + const SECItem *cookieBytes) +{ + SECStatus rv; + if (ss->version >= SSL_LIBRARY_VERSION_TLS_1_3) { + if (sidBytes->len > 0 && !IS_DTLS(ss)) { + SECITEM_FreeItem(&ss->ssl3.hs.fakeSid, PR_FALSE); + rv = SECITEM_CopyItem(NULL, &ss->ssl3.hs.fakeSid, sidBytes); + if (rv != SECSuccess) { + FATAL_ERROR(ss, PORT_GetError(), internal_error); + return SECFailure; + } + } + + /* TLS 1.3 requires that compression include only null. */ + if (comps->len != 1 || comps->data[0] != ssl_compression_null) { + FATAL_ERROR(ss, SSL_ERROR_RX_MALFORMED_CLIENT_HELLO, illegal_parameter); + return SECFailure; + } + + /* receivedCcs is only valid if we sent an HRR. */ + if (ss->ssl3.hs.receivedCcs && !ss->ssl3.hs.helloRetry) { + FATAL_ERROR(ss, SSL_ERROR_RX_UNEXPECTED_CHANGE_CIPHER, unexpected_message); + return SECFailure; + } + + /* A DTLS 1.3-only client MUST set the legacy_cookie field to zero length. + * If a DTLS 1.3 ClientHello is received with any other value in this field, + * the server MUST abort the handshake with an "illegal_parameter" alert. */ + if (IS_DTLS(ss) && cookieBytes->len != 0) { + FATAL_ERROR(ss, SSL_ERROR_RX_MALFORMED_CLIENT_HELLO, illegal_parameter); + return SECFailure; + } + } else { + /* ECH not possible here. */ + ss->ssl3.hs.preliminaryInfo |= ssl_preinfo_ech; + + /* HRR and ECH are TLS1.3-only. We ignore the Cookie extension here. */ + if (ss->ssl3.hs.helloRetry) { + FATAL_ERROR(ss, SSL_ERROR_UNSUPPORTED_VERSION, protocol_version); + return SECFailure; + } + + /* receivedCcs is only valid if we sent an HRR. */ + if (ss->ssl3.hs.receivedCcs) { + FATAL_ERROR(ss, SSL_ERROR_RX_UNEXPECTED_CHANGE_CIPHER, unexpected_message); + return SECFailure; + } + + /* TLS versions prior to 1.3 must include null somewhere. */ + if (comps->len < 1 || + !memchr(comps->data, ssl_compression_null, comps->len)) { + FATAL_ERROR(ss, SSL_ERROR_RX_MALFORMED_CLIENT_HELLO, illegal_parameter); + return SECFailure; + } + + /* We never send cookies in DTLS 1.2. */ + if (IS_DTLS(ss) && cookieBytes->len != 0) { + FATAL_ERROR(ss, SSL_ERROR_RX_MALFORMED_CLIENT_HELLO, illegal_parameter); + return SECFailure; + } + } + + return SECSuccess; +} + /* Called from ssl3_HandleHandshakeMessage() when it has deciphered a complete * ssl3 Client Hello message. * Caller must hold Handshake and RecvBuf locks. @@ -8627,6 +8825,7 @@ ssl3_HandleClientHello(sslSocket *ss, PRUint8 *b, PRUint32 length) SECItem cookieBytes = { siBuffer, NULL, 0 }; SECItem suites = { siBuffer, NULL, 0 }; SECItem comps = { siBuffer, NULL, 0 }; + SECItem *echInner = NULL; PRBool isTLS13; const PRUint8 *savedMsg = b; const PRUint32 savedLen = length; @@ -8748,64 +8947,23 @@ ssl3_HandleClientHello(sslSocket *ss, PRUint8 *b, PRUint32 length) goto alert_loser; } - if (sidBytes.len > 0 && !IS_DTLS(ss)) { - SECITEM_FreeItem(&ss->ssl3.hs.fakeSid, PR_FALSE); - rv = SECITEM_CopyItem(NULL, &ss->ssl3.hs.fakeSid, &sidBytes); - if (rv != SECSuccess) { - desc = internal_error; - errCode = PORT_GetError(); - goto alert_loser; - } - } - - /* TLS 1.3 requires that compression include only null. */ - if (comps.len != 1 || comps.data[0] != ssl_compression_null) { - goto alert_loser; - } - /* If there is a cookie, then this is a second ClientHello (TLS 1.3). */ if (ssl3_FindExtension(ss, ssl_tls13_cookie_xtn)) { ss->ssl3.hs.helloRetry = PR_TRUE; } - /* receivedCcs is only valid if we sent an HRR. */ - if (ss->ssl3.hs.receivedCcs && !ss->ssl3.hs.helloRetry) { - desc = unexpected_message; - errCode = SSL_ERROR_RX_UNEXPECTED_CHANGE_CIPHER; - goto alert_loser; - } - - /* A DTLS 1.3-only client MUST set the legacy_cookie field to zero length. - * If a DTLS 1.3 ClientHello is received with any other value in this field, - * the server MUST abort the handshake with an "illegal_parameter" alert. */ - if (IS_DTLS(ss) && cookieBytes.len != 0) { - goto alert_loser; - } - } else { - /* HRR is TLS1.3-only. We ignore the Cookie extension here. */ - if (ss->ssl3.hs.helloRetry) { - desc = protocol_version; - errCode = SSL_ERROR_UNSUPPORTED_VERSION; - goto alert_loser; - } - - /* receivedCcs is only valid if we sent an HRR. */ - if (ss->ssl3.hs.receivedCcs) { - desc = unexpected_message; - errCode = SSL_ERROR_RX_UNEXPECTED_CHANGE_CIPHER; - goto alert_loser; - } - - /* TLS versions prior to 1.3 must include null somewhere. */ - if (comps.len < 1 || - !memchr(comps.data, ssl_compression_null, comps.len)) { - goto alert_loser; + rv = tls13_MaybeHandleEch(ss, savedMsg, savedLen, &sidBytes, + &comps, &cookieBytes, &suites, &echInner); + if (rv != SECSuccess) { + errCode = PORT_GetError(); + goto loser; /* code set, alert sent. */ } + } - /* We never send cookies in DTLS 1.2. */ - if (IS_DTLS(ss) && cookieBytes.len != 0) { - goto loser; - } + rv = ssl3_ValidatePreambleWithVersion(ss, &sidBytes, &comps, &cookieBytes); + if (rv != SECSuccess) { + errCode = PORT_GetError(); + goto loser; /* code set, alert sent. */ } /* Now parse the rest of the extensions. */ @@ -8941,7 +9099,11 @@ ssl3_HandleClientHello(sslSocket *ss, PRUint8 *b, PRUint32 length) } if (isTLS13) { - rv = tls13_HandleClientHelloPart2(ss, &suites, sid, savedMsg, savedLen); + rv = tls13_HandleClientHelloPart2(ss, &suites, sid, + ss->ssl3.hs.echAccepted ? echInner->data : savedMsg, + ss->ssl3.hs.echAccepted ? echInner->len : savedLen); + SECITEM_FreeItem(echInner, PR_TRUE); + echInner = NULL; } else { rv = ssl3_HandleClientHelloPart2(ss, &suites, sid, savedMsg, savedLen); @@ -8956,6 +9118,7 @@ ssl3_HandleClientHello(sslSocket *ss, PRUint8 *b, PRUint32 length) (void)SSL3_SendAlert(ss, level, desc); /* FALLTHRU */ loser: + SECITEM_FreeItem(echInner, PR_TRUE); PORT_SetError(errCode); return SECFailure; } @@ -9402,6 +9565,8 @@ ssl3_HandleV2ClientHello(sslSocket *ss, unsigned char *buffer, unsigned int leng errCode = SSL_ERROR_UNSUPPORTED_VERSION; goto alert_loser; } + /* ECH not possible here. */ + ss->ssl3.hs.preliminaryInfo |= ssl_preinfo_ech; ss->ssl3.hs.preliminaryInfo |= ssl_preinfo_version; if (!ss->firstHsDone) { ssl_GetSpecWriteLock(ss); @@ -12114,6 +12279,21 @@ ssl_HashHandshakeMessage(sslSocket *ss, SSLHandshakeType ct, b, length, ssl3_UpdateHandshakeHashes); } +SECStatus +ssl_HashHandshakeMessageDefault(sslSocket *ss, SSLHandshakeType ct, + const PRUint8 *b, PRUint32 length) +{ + return ssl_HashHandshakeMessageInt(ss, ct, ss->ssl3.hs.recvMessageSeq, + b, length, ssl3_UpdateOuterHandshakeHashes); +} +SECStatus +ssl_HashHandshakeMessageEchInner(sslSocket *ss, SSLHandshakeType ct, + const PRUint8 *b, PRUint32 length) +{ + return ssl_HashHandshakeMessageInt(ss, ct, ss->ssl3.hs.recvMessageSeq, + b, length, ssl3_UpdateInnerHandshakeHashes); +} + SECStatus ssl_HashPostHandshakeMessage(sslSocket *ss, SSLHandshakeType ct, const PRUint8 *b, PRUint32 length) @@ -13246,6 +13426,7 @@ ssl3_InitState(sslSocket *ss) ssl3_ResetExtensionData(&ss->xtnData, ss); PR_INIT_CLIST(&ss->ssl3.hs.remoteExtensions); + PR_INIT_CLIST(&ss->ssl3.hs.echOuterExtensions); if (IS_DTLS(ss)) { ss->ssl3.hs.sendMessageSeq = 0; ss->ssl3.hs.recvMessageSeq = 0; @@ -13264,6 +13445,8 @@ ssl3_InitState(sslSocket *ss) ss->ssl3.hs.serverHsTrafficSecret = NULL; ss->ssl3.hs.clientTrafficSecret = NULL; ss->ssl3.hs.serverTrafficSecret = NULL; + ss->ssl3.hs.echHpkeCtx = NULL; + ss->ssl3.hs.echAccepted = PR_FALSE; PORT_Assert(!ss->ssl3.hs.messages.buf && !ss->ssl3.hs.messages.space); ss->ssl3.hs.messages.buf = NULL; @@ -13596,12 +13779,18 @@ ssl3_DestroySSL3Info(sslSocket *ss) if (ss->ssl3.hs.sha) { PK11_DestroyContext(ss->ssl3.hs.sha, PR_TRUE); } + if (ss->ssl3.hs.shaEchInner) { + PK11_DestroyContext(ss->ssl3.hs.shaEchInner, PR_TRUE); + } if (ss->ssl3.hs.shaPostHandshake) { PK11_DestroyContext(ss->ssl3.hs.shaPostHandshake, PR_TRUE); } if (ss->ssl3.hs.messages.buf) { sslBuffer_Clear(&ss->ssl3.hs.messages); } + if (ss->ssl3.hs.echInnerMessages.buf) { + sslBuffer_Clear(&ss->ssl3.hs.echInnerMessages); + } /* free the SSL3Buffer (msg_body) */ PORT_Free(ss->ssl3.hs.msg_body.buf); @@ -13620,6 +13809,7 @@ ssl3_DestroySSL3Info(sslSocket *ss) /* Destroy remote extensions */ ssl3_DestroyRemoteExtensions(&ss->ssl3.hs.remoteExtensions); + ssl3_DestroyRemoteExtensions(&ss->ssl3.hs.echOuterExtensions); ssl3_DestroyExtensionData(&ss->xtnData); /* Destroy cipher specs */ @@ -13653,6 +13843,10 @@ ssl3_DestroySSL3Info(sslSocket *ss) /* Destroy TLS 1.3 PSKs. */ tls13_DestroyPskList(&ss->ssl3.hs.psks); + + /* TLS 1.3 ECH state. */ + PK11_HPKE_DestroyContext(ss->ssl3.hs.echHpkeCtx, PR_TRUE); + PORT_Free((void *)ss->ssl3.hs.echPublicName); /* CONST */ } /* diff --git a/lib/ssl/ssl3ext.c b/lib/ssl/ssl3ext.c index 65a69450db..78c2b901a9 100644 --- a/lib/ssl/ssl3ext.c +++ b/lib/ssl/ssl3ext.c @@ -53,7 +53,6 @@ static const ssl3ExtensionHandler clientHelloHandlers[] = { { ssl_tls13_early_data_xtn, &tls13_ServerHandleEarlyDataXtn }, { ssl_tls13_psk_key_exchange_modes_xtn, &tls13_ServerHandlePskModesXtn }, { ssl_tls13_cookie_xtn, &tls13_ServerHandleCookieXtn }, - { ssl_tls13_encrypted_sni_xtn, &tls13_ServerHandleEsniXtn }, { ssl_tls13_post_handshake_auth_xtn, &tls13_ServerHandlePostHandshakeAuthXtn }, { ssl_record_size_limit_xtn, &ssl_HandleRecordSizeLimitXtn }, { 0, NULL } @@ -74,6 +73,7 @@ static const ssl3ExtensionHandler serverHelloHandlersTLS[] = { { ssl_tls13_key_share_xtn, &tls13_ClientHandleKeyShareXtn }, { ssl_tls13_pre_shared_key_xtn, &tls13_ClientHandlePreSharedKeyXtn }, { ssl_tls13_early_data_xtn, &tls13_ClientHandleEarlyDataXtn }, + { ssl_tls13_encrypted_client_hello_xtn, &tls13_ClientHandleEchXtn }, { ssl_record_size_limit_xtn, &ssl_HandleRecordSizeLimitXtn }, { 0, NULL } }; @@ -143,7 +143,6 @@ static const sslExtensionBuilder clientHelloSendersTLS[] = { ssl_signature_algorithms_xtn, &ssl3_SendSigAlgsXtn }, { ssl_tls13_cookie_xtn, &tls13_ClientSendHrrCookieXtn }, { ssl_tls13_psk_key_exchange_modes_xtn, &tls13_ClientSendPskModesXtn }, - { ssl_tls13_encrypted_sni_xtn, &tls13_ClientSendEsniXtn }, { ssl_tls13_post_handshake_auth_xtn, &tls13_ClientSendPostHandshakeAuthXtn }, { ssl_record_size_limit_xtn, &ssl_SendRecordSizeLimitXtn }, /* The pre_shared_key extension MUST be last. */ @@ -193,7 +192,8 @@ static const struct { { ssl_tls13_psk_key_exchange_modes_xtn, ssl_ext_native_only }, { ssl_tls13_ticket_early_data_info_xtn, ssl_ext_native_only }, { ssl_tls13_certificate_authorities_xtn, ssl_ext_native }, - { ssl_renegotiation_info_xtn, ssl_ext_native } + { ssl_renegotiation_info_xtn, ssl_ext_native }, + { ssl_tls13_encrypted_client_hello_xtn, ssl_ext_native_only }, }; static SSLExtensionSupport @@ -866,40 +866,25 @@ ssl_CalculatePaddingExtLen(const sslSocket *ss, unsigned int clientHelloLength) return extensionLen - 4; } -/* ssl3_SendPaddingExtension possibly adds an extension which ensures that a - * ClientHello record is either < 256 bytes or is >= 512 bytes. This ensures - * that we don't trigger bugs in F5 products. - * - * This takes an existing extension buffer, |buf|, and the length of the - * remainder of the ClientHello, |prefixLen|. It modifies the extension buffer - * to insert padding at the right place. - */ +/* Manually insert an extension, retaining the position of the PSK + * extension, if present. */ SECStatus -ssl_InsertPaddingExtension(const sslSocket *ss, unsigned int prefixLen, - sslBuffer *buf) +ssl3_EmplaceExtension(sslSocket *ss, sslBuffer *buf, PRUint16 exType, + const PRUint8 *data, unsigned int len, PRBool advertise) { - static unsigned char padding[252] = { 0 }; - unsigned int paddingLen; - unsigned int tailLen; SECStatus rv; - - /* Account for the size of the header, the length field of the extensions - * block and the size of the existing extensions. */ - paddingLen = ssl_CalculatePaddingExtLen(ss, prefixLen + 2 + buf->len); - if (!paddingLen) { - return SECSuccess; - } + unsigned int tailLen; /* Move the tail if there is one. This only happens if we are sending the * TLS 1.3 PSK extension, which needs to be at the end. */ if (ss->xtnData.lastXtnOffset) { PORT_Assert(buf->len > ss->xtnData.lastXtnOffset); tailLen = buf->len - ss->xtnData.lastXtnOffset; - rv = sslBuffer_Grow(buf, buf->len + 4 + paddingLen); + rv = sslBuffer_Grow(buf, buf->len + 4 + len); if (rv != SECSuccess) { return SECFailure; } - PORT_Memmove(buf->buf + ss->xtnData.lastXtnOffset + 4 + paddingLen, + PORT_Memmove(buf->buf + ss->xtnData.lastXtnOffset + 4 + len, buf->buf + ss->xtnData.lastXtnOffset, tailLen); buf->len = ss->xtnData.lastXtnOffset; @@ -907,20 +892,71 @@ ssl_InsertPaddingExtension(const sslSocket *ss, unsigned int prefixLen, tailLen = 0; } - rv = sslBuffer_AppendNumber(buf, ssl_padding_xtn, 2); + rv = sslBuffer_AppendNumber(buf, exType, 2); if (rv != SECSuccess) { return SECFailure; /* Code already set. */ } - rv = sslBuffer_AppendVariable(buf, padding, paddingLen, 2); + rv = sslBuffer_AppendVariable(buf, data, len, 2); if (rv != SECSuccess) { return SECFailure; /* Code already set. */ } + if (ss->xtnData.lastXtnOffset) { + ss->xtnData.lastXtnOffset += 4 + len; + } + buf->len += tailLen; + /* False only to retain behavior with padding_xtn. Maybe + * we can just mark that advertised as well? TODO */ + if (advertise) { + ss->xtnData.advertised[ss->xtnData.numAdvertised++] = exType; + } + return SECSuccess; } +/* ssl3_SendPaddingExtension possibly adds an extension which ensures that a + * ClientHello record is either < 256 bytes or is >= 512 bytes. This ensures + * that we don't trigger bugs in F5 products. + * + * This takes an existing extension buffer, |buf|, and the length of the + * remainder of the ClientHello, |prefixLen|. It modifies the extension buffer + * to insert padding at the right place. + */ +SECStatus +ssl_InsertPaddingExtension(sslSocket *ss, unsigned int prefixLen, + sslBuffer *buf) +{ + static unsigned char padding[252] = { 0 }; + unsigned int paddingLen; + /* Exit early if an application-provided extension hook + * already added padding. */ + if (ssl3_ExtensionAdvertised(ss, ssl_padding_xtn)) { + return SECSuccess; + } + + /* Account for the size of the header, the length field of the extensions + * block and the size of the existing extensions. */ + paddingLen = ssl_CalculatePaddingExtLen(ss, prefixLen + 2 + buf->len); + if (!paddingLen) { + return SECSuccess; + } + + return ssl3_EmplaceExtension(ss, buf, ssl_padding_xtn, padding, paddingLen, PR_FALSE); +} + +void +ssl3_MoveRemoteExtensions(PRCList *dst, PRCList *src) +{ + PRCList *cur_p; + while (!PR_CLIST_IS_EMPTY(src)) { + cur_p = PR_LIST_TAIL(src); + PR_REMOVE_LINK(cur_p); + PR_APPEND_LINK(cur_p, dst); + } +} + void ssl3_DestroyRemoteExtensions(PRCList *list) { @@ -982,9 +1018,14 @@ ssl3_DestroyExtensionData(TLSExtensionData *xtnData) xtnData->certReqAuthorities.arena = NULL; } PORT_Free(xtnData->advertised); - ssl_FreeEphemeralKeyPair(xtnData->esniPrivateKey); - SECITEM_FreeItem(&xtnData->keyShareExtension, PR_FALSE); 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; } /* Free everything that has been allocated and then reset back to diff --git a/lib/ssl/ssl3ext.h b/lib/ssl/ssl3ext.h index ff2f7c211b..45510041e9 100644 --- a/lib/ssl/ssl3ext.h +++ b/lib/ssl/ssl3ext.h @@ -9,10 +9,9 @@ #ifndef __ssl3ext_h_ #define __ssl3ext_h_ +#include "pk11hpke.h" #include "sslencode.h" -#define TLS13_ESNI_NONCE_SIZE 16 - typedef enum { sni_nametype_hostname } SNINameType; @@ -98,7 +97,8 @@ struct TLSExtensionDataStr { PRUint16 dtlsSRTPCipherSuite; /* 0 if not selected */ - unsigned int lastXtnOffset; /* Where to insert padding. 0 = end. */ + unsigned int lastXtnOffset; /* Where to insert any other extensions. + * 0 = end, otherwise base of PSK xtn. */ PRCList remoteKeyShares; /* The other side's public keys (TLS 1.3) */ /* The following are used by a TLS 1.3 server. */ @@ -114,14 +114,6 @@ struct TLSExtensionDataStr { /* The record size limit set by the peer. Our value is kept in ss->opt. */ PRUint16 recordSizeLimit; - /* ESNI working state */ - SECItem keyShareExtension; - ssl3CipherSuite esniSuite; - sslEphemeralKeyPair *esniPrivateKey; - /* Pointer into |ss->esniKeys->keyShares| */ - TLS13KeyShareEntry *peerEsniShare; - PRUint8 esniNonce[TLS13_ESNI_NONCE_SIZE]; - /* Delegated credentials. * * The delegated credential sent by the peer. Set by @@ -138,6 +130,14 @@ struct TLSExtensionDataStr { /* A non-owning reference to the selected PSKs. MUST NOT be freed directly, * 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. */ }; typedef struct TLSExtensionStr { @@ -165,6 +165,7 @@ SECStatus ssl3_HandleParsedExtensions(sslSocket *ss, TLSExtension *ssl3_FindExtension(sslSocket *ss, SSLExtensionType extension_type); void ssl3_DestroyRemoteExtensions(PRCList *list); +void ssl3_MoveRemoteExtensions(PRCList *dst, PRCList *src); void ssl3_InitExtensionData(TLSExtensionData *xtnData, const sslSocket *ss); void ssl3_DestroyExtensionData(TLSExtensionData *xtnData); void ssl3_ResetExtensionData(TLSExtensionData *xtnData, const sslSocket *ss); @@ -180,7 +181,9 @@ SECStatus ssl_ConstructExtensions(sslSocket *ss, sslBuffer *buf, SSLHandshakeType message); SECStatus ssl_SendEmptyExtension(const sslSocket *ss, TLSExtensionData *xtnData, sslBuffer *buf, PRBool *append); -SECStatus ssl_InsertPaddingExtension(const sslSocket *ss, unsigned int prefixLen, +SECStatus ssl3_EmplaceExtension(sslSocket *ss, sslBuffer *buf, PRUint16 exType, + const PRUint8 *data, unsigned int len, PRBool advertise); +SECStatus ssl_InsertPaddingExtension(sslSocket *ss, unsigned int prefixLen, sslBuffer *buf); /* Thunks to let us operate on const sslSocket* objects. */ diff --git a/lib/ssl/ssl3exthandle.c b/lib/ssl/ssl3exthandle.c index cb46982538..2f1ab56fe0 100644 --- a/lib/ssl/ssl3exthandle.c +++ b/lib/ssl/ssl3exthandle.c @@ -15,7 +15,7 @@ #include "selfencrypt.h" #include "ssl3ext.h" #include "ssl3exthandle.h" -#include "tls13esni.h" +#include "tls13ech.h" #include "tls13exthandle.h" /* For tls13_ServerSendStatusRequestXtn. */ PRBool @@ -42,13 +42,11 @@ ssl_ShouldSendSNIExtension(const sslSocket *ss, const char *url) */ SECStatus ssl3_ClientFormatServerNameXtn(const sslSocket *ss, const char *url, - TLSExtensionData *xtnData, + unsigned int len, TLSExtensionData *xtnData, sslBuffer *buf) { - unsigned int len; SECStatus rv; - len = PORT_Strlen(url); /* length of server_name_list */ rv = sslBuffer_AppendNumber(buf, len + 3, 2); if (rv != SECSuccess) { @@ -76,17 +74,15 @@ ssl3_ClientSendServerNameXtn(const sslSocket *ss, TLSExtensionData *xtnData, const char *url = ss->url; - /* We only make an ESNI private key if we are going to - * send ESNI. */ - if (ss->xtnData.esniPrivateKey != NULL) { - url = ss->esniKeys->dummySni; - } - if (!ssl_ShouldSendSNIExtension(ss, url)) { return SECSuccess; } - rv = ssl3_ClientFormatServerNameXtn(ss, url, xtnData, buf); + /* If ECH, write the public name. The real server name + * is emplaced while constructing CHInner extensions. */ + sslEchConfig *cfg = (sslEchConfig *)PR_LIST_HEAD(&ss->echConfigs); + const char *sniContents = PR_CLIST_IS_EMPTY(&ss->echConfigs) ? url : cfg->contents.publicName; + rv = ssl3_ClientFormatServerNameXtn(ss, sniContents, strlen(sniContents), xtnData, buf); if (rv != SECSuccess) { return SECFailure; } @@ -107,13 +103,6 @@ ssl3_HandleServerNameXtn(const sslSocket *ss, TLSExtensionData *xtnData, return SECSuccess; /* ignore extension */ } - if (ssl3_ExtensionNegotiated(ss, ssl_tls13_encrypted_sni_xtn)) { - /* If we already have ESNI, make sure we don't overwrite - * the value. */ - PORT_Assert(ss->version >= SSL_LIBRARY_VERSION_TLS_1_3); - return SECSuccess; - } - /* Server side - consume client data and register server sender. */ /* do not parse the data if don't have user extension handling function. */ if (!ss->sniSocketConfig) { @@ -323,11 +312,11 @@ ssl3_SelectAppProtocol(const sslSocket *ss, TLSExtensionData *xtnData, } PORT_Assert(ss->nextProtoCallback); - /* The cipher suite isn't selected yet. Note that extensions + /* Neither the cipher suite nor ECH are selected yet Note that extensions * sometimes affect what cipher suite is selected, e.g., for ECC. */ PORT_Assert((ss->ssl3.hs.preliminaryInfo & - ssl_preinfo_all & ~ssl_preinfo_cipher_suite) == - (ssl_preinfo_all & ~ssl_preinfo_cipher_suite)); + ssl_preinfo_all & ~ssl_preinfo_cipher_suite & ~ssl_preinfo_ech) == + (ssl_preinfo_all & ~ssl_preinfo_cipher_suite & ~ssl_preinfo_ech)); /* The callback has to make sure that either rv != SECSuccess or that result * is not set if there is no common protocol. */ rv = ss->nextProtoCallback(ss->nextProtoArg, ss->fd, data->data, data->len, diff --git a/lib/ssl/ssl3exthandle.h b/lib/ssl/ssl3exthandle.h index 3e9b418cfe..654b90de8c 100644 --- a/lib/ssl/ssl3exthandle.h +++ b/lib/ssl/ssl3exthandle.h @@ -93,7 +93,7 @@ SECStatus ssl3_ProcessSessionTicketCommon(sslSocket *ss, const SECItem *ticket, /* out */ SECItem *appToken); PRBool ssl_ShouldSendSNIExtension(const sslSocket *ss, const char *url); SECStatus ssl3_ClientFormatServerNameXtn(const sslSocket *ss, const char *url, - TLSExtensionData *xtnData, + unsigned int len, TLSExtensionData *xtnData, sslBuffer *buf); SECStatus ssl3_ClientSendServerNameXtn(const sslSocket *ss, TLSExtensionData *xtnData, diff --git a/lib/ssl/ssl3prot.h b/lib/ssl/ssl3prot.h index edf4592903..b4c5a878ac 100644 --- a/lib/ssl/ssl3prot.h +++ b/lib/ssl/ssl3prot.h @@ -76,6 +76,7 @@ typedef enum { bad_certificate_hash_value = 114, certificate_required = 116, no_application_protocol = 120, + ech_required = 121, /* invalid alert */ no_alert = 256 diff --git a/lib/ssl/sslencode.c b/lib/ssl/sslencode.c index e7f1ae7b9d..ea6f41e4b9 100644 --- a/lib/ssl/sslencode.c +++ b/lib/ssl/sslencode.c @@ -264,8 +264,8 @@ sslRead_ReadNumber(sslReader *reader, unsigned int bytes, PRUint64 *num) #define MAX_SEND_BUF_LENGTH 32000 /* watch for 16-bit integer overflow */ #define MIN_SEND_BUF_LENGTH 4000 -SECStatus -ssl3_AppendHandshake(sslSocket *ss, const void *void_src, unsigned int bytes) +static SECStatus +ssl3_AppendHandshakeInternal(sslSocket *ss, const void *void_src, unsigned int bytes, PRBool suppressHash) { unsigned char *src = (unsigned char *)void_src; int room = ss->sec.ci.sendBuf.space - ss->sec.ci.sendBuf.len; @@ -284,7 +284,8 @@ ssl3_AppendHandshake(sslSocket *ss, const void *void_src, unsigned int bytes) } PRINT_BUF(60, (ss, "Append to Handshake", (unsigned char *)void_src, bytes)); - if (!ss->firstHsDone || ss->version < SSL_LIBRARY_VERSION_TLS_1_3) { + // TODO: Move firstHsDone and version check into callers as a suppression. + if (!suppressHash && (!ss->firstHsDone || ss->version < SSL_LIBRARY_VERSION_TLS_1_3)) { rv = ssl3_UpdateHandshakeHashes(ss, src, bytes); if (rv != SECSuccess) return SECFailure; /* error code set by ssl3_UpdateHandshakeHashes */ @@ -309,6 +310,18 @@ ssl3_AppendHandshake(sslSocket *ss, const void *void_src, unsigned int bytes) return SECSuccess; } +SECStatus +ssl3_AppendHandshakeSuppressHash(sslSocket *ss, const void *void_src, unsigned int bytes) +{ + return ssl3_AppendHandshakeInternal(ss, void_src, bytes, PR_TRUE); +} + +SECStatus +ssl3_AppendHandshake(sslSocket *ss, const void *void_src, unsigned int bytes) +{ + return ssl3_AppendHandshakeInternal(ss, void_src, bytes, PR_FALSE); +} + SECStatus ssl3_AppendHandshakeNumber(sslSocket *ss, PRUint64 num, unsigned int lenSize) { diff --git a/lib/ssl/sslencode.h b/lib/ssl/sslencode.h index 8223169aad..e87bace495 100644 --- a/lib/ssl/sslencode.h +++ b/lib/ssl/sslencode.h @@ -27,6 +27,10 @@ typedef struct sslBufferStr { { \ b, 0, maxlen, PR_TRUE \ } +#define SSL_BUFFER_FIXED_LEN(b, len) \ + { \ + b, len, 0, PR_TRUE \ + } #define SSL_BUFFER(b) SSL_BUFFER_FIXED(b, sizeof(b)) #define SSL_BUFFER_BASE(b) ((b)->buf) #define SSL_BUFFER_LEN(b) ((b)->len) @@ -51,6 +55,8 @@ void sslBuffer_Clear(sslBuffer *b); SECStatus ssl3_AppendHandshake(sslSocket *ss, const void *void_src, unsigned int bytes); +SECStatus ssl3_AppendHandshakeSuppressHash(sslSocket *ss, const void *void_src, + unsigned int bytes); SECStatus ssl3_AppendHandshakeHeader(sslSocket *ss, SSLHandshakeType t, unsigned int length); SECStatus ssl3_AppendHandshakeNumber(sslSocket *ss, PRUint64 num, diff --git a/lib/ssl/sslerr.h b/lib/ssl/sslerr.h index eb8f7c2da2..837fca7ba1 100644 --- a/lib/ssl/sslerr.h +++ b/lib/ssl/sslerr.h @@ -276,8 +276,21 @@ typedef enum { SSL_ERROR_DC_EXPIRED = (SSL_ERROR_BASE + 185), SSL_ERROR_DC_INAPPROPRIATE_VALIDITY_PERIOD = (SSL_ERROR_BASE + 186), SSL_ERROR_FEATURE_DISABLED = (SSL_ERROR_BASE + 187), + /* ECH rejected, public name authentication succeeded, + * and at least one of the retry_configs is compatible. */ + SSL_ERROR_ECH_RETRY_WITH_ECH = (SSL_ERROR_BASE + 188), + /* ECH rejected, public name authentication succeeded, + * but none of the retry_configs are compatible. */ + SSL_ERROR_ECH_RETRY_WITHOUT_ECH = (SSL_ERROR_BASE + 189), + /* ECH rejected and public name authentication failed. */ + SSL_ERROR_ECH_FAILED = (SSL_ERROR_BASE + 190), + SSL_ERROR_ECH_REQUIRED_ALERT = (SSL_ERROR_BASE + 191), SSL_ERROR_END_OF_LIST /* let the c compiler determine the value of this. */ } SSLErrorCodes; + +#define SSL_ERROR_RX_MALFORMED_ECH_CONFIG SSL_ERROR_RX_MALFORMED_ESNI_KEYS +#define SSL_ERROR_RX_MALFORMED_ECH_EXTENSION SSL_ERROR_RX_MALFORMED_ESNI_EXTENSION +#define SSL_ERROR_MISSING_ECH_EXTENSION SSL_ERROR_MISSING_ESNI_EXTENSION #endif /* NO_SECURITY_ERROR_ENUM */ /* clang-format on */ diff --git a/lib/ssl/sslexp.h b/lib/ssl/sslexp.h index 8a92a39ad9..a02f0f3516 100644 --- a/lib/ssl/sslexp.h +++ b/lib/ssl/sslexp.h @@ -502,63 +502,86 @@ typedef SECStatus(PR_CALLBACK *SSLResumptionTokenCallback)( (PRFileDesc * _fd, PRUint32 _size), \ (fd, size)) -/* Set the ESNI key pair on a socket (server side) +/* If |enabled|, a GREASE ECH extension will be sent in every ClientHello, + * unless a valid and supported ECHConfig is configured to the socket + * (in which case real ECH takes precedence). If |!enabled|, it is not sent.*/ +#define SSL_EnableTls13GreaseEch(fd, enabled) \ + SSL_EXPERIMENTAL_API("SSL_EnableTls13GreaseEch", \ + (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. + * These configs MUST NOT be used for more than the single retry + * attempt. Subsequent connections MUST use advertised ECHConfigs. */ +#define SSL_GetEchRetryConfigs(fd, out) \ + SSL_EXPERIMENTAL_API("SSL_GetEchRetryConfigs", \ + (PRFileDesc * _fd, \ + SECItem * _out), \ + (fd, out)) + +/* Called to remove all ECHConfigs from a socket (fd). */ +#define SSL_RemoveEchConfigs(fd) \ + SSL_EXPERIMENTAL_API("SSL_RemoveEchConfigs", \ + (PRFileDesc * _fd), \ + (fd)) + +/* Set the ECHConfig and key pair on a socket (server side) * * fd -- the socket + * pubKey -- the server's SECKEYPublicKey for HPKE/ECH. + * privateKey -- the server's SECKEYPrivateKey for HPKE/ECH. * record/recordLen -- the encoded DNS record (not base64) - * - * Important: the suites that are advertised in the record must - * be configured on, or this call will fail. */ -#define SSL_SetESNIKeyPair(fd, \ - privKey, record, recordLen) \ - SSL_EXPERIMENTAL_API("SSL_SetESNIKeyPair", \ +#define SSL_SetServerEchConfigs(fd, pubKey, \ + privKey, record, recordLen) \ + SSL_EXPERIMENTAL_API("SSL_SetServerEchConfigs", \ (PRFileDesc * _fd, \ - SECKEYPrivateKey * _privKey, \ + const SECKEYPublicKey *_pubKey, \ + const SECKEYPrivateKey *_privKey, \ const PRUint8 *_record, unsigned int _recordLen), \ - (fd, privKey, \ + (fd, pubKey, privKey, \ record, recordLen)) -/* Set the ESNI keys on a client +/* Set ECHConfig(s) on a client. The first supported ECHConfig will be used. * * fd -- the socket - * ensikeys/esniKeysLen -- the ESNI key structure (not base64) - * dummyESNI -- the dummy ESNI to use (if any) + * echConfigs/echConfigsLen -- the ECHConfigs structure (not base64) */ -#define SSL_EnableESNI(fd, esniKeys, esniKeysLen, dummySNI) \ - SSL_EXPERIMENTAL_API("SSL_EnableESNI", \ - (PRFileDesc * _fd, \ - const PRUint8 *_esniKeys, \ - unsigned int _esniKeysLen, \ - const char *_dummySNI), \ - (fd, esniKeys, esniKeysLen, dummySNI)) +#define SSL_SetClientEchConfigs(fd, echConfigs, echConfigsLen) \ + SSL_EXPERIMENTAL_API("SSL_SetClientEchConfigs", \ + (PRFileDesc * _fd, \ + const PRUint8 *_echConfigs, \ + unsigned int _echConfigsLen), \ + (fd, echConfigs, echConfigsLen)) /* - * Generate an encoded ESNIKeys structure (presumably server side). + * Generate an encoded ECHConfig structure (presumably server side). * - * cipherSuites -- the cipher suites that can be used - * cipherSuitesCount -- the number of suites in cipherSuites + * publicName -- the public_name value to be placed in SNI. + * hpkeSuites -- the HPKE cipher suites that can be used + * hpkeSuitesCount -- the number of suites in hpkeSuites + * kemId -- the HKPE KEM ID value * group -- the named group this key corresponds to * pubKey -- the public key for the key pair - * pad -- the length to pad to - * notBefore/notAfter -- validity range in seconds since epoch + * pad -- the maximum length to pad to * out/outlen/maxlen -- where to output the data */ -#define SSL_EncodeESNIKeys(cipherSuites, cipherSuiteCount, \ - group, pubKey, pad, notBefore, notAfter, \ - out, outlen, maxlen) \ - SSL_EXPERIMENTAL_API("SSL_EncodeESNIKeys", \ - (PRUint16 * _cipherSuites, \ - unsigned int _cipherSuiteCount, \ - SSLNamedGroup _group, \ - SECKEYPublicKey *_pubKey, \ - PRUint16 _pad, \ - PRUint64 _notBefore, PRUint64 _notAfter, \ - PRUint8 *_out, unsigned int *_outlen, \ - unsigned int _maxlen), \ - (cipherSuites, cipherSuiteCount, \ - group, pubKey, pad, notBefore, notAfter, \ - out, outlen, maxlen)) +#define SSL_EncodeEchConfig(publicName, hpkeSuites, hpkeSuitesCount, \ + kemId, pubKey, maxNameLen, out, outlen, \ + maxlen) \ + SSL_EXPERIMENTAL_API("SSL_EncodeEchConfig", \ + (const char *_publicName, \ + const PRUint32 *_hpkeSuites, \ + unsigned int _hpkeSuitesCount, \ + HpkeKemId _kemId, \ + const SECKEYPublicKey *_pubKey, \ + PRUint16 _maxNameLen, \ + PRUint8 *_out, unsigned int *_outlen, \ + unsigned int _maxlen), \ + (publicName, hpkeSuites, hpkeSuitesCount, \ + kemId, pubKey, maxNameLen, out, outlen, \ + maxlen)) /* SSL_SetSecretCallback installs a callback that TLS calls when it installs new * traffic secrets. @@ -1001,6 +1024,9 @@ typedef struct SSLMaskingContextStr { #define SSL_UseAltServerHelloType(fd, enable) SSL_DEPRECATED_EXPERIMENTAL_API #define SSL_SetupAntiReplay(a, b, c) SSL_DEPRECATED_EXPERIMENTAL_API #define SSL_InitAntiReplay(a, b, c) SSL_DEPRECATED_EXPERIMENTAL_API +#define SSL_EnableESNI(a, b, c, d) SSL_DEPRECATED_EXPERIMENTAL_API +#define SSL_EncodeESNIKeys(a, b, c, d, e, f, g, h, i, j) SSL_DEPRECATED_EXPERIMENTAL_API +#define SSL_SetESNIKeyPair(a, b, c, d) SSL_DEPRECATED_EXPERIMENTAL_API SEC_END_PROTOS diff --git a/lib/ssl/sslimpl.h b/lib/ssl/sslimpl.h index 35d0c2d6bc..a126cb8c3b 100644 --- a/lib/ssl/sslimpl.h +++ b/lib/ssl/sslimpl.h @@ -37,6 +37,8 @@ typedef struct sslSocketStr sslSocket; typedef struct sslNamedGroupDefStr sslNamedGroupDef; typedef struct sslEsniKeysStr sslEsniKeys; +typedef struct sslEchConfigStr sslEchConfig; +typedef struct sslEchConfigContentsStr sslEchConfigContents; typedef struct sslPskStr sslPsk; typedef struct sslDelegatedCredentialStr sslDelegatedCredential; typedef struct sslEphemeralKeyPairStr sslEphemeralKeyPair; @@ -284,6 +286,7 @@ typedef struct sslOptionsStr { unsigned int enableDelegatedCredentials : 1; unsigned int enableDtls13VersionCompat : 1; unsigned int suppressEndOfEarlyData : 1; + unsigned int enableTls13GreaseEch : 1; } sslOptions; typedef enum { sslHandshakingUndetermined = 0, @@ -611,17 +614,26 @@ typedef struct { typedef struct SSL3HandshakeStateStr { SSL3Random server_random; SSL3Random client_random; - SSL3WaitState ws; /* May also contain SSL3WaitState | 0x80 for TLS 1.3 */ + SSL3Random client_inner_random; /* TLS 1.3 ECH Inner. */ + SSL3WaitState ws; /* May also contain SSL3WaitState | 0x80 for TLS 1.3 */ /* This group of members is used for handshake running hashes. */ SSL3HandshakeHashType hashType; - sslBuffer messages; /* Accumulated handshake messages */ + sslBuffer messages; /* Accumulated handshake messages */ + sslBuffer echInnerMessages; /* Accumulated ECH Inner handshake messages */ /* PKCS #11 mode: * SSL 3.0 - TLS 1.1 use both |md5| and |sha|. |md5| is used for MD5 and * |sha| for SHA-1. - * TLS 1.2 and later use only |sha|, for SHA-256. */ + * TLS 1.2 and later use only |sha| variants, for SHA-256. + * Under normal (non-1.3 ECH) handshakes, only |sha| and |shaPostHandshake| + * are used. When doing 1.3 ECH, |sha| contains the transcript hash + * corresponding to the outer Client Hello. To facilitate secure retry and + * disablement, |shaEchInner|, tracks, in parallel, the transcript hash + * corresponding to the inner Client Hello. Once we process the SH + * extensions, coalesce into |sha|. */ PK11Context *md5; PK11Context *sha; + PK11Context *shaEchInner; PK11Context *shaPostHandshake; SSLSignatureScheme signatureScheme; const ssl3KEADef *kea_def; @@ -662,7 +674,8 @@ typedef struct SSL3HandshakeStateStr { PRUint32 preliminaryInfo; /* Parsed extensions */ - PRCList remoteExtensions; /* Parsed incoming extensions */ + PRCList remoteExtensions; /* Parsed incoming extensions */ + PRCList echOuterExtensions; /* If ECH, hold CHOuter extensions for decompression. */ /* This group of values is used for DTLS */ PRUint16 sendMessageSeq; /* The sending message sequence @@ -717,6 +730,7 @@ typedef struct SSL3HandshakeStateStr { * we use for TLS 1.3 */ PRUint16 ticketNonce; /* A counter we use for tickets. */ SECItem fakeSid; /* ... (server) the SID the client used. */ + PRCList psks; /* A list of PSKs, resumption and/or external. */ /* rttEstimate is used to guess the round trip time between server and client. * When the server sends ServerHello it sets this to the current time. @@ -729,13 +743,19 @@ typedef struct SSL3HandshakeStateStr { PRCList dtlsRcvdHandshake; /* Handshake records we have received * used to generate ACKs. */ - PRCList psks; /* A list of PSKs, resumption and/or external. */ + /* TLS 1.3 ECH state. */ + PRBool echAccepted; /* Client/Server: True if we've commited to using CHInner. */ + HpkeContext *echHpkeCtx; /* Client/Server: HPKE context for ECH. */ + const char *echPublicName; /* Client: If rejected, the ECHConfig.publicName to + * use for certificate verification. */ + } SSL3HandshakeState; #define SSL_ASSERT_HASHES_EMPTY(ss) \ do { \ PORT_Assert(ss->ssl3.hs.hashType == handshake_hash_unknown); \ PORT_Assert(ss->ssl3.hs.messages.len == 0); \ + PORT_Assert(ss->ssl3.hs.echInnerMessages.len == 0); \ } while (0) /* @@ -1101,9 +1121,10 @@ struct sslSocketStr { /* Whether we are doing stream or datagram mode */ SSLProtocolVariant protocolVariant; - /* The information from the ESNI keys record - * (also the private key for the server). */ - sslEsniKeys *esniKeys; + /* 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 */ + SECKEYPrivateKey *echPrivKey; /* As above. */ /* Anti-replay for TLS 1.3 0-RTT. */ SSLAntiReplayContext *antiReplay; @@ -1262,6 +1283,10 @@ ssl_HashHandshakeMessageInt(sslSocket *ss, SSLHandshakeType type, sslUpdateHandshakeHashes cb); SECStatus ssl_HashHandshakeMessage(sslSocket *ss, SSLHandshakeType type, const PRUint8 *b, PRUint32 length); +SECStatus ssl_HashHandshakeMessageEchInner(sslSocket *ss, SSLHandshakeType type, + const PRUint8 *b, PRUint32 length); +SECStatus ssl_HashHandshakeMessageDefault(sslSocket *ss, SSLHandshakeType type, + const PRUint8 *b, PRUint32 length); SECStatus ssl_HashPostHandshakeMessage(sslSocket *ss, SSLHandshakeType type, const PRUint8 *b, PRUint32 length); @@ -1438,6 +1463,11 @@ extern SECStatus ssl3_AuthCertificateComplete(sslSocket *ss, PRErrorCode error); extern SECStatus ssl3_HandleV2ClientHello( sslSocket *ss, unsigned char *buffer, unsigned int length, PRUint8 padding); +SECStatus +ssl3_CreateClientHelloPreamble(sslSocket *ss, const sslSessionID *sid, + PRBool realSid, PRUint16 version, PRBool isEchInner, + const sslBuffer *extensions, sslBuffer *preamble); +SECStatus ssl3_InsertChHeaderSize(const sslSocket *ss, sslBuffer *preamble, const sslBuffer *extensions); SECStatus ssl3_SendClientHello(sslSocket *ss, sslClientHelloType type); /* @@ -1679,6 +1709,7 @@ SECStatus ssl3_NegotiateCipherSuiteInner(sslSocket *ss, const SECItem *suites, SECStatus ssl3_NegotiateCipherSuite(sslSocket *ss, const SECItem *suites, PRBool initHashes); SECStatus ssl3_InitHandshakeHashes(sslSocket *ss); +void ssl3_CoalesceEchHandshakeHashes(sslSocket *ss); SECStatus ssl3_ServerCallSNICallback(sslSocket *ss); SECStatus ssl3_FlushHandshake(sslSocket *ss, PRInt32 flags); SECStatus ssl3_CompleteHandleCertificate(sslSocket *ss, @@ -1725,6 +1756,7 @@ SECStatus ssl_CreateECDHEphemeralKeyPair(const sslSocket *ss, SECStatus ssl_CreateStaticECDHEKey(sslSocket *ss, const sslNamedGroupDef *ecGroup); SECStatus ssl3_FlushHandshake(sslSocket *ss, PRInt32 flags); +SECStatus ssl3_GetNewRandom(SSL3Random random); PK11SymKey *ssl3_GetWrappingKey(sslSocket *ss, PK11SlotInfo *masterSecretSlot, CK_MECHANISM_TYPE masterWrapMech, @@ -1914,6 +1946,8 @@ SECStatus SSLExp_CreateMask(SSLMaskingContext *ctx, const PRUint8 *sample, SECStatus SSLExp_DestroyMaskingContext(SSLMaskingContext *ctx); +SECStatus SSLExp_EnableTls13GreaseEch(PRFileDesc *fd, PRBool enabled); + SEC_END_PROTOS #if defined(XP_UNIX) || defined(XP_OS2) || defined(XP_BEOS) diff --git a/lib/ssl/sslinfo.c b/lib/ssl/sslinfo.c index a92ed16040..65aafe94a5 100644 --- a/lib/ssl/sslinfo.c +++ b/lib/ssl/sslinfo.c @@ -89,6 +89,7 @@ SSL_GetChannelInfo(PRFileDesc *fd, SSLChannelInfo *info, PRUintn len) inf.pskType = ssl_psk_none; } inf.peerDelegCred = tls13_IsVerifyingWithDelegatedCredential(ss); + inf.echAccepted = ss->ssl3.hs.echAccepted; if (sid) { unsigned int sidLen; @@ -171,6 +172,9 @@ SSL_GetPreliminaryChannelInfo(PRFileDesc *fd, inf.peerDelegCred = tls13_IsVerifyingWithDelegatedCredential(ss); inf.authKeyBits = ss->sec.authKeyBits; inf.signatureScheme = ss->sec.signatureScheme; + inf.echAccepted = ss->ssl3.hs.echAccepted; + /* Only expose this if the application should use it for verification. */ + inf.echPublicName = (inf.echAccepted == PR_FALSE) ? ss->ssl3.hs.echPublicName : NULL; memcpy(info, &inf, inf.length); return SECSuccess; diff --git a/lib/ssl/sslsecur.c b/lib/ssl/sslsecur.c index ef978c90ae..162fc66d0c 100644 --- a/lib/ssl/sslsecur.c +++ b/lib/ssl/sslsecur.c @@ -173,9 +173,18 @@ SSL_ResetHandshake(PRFileDesc *s, PRBool asServer) ssl_Release1stHandshakeLock(ss); ssl3_DestroyRemoteExtensions(&ss->ssl3.hs.remoteExtensions); + ssl3_DestroyRemoteExtensions(&ss->ssl3.hs.echOuterExtensions); ssl3_ResetExtensionData(&ss->xtnData, ss); tls13_ResetHandshakePsks(ss, &ss->ssl3.hs.psks); + if (ss->ssl3.hs.echHpkeCtx) { + PK11_HPKE_DestroyContext(ss->ssl3.hs.echHpkeCtx, PR_TRUE); + ss->ssl3.hs.echHpkeCtx = NULL; + PORT_Assert(ss->ssl3.hs.echPublicName); + PORT_Free((void *)ss->ssl3.hs.echPublicName); /* CONST */ + ss->ssl3.hs.echPublicName = NULL; + } + if (!ss->TCPconnected) ss->TCPconnected = (PR_SUCCESS == ssl_DefGetpeername(ss, &addr)); diff --git a/lib/ssl/sslsock.c b/lib/ssl/sslsock.c index 695f39c50b..e075e23c81 100644 --- a/lib/ssl/sslsock.c +++ b/lib/ssl/sslsock.c @@ -19,7 +19,7 @@ #include "nss.h" #include "pk11pqg.h" #include "pk11pub.h" -#include "tls13esni.h" +#include "tls13ech.h" #include "tls13psk.h" #include "tls13subcerts.h" @@ -92,7 +92,8 @@ static sslOptions ssl_defaults = { .enableHelloDowngradeCheck = PR_FALSE, .enableV2CompatibleHello = PR_FALSE, .enablePostHandshakeAuth = PR_FALSE, - .suppressEndOfEarlyData = PR_FALSE + .suppressEndOfEarlyData = PR_FALSE, + .enableTls13GreaseEch = PR_FALSE }; /* @@ -371,12 +372,18 @@ ssl_DupSocket(sslSocket *os) ss->resumptionTokenCallback = os->resumptionTokenCallback; ss->resumptionTokenContext = os->resumptionTokenContext; - if (os->esniKeys) { - ss->esniKeys = tls13_CopyESNIKeys(os->esniKeys); - if (!ss->esniKeys) { + rv = tls13_CopyEchConfigs(&os->echConfigs, &ss->echConfigs); + if (rv != SECSuccess) { + goto loser; + } + if (os->echPrivKey && os->echPubKey) { + ss->echPrivKey = SECKEY_CopyPrivateKey(os->echPrivKey); + ss->echPubKey = SECKEY_CopyPublicKey(os->echPubKey); + if (!ss->echPrivKey || !ss->echPubKey) { goto loser; } } + if (os->antiReplay) { ss->antiReplay = tls13_RefAntiReplayContext(os->antiReplay); PORT_Assert(ss->antiReplay); /* Can't fail. */ @@ -478,13 +485,13 @@ ssl_DestroySocketContents(sslSocket *ss) ssl_ClearPRCList(&ss->ssl3.hs.dtlsRcvdHandshake, NULL); tls13_DestroyPskList(&ss->ssl3.hs.psks); - tls13_DestroyESNIKeys(ss->esniKeys); tls13_ReleaseAntiReplayContext(ss->antiReplay); - if (ss->psk) { - tls13_DestroyPsk(ss->psk); - ss->psk = NULL; - } + tls13_DestroyPsk(ss->psk); + + tls13_DestroyEchConfigs(&ss->echConfigs); + SECKEY_DestroyPrivateKey(ss->echPrivKey); + SECKEY_DestroyPublicKey(ss->echPubKey); } /* @@ -2378,6 +2385,7 @@ SSL_ReconfigFD(PRFileDesc *model, PRFileDesc *fd) { sslSocket *sm = NULL, *ss = NULL; PRCList *cursor; + SECStatus rv; if (model == NULL) { PR_SetError(SEC_ERROR_INVALID_ARGS, 0); @@ -2447,7 +2455,6 @@ SSL_ReconfigFD(PRFileDesc *model, PRFileDesc *fd) for (cursor = PR_NEXT_LINK(&sm->extensionHooks); cursor != &sm->extensionHooks; cursor = PR_NEXT_LINK(cursor)) { - SECStatus rv; sslCustomExtensionHooks *hook = (sslCustomExtensionHooks *)cursor; rv = SSL_InstallExtensionHooks(ss->fd, hook->type, hook->writer, hook->writerArg, @@ -2473,12 +2480,19 @@ SSL_ReconfigFD(PRFileDesc *model, PRFileDesc *fd) } } - /* Copy ESNI. */ - tls13_DestroyESNIKeys(ss->esniKeys); - ss->esniKeys = NULL; - if (sm->esniKeys) { - ss->esniKeys = tls13_CopyESNIKeys(sm->esniKeys); - if (!ss->esniKeys) { + /* Copy ECH. */ + tls13_DestroyEchConfigs(&ss->echConfigs); + SECKEY_DestroyPrivateKey(ss->echPrivKey); + SECKEY_DestroyPublicKey(ss->echPubKey); + rv = tls13_CopyEchConfigs(&sm->echConfigs, &ss->echConfigs); + if (rv != SECSuccess) { + return NULL; + } + if (sm->echPrivKey && sm->echPubKey) { + /* Might be client (no keys). */ + ss->echPrivKey = SECKEY_CopyPrivateKey(sm->echPrivKey); + ss->echPubKey = SECKEY_CopyPublicKey(sm->echPubKey); + if (!ss->echPrivKey || !ss->echPubKey) { return NULL; } } @@ -4161,6 +4175,7 @@ ssl_NewSocket(PRBool makeLocks, SSLProtocolVariant protocolVariant) PR_INIT_CLIST(&ss->serverCerts); PR_INIT_CLIST(&ss->ephemeralKeyPairs); PR_INIT_CLIST(&ss->extensionHooks); + PR_INIT_CLIST(&ss->echConfigs); ss->dbHandle = CERT_GetDefaultCertDB(); @@ -4194,7 +4209,8 @@ ssl_NewSocket(PRBool makeLocks, SSLProtocolVariant protocolVariant) PR_INIT_CLIST(&ss->ssl3.hs.psks); dtls_InitTimers(ss); - ss->esniKeys = NULL; + ss->echPrivKey = NULL; + ss->echPubKey = NULL; ss->antiReplay = NULL; ss->psk = NULL; @@ -4277,9 +4293,10 @@ struct { EXP(DestroyAead), EXP(DestroyMaskingContext), EXP(DestroyResumptionTokenInfo), - EXP(EnableESNI), - EXP(EncodeESNIKeys), + EXP(EnableTls13GreaseEch), + EXP(EncodeEchConfig), EXP(GetCurrentEpoch), + EXP(GetEchRetryConfigs), EXP(GetExtensionSupport), EXP(GetResumptionTokenInfo), EXP(HelloRetryRequestCallback), @@ -4295,16 +4312,18 @@ struct { EXP(RecordLayerData), EXP(RecordLayerWriteCallback), EXP(ReleaseAntiReplayContext), + EXP(RemoveEchConfigs), EXP(RemoveExternalPsk), EXP(SecretCallback), EXP(SendCertificateRequest), EXP(SendSessionTicket), EXP(SetAntiReplayContext), + EXP(SetClientEchConfigs), EXP(SetDtls13VersionWorkaround), - EXP(SetESNIKeyPair), EXP(SetMaxEarlyDataSize), EXP(SetResumptionTokenCallback), EXP(SetResumptionToken), + EXP(SetServerEchConfigs), EXP(SetTimeFunc), #endif { "", NULL } @@ -4341,6 +4360,17 @@ ssl_ClearPRCList(PRCList *list, void (*f)(void *)) } } +SECStatus +SSLExp_EnableTls13GreaseEch(PRFileDesc *fd, PRBool enabled) +{ + sslSocket *ss = ssl_FindSocket(fd); + if (!ss) { + return SECFailure; + } + ss->opt.enableTls13GreaseEch = enabled; + return SECSuccess; +} + SECStatus SSLExp_SetDtls13VersionWorkaround(PRFileDesc *fd, PRBool enabled) { diff --git a/lib/ssl/sslt.h b/lib/ssl/sslt.h index eaf4133e3c..f1713069bd 100644 --- a/lib/ssl/sslt.h +++ b/lib/ssl/sslt.h @@ -368,6 +368,11 @@ typedef struct SSLChannelInfoStr { /* Indicates what type of PSK, if any, was used in a handshake. */ SSLPskType pskType; + /* The following fields were added in NSS 3.60 */ + /* This field is PR_TRUE when the connection is established + * with TLS 1.3 Encrypted Client Hello. */ + PRBool echAccepted; + /* When adding new fields to this structure, please document the * NSS version in which they were added. */ } SSLChannelInfo; @@ -376,12 +381,13 @@ typedef struct SSLChannelInfoStr { #define ssl_preinfo_version (1U << 0) #define ssl_preinfo_cipher_suite (1U << 1) #define ssl_preinfo_0rtt_cipher_suite (1U << 2) -/* ssl_preinfo_peer_auth covers peerDelegCred, authKeyBits, and scheme. Not - * included in ssl_preinfo_all as it is client-only. */ +/* ssl_preinfo_peer_auth covers peerDelegCred, authKeyBits, + * and scheme. Not included in ssl_preinfo_all as it is client-only. */ #define ssl_preinfo_peer_auth (1U << 3) +#define ssl_preinfo_ech (1U << 4) /* ssl_preinfo_all doesn't contain ssl_preinfo_0rtt_cipher_suite because that * field is only set if 0-RTT is sent (client) or accepted (server). */ -#define ssl_preinfo_all (ssl_preinfo_version | ssl_preinfo_cipher_suite) +#define ssl_preinfo_all (ssl_preinfo_version | ssl_preinfo_cipher_suite | ssl_preinfo_ech) typedef struct SSLPreliminaryChannelInfoStr { /* On return, SSL_GetPreliminaryChannelInfo sets |length| to the smaller of @@ -429,6 +435,12 @@ typedef struct SSLPreliminaryChannelInfoStr { PRUint32 authKeyBits; SSLSignatureScheme signatureScheme; + /* The following fields were added in NSS 3.60. */ + PRBool echAccepted; + /* If the application configured ECH but |!echAccepted|, authCertificate + * should use the following hostname extracted from the ECHConfig. */ + const char* echPublicName; + /* When adding new fields to this structure, please document the * NSS version in which they were added. */ } SSLPreliminaryChannelInfo; @@ -533,7 +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_encrypted_sni_xtn = 0xffce, + ssl_tls13_outer_extensions_xtn = 0xfd00, + ssl_tls13_encrypted_client_hello_xtn = 0xfe08, + ssl_tls13_encrypted_sni_xtn = 0xffce, /* Deprecated. */ } SSLExtensionType; /* This is the old name for the supported_groups extensions. */ diff --git a/lib/ssl/tls13con.c b/lib/ssl/tls13con.c index 3a6bacb610..b1e3b2ebe9 100644 --- a/lib/ssl/tls13con.c +++ b/lib/ssl/tls13con.c @@ -21,7 +21,7 @@ #include "tls13hkdf.h" #include "tls13con.h" #include "tls13err.h" -#include "tls13esni.h" +#include "tls13ech.h" #include "tls13exthandle.h" #include "tls13hashstate.h" #include "tls13subcerts.h" @@ -446,10 +446,7 @@ tls13_SetupClientHello(sslSocket *ss, sslClientHelloType chType) PORT_Assert(ss->opt.noLocks || ssl_HaveSSL3HandshakeLock(ss)); PORT_Assert(ss->opt.noLocks || ssl_HaveXmitBufLock(ss)); - /* Do encrypted SNI. - * Note: this makes a new key even though we don't need one. - * Maybe remove this in future for efficiency. */ - rv = tls13_ClientSetupESNI(ss); + rv = tls13_ClientSetupEch(ss, chType); if (rv != SECSuccess) { return SECFailure; } @@ -1755,6 +1752,11 @@ tls13_MaybeSendHelloRetry(sslSocket *ss, const sslNamedGroupDef *requestedGroup, return SECFailure; /* Code already set. */ } + /* We received ECH, but have to start over with CH2. */ + ss->ssl3.hs.echAccepted = PR_FALSE; + PK11_HPKE_DestroyContext(ss->ssl3.hs.echHpkeCtx, PR_TRUE); + ss->ssl3.hs.echHpkeCtx = NULL; + *hrrSent = PR_TRUE; return SECSuccess; } @@ -1814,6 +1816,7 @@ tls13_HandleClientHelloPart2(sslSocket *ss, TLS13KeyShareEntry *clientShare = NULL; ssl3CipherSuite previousCipherSuite = 0; const sslNamedGroupDef *previousGroup = NULL; + PRBool previousEchOffered = PR_FALSE; PRBool hrr = PR_FALSE; /* If the legacy_version field is set to 0x300 or smaller, @@ -1865,11 +1868,19 @@ tls13_HandleClientHelloPart2(sslSocket *ss, rv = tls13_RecoverHashState(ss, ss->xtnData.cookie.data, ss->xtnData.cookie.len, &previousCipherSuite, - &previousGroup); + &previousGroup, + &previousEchOffered); 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. */ @@ -2426,14 +2437,19 @@ tls13_SendCertificateRequest(sslSocket *ss) * 00 00 Hash.length || // Handshake message length * Hash(ClientHello1) || // Hash of ClientHello1 * HelloRetryRequest ... MN) + * + * For an ECH handshake, the process occurs for the outer + * transcript in |ss->ssl3.hs.messages| and the inner + * transcript in |ss->ssl3.hs.echInnerMessages|. */ static SECStatus tls13_ReinjectHandshakeTranscript(sslSocket *ss) { SSL3Hashes hashes; + SSL3Hashes echInnerHashes; SECStatus rv; - // First compute the hash. + /* First compute the hash. */ rv = tls13_ComputeHash(ss, &hashes, ss->ssl3.hs.messages.buf, ss->ssl3.hs.messages.len, @@ -2442,16 +2458,35 @@ tls13_ReinjectHandshakeTranscript(sslSocket *ss) return SECFailure; } - // Now re-init the handshake. + if (ss->ssl3.hs.echHpkeCtx) { + rv = tls13_ComputeHash(ss, &echInnerHashes, + ss->ssl3.hs.echInnerMessages.buf, + ss->ssl3.hs.echInnerMessages.len, + tls13_GetHash(ss)); + if (rv != SECSuccess) { + return SECFailure; + } + } + ssl3_RestartHandshakeHashes(ss); - // And reinject the message. - rv = ssl_HashHandshakeMessage(ss, ssl_hs_message_hash, - hashes.u.raw, hashes.len); + /* Reinject the message. The Default context variant updates + * the default hash state. Use it for both non-ECH and ECH Outer. */ + rv = ssl_HashHandshakeMessageDefault(ss, ssl_hs_message_hash, + hashes.u.raw, hashes.len); if (rv != SECSuccess) { return SECFailure; } + if (ss->ssl3.hs.echHpkeCtx) { + rv = ssl_HashHandshakeMessageEchInner(ss, ssl_hs_message_hash, + echInnerHashes.u.raw, + echInnerHashes.len); + if (rv != SECSuccess) { + return SECFailure; + } + } + return SECSuccess; } static unsigned int @@ -3841,6 +3876,8 @@ tls13_ComputeHandshakeHashes(sslSocket *ss, SSL3Hashes *hashes) { SECStatus rv; PK11Context *ctx = NULL; + PRBool useEchInner; + sslBuffer *transcript; PORT_Assert(ss->opt.noLocks || ssl_HaveSSL3HandshakeLock(ss)); if (ss->ssl3.hs.hashType == handshake_hash_unknown) { @@ -3857,13 +3894,18 @@ tls13_ComputeHandshakeHashes(sslSocket *ss, SSL3Hashes *hashes) goto loser; } + /* One might expect this to use ss->ssl3.hs.echAccepted, + * but with 0-RTT we don't know that yet. */ + useEchInner = ss->sec.isServer ? PR_FALSE : !!ss->ssl3.hs.echHpkeCtx; + transcript = useEchInner ? &ss->ssl3.hs.echInnerMessages : &ss->ssl3.hs.messages; + PRINT_BUF(10, (ss, "Handshake hash computed over saved messages", - ss->ssl3.hs.messages.buf, - ss->ssl3.hs.messages.len)); + transcript->buf, + transcript->len)); if (PK11_DigestOp(ctx, - ss->ssl3.hs.messages.buf, - ss->ssl3.hs.messages.len) != SECSuccess) { + transcript->buf, + transcript->len) != SECSuccess) { ssl_MapLowLevelError(SSL_ERROR_SHA_DIGEST_FAILURE); goto loser; } @@ -4133,15 +4175,6 @@ tls13_HandleEncryptedExtensions(sslSocket *ss, PRUint8 *b, PRUint32 length) return SECFailure; /* Error code set below */ } - /* If we sent ESNI, check the nonce. */ - if (ss->xtnData.esniPrivateKey) { - PORT_Assert(ssl3_ExtensionAdvertised(ss, ssl_tls13_encrypted_sni_xtn)); - rv = tls13_ClientCheckEsniXtn(ss); - if (rv != SECSuccess) { - return SECFailure; - } - } - /* Handle the rest of the extensions. */ rv = ssl3_HandleParsedExtensions(ss, ssl_hs_encrypted_extensions); if (rv != SECSuccess) { @@ -4470,14 +4503,20 @@ tls13_HandleCertificateVerify(sslSocket *ss, PRUint8 *b, PRUint32 length) return SECFailure; } +/* Compute the PSK binder hash over: + * Client HRR prefix, if present in ss->ssl3.hs.messages or ss->ssl3.hs.echInnerMessages, + * |len| bytes of |buf| */ static SECStatus tls13_ComputePskBinderHash(sslSocket *ss, PRUint8 *b, size_t length, SSL3Hashes *hashes, SSLHashType hashType) { SECStatus rv; PK11Context *ctx = NULL; - /* On the server, HRR residual is already buffered. */ - sslBuffer *clientResidual = ss->sec.isServer ? NULL : &ss->ssl3.hs.messages; + sslBuffer *clientResidual = NULL; + if (!ss->sec.isServer) { + /* On the server, HRR residual is already buffered. */ + clientResidual = ss->ssl3.hs.echHpkeCtx ? &ss->ssl3.hs.echInnerMessages : &ss->ssl3.hs.messages; + } PORT_Assert(ss->ssl3.hs.hashType == handshake_hash_unknown); PORT_Assert(ss->opt.noLocks || ssl_HaveSSL3HandshakeLock(ss)); @@ -4904,6 +4943,8 @@ tls13_ServerHandleFinished(sslSocket *ss, PRUint8 *b, PRUint32 length) static SECStatus tls13_FinishHandshake(sslSocket *ss) { + /* If |!echHpkeCtx|, any advertised ECH was GREASE ECH. */ + PRBool offeredEch = !ss->sec.isServer && ss->ssl3.hs.echHpkeCtx; PORT_Assert(ss->opt.noLocks || ssl_HaveRecvBufLock(ss)); PORT_Assert(ss->opt.noLocks || ssl_HaveSSL3HandshakeLock(ss)); PORT_Assert(ss->ssl3.hs.restartTarget == NULL); @@ -4919,6 +4960,21 @@ tls13_FinishHandshake(sslSocket *ss) TLS13_SET_HS_STATE(ss, idle_handshake); + if (offeredEch && + !ssl3_ExtensionNegotiated(ss, ssl_tls13_encrypted_client_hello_xtn)) { + SSL3_SendAlert(ss, alert_fatal, ech_required); + + /* "If [one, none] of the values contains a supported version, the client can + * regard ECH as securely [replaced, disabled] by the server." */ + if (ss->xtnData.echRetryConfigs.len) { + PORT_SetError(SSL_ERROR_ECH_RETRY_WITH_ECH); + ss->xtnData.echRetryConfigsValid = PR_TRUE; + } else { + PORT_SetError(SSL_ERROR_ECH_RETRY_WITHOUT_ECH); + } + return SECFailure; + } + ssl_FinishHandshake(ss); return SECSuccess; @@ -5429,6 +5485,7 @@ tls13_HandleNewSessionTicket(sslSocket *ss, PRUint8 *b, PRUint32 length) return SECSuccess; } +#define _M_NONE 0 #define _M(a) (1 << PR_MIN(a, 31)) #define _M1(a) (_M(ssl_hs_##a)) #define _M2(a, b) (_M1(a) | _M1(b)) @@ -5462,7 +5519,8 @@ static const struct { { ssl_tls13_supported_versions_xtn, _M3(client_hello, server_hello, hello_retry_request) }, { ssl_record_size_limit_xtn, _M2(client_hello, encrypted_extensions) }, - { ssl_tls13_encrypted_sni_xtn, _M2(client_hello, encrypted_extensions) }, + { ssl_tls13_encrypted_client_hello_xtn, _M2(client_hello, encrypted_extensions) }, + { ssl_tls13_outer_extensions_xtn, _M_NONE /* Encoding/decoding only */ }, { ssl_tls13_post_handshake_auth_xtn, _M1(client_hello) } }; @@ -6158,10 +6216,9 @@ tls13_NegotiateVersion(sslSocket *ss, const TLSExtension *supportedVersions) return SECFailure; } for (version = ss->vrange.max; version >= ss->vrange.min; --version) { - if (ss->ssl3.hs.helloRetry && version < SSL_LIBRARY_VERSION_TLS_1_3) { - /* Prevent negotiating to a lower version in response to a TLS 1.3 HRR. - * Since we check in descending (local) order, this will only fail if - * our vrange has changed or the client didn't offer 1.3 in response. */ + if (version < SSL_LIBRARY_VERSION_TLS_1_3 && + (ss->ssl3.hs.helloRetry || ss->ssl3.hs.echAccepted)) { + /* Prevent negotiating to a lower version after 1.3 HRR or ECH */ PORT_SetError(SSL_ERROR_UNSUPPORTED_VERSION); FATAL_ERROR(ss, SSL_ERROR_UNSUPPORTED_VERSION, protocol_version); return SECFailure; diff --git a/lib/ssl/tls13con.h b/lib/ssl/tls13con.h index 7b8d58648d..ae0b4ae332 100644 --- a/lib/ssl/tls13con.h +++ b/lib/ssl/tls13con.h @@ -19,6 +19,7 @@ typedef enum { } tls13ExtensionStatus; #define TLS13_MAX_FINISHED_SIZE 64 +#define TLS13_COOKIE_SENTINEL 0xff SECStatus tls13_UnprotectRecord( sslSocket *ss, ssl3CipherSpec *spec, diff --git a/lib/ssl/tls13ech.c b/lib/ssl/tls13ech.c new file mode 100644 index 0000000000..e208c92bfc --- /dev/null +++ b/lib/ssl/tls13ech.c @@ -0,0 +1,2201 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nss.h" +#include "pk11func.h" +#include "pk11hpke.h" +#include "ssl.h" +#include "sslproto.h" +#include "sslimpl.h" +#include "selfencrypt.h" +#include "ssl3exthandle.h" +#include "tls13ech.h" +#include "tls13exthandle.h" +#include "tls13hkdf.h" + +extern SECStatus +ssl3_UpdateExplicitHandshakeTranscript(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); + +void +tls13_DestroyEchConfig(sslEchConfig *config) +{ + if (!config) { + return; + } + SECITEM_FreeItem(&config->contents.publicKey, PR_FALSE); + SECITEM_FreeItem(&config->contents.suites, PR_FALSE); + SECITEM_FreeItem(&config->raw, PR_FALSE); + PORT_Free(config->contents.publicName); + config->contents.publicName = NULL; + PORT_ZFree(config, sizeof(*config)); +} + +void +tls13_DestroyEchConfigs(PRCList *list) +{ + PRCList *cur_p; + while (!PR_CLIST_IS_EMPTY(list)) { + cur_p = PR_LIST_TAIL(list); + PR_REMOVE_LINK(cur_p); + tls13_DestroyEchConfig((sslEchConfig *)cur_p); + } +} + +SECStatus +tls13_CopyEchConfigs(PRCList *oConfigs, PRCList *configs) +{ + SECStatus rv; + sslEchConfig *config; + sslEchConfig *newConfig = NULL; + + for (PRCList *cur_p = PR_LIST_HEAD(oConfigs); + cur_p != oConfigs; + cur_p = PR_NEXT_LINK(cur_p)) { + config = (sslEchConfig *)PR_LIST_TAIL(oConfigs); + newConfig = PORT_ZNew(sslEchConfig); + if (!newConfig) { + goto loser; + } + + rv = SECITEM_CopyItem(NULL, &newConfig->raw, &config->raw); + if (rv != SECSuccess) { + goto loser; + } + newConfig->contents.publicName = PORT_Strdup(config->contents.publicName); + if (!newConfig->contents.publicName) { + goto loser; + } + rv = SECITEM_CopyItem(NULL, &newConfig->contents.publicKey, + &config->contents.publicKey); + if (rv != SECSuccess) { + goto loser; + } + rv = SECITEM_CopyItem(NULL, &newConfig->contents.suites, + &config->contents.suites); + if (rv != SECSuccess) { + goto loser; + } + newConfig->contents.kemId = config->contents.kemId; + newConfig->contents.kdfId = config->contents.kdfId; + newConfig->contents.aeadId = config->contents.aeadId; + newConfig->contents.maxNameLen = config->contents.maxNameLen; + PR_APPEND_LINK(&newConfig->link, configs); + } + return SECSuccess; + +loser: + tls13_DestroyEchConfig(newConfig); + tls13_DestroyEchConfigs(configs); + return SECFailure; +} + +static SECStatus +tls13_DigestEchConfig(const sslEchConfig *cfg, PRUint8 *digest, size_t maxDigestLen) +{ + SECStatus rv; + PK11SymKey *configKey = NULL; + PK11SymKey *derived = NULL; + SECItem *derivedItem = NULL; + CK_HKDF_PARAMS params = { 0 }; + SECItem paramsi = { siBuffer, (unsigned char *)¶ms, sizeof(params) }; + PK11SlotInfo *slot = PK11_GetInternalSlot(); + + if (!slot) { + goto loser; + } + + configKey = PK11_ImportDataKey(slot, CKM_HKDF_DATA, PK11_OriginUnwrap, + CKA_DERIVE, CONST_CAST(SECItem, &cfg->raw), NULL); + if (!configKey) { + goto loser; + } + + /* We only support SHA256 KDF. */ + PORT_Assert(cfg->contents.kdfId == HpkeKdfHkdfSha256); + params.bExtract = CK_TRUE; + params.bExpand = CK_TRUE; + params.prfHashMechanism = CKM_SHA256; + params.ulSaltType = CKF_HKDF_SALT_NULL; + 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, + CKF_SIGN | CKF_VERIFY); + + rv = PK11_ExtractKeyValue(derived); + if (rv != SECSuccess) { + goto loser; + } + + derivedItem = PK11_GetKeyData(derived); + if (!derivedItem) { + goto loser; + } + + if (derivedItem->len != maxDigestLen) { + PORT_SetError(SEC_ERROR_LIBRARY_FAILURE); + goto loser; + } + + PORT_Memcpy(digest, derivedItem->data, derivedItem->len); + PK11_FreeSymKey(configKey); + PK11_FreeSymKey(derived); + PK11_FreeSlot(slot); + return SECSuccess; + +loser: + PK11_FreeSymKey(configKey); + PK11_FreeSymKey(derived); + if (slot) { + PK11_FreeSlot(slot); + } + return SECFailure; +} + +static SECStatus +tls13_DecodeEchConfigContents(const sslReadBuffer *rawConfig, + sslEchConfig **outConfig) +{ + SECStatus rv; + sslEchConfigContents contents = { 0 }; + sslEchConfig *decodedConfig; + PRUint64 tmpn; + PRUint64 tmpn2; + sslReadBuffer tmpBuf; + PRUint16 *extensionTypes = NULL; + unsigned int extensionIndex = 0; + sslReader configReader = SSL_READER(rawConfig->buf, rawConfig->len); + sslReader suiteReader; + sslReader extensionReader; + PRBool hasValidSuite = PR_FALSE; + + /* Parse the public_name. */ + rv = sslRead_ReadVariable(&configReader, 2, &tmpBuf); + if (rv != SECSuccess) { + goto loser; + } + /* Make sure the public name doesn't contain any NULLs. + * TODO: Just store the SECItem instead. */ + for (tmpn = 0; tmpn < tmpBuf.len; tmpn++) { + if (tmpBuf.buf[tmpn] == '\0') { + PORT_SetError(SSL_ERROR_RX_MALFORMED_ECH_CONFIG); + goto loser; + } + } + + 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); + + /* Public key. */ + rv = sslRead_ReadVariable(&configReader, 2, &tmpBuf); + if (rv != SECSuccess) { + goto loser; + } + rv = SECITEM_MakeItem(NULL, &contents.publicKey, (PRUint8 *)tmpBuf.buf, tmpBuf.len); + if (rv != SECSuccess) { + goto loser; + } + + rv = sslRead_ReadNumber(&configReader, 2, &tmpn); + if (rv != SECSuccess) { + goto loser; + } + contents.kemId = tmpn; + + /* Parse HPKE cipher suites. */ + rv = sslRead_ReadVariable(&configReader, 2, &tmpBuf); + if (rv != SECSuccess) { + goto loser; + } + if (tmpBuf.len & 1) { + PORT_SetError(SSL_ERROR_RX_MALFORMED_ECH_CONFIG); + goto loser; + } + suiteReader = (sslReader)SSL_READER(tmpBuf.buf, tmpBuf.len); + while (SSL_READER_REMAINING(&suiteReader)) { + /* kdf_id */ + rv = sslRead_ReadNumber(&suiteReader, 2, &tmpn); + if (rv != SECSuccess) { + goto loser; + } + /* aead_id */ + rv = sslRead_ReadNumber(&suiteReader, 2, &tmpn2); + if (rv != SECSuccess) { + goto loser; + } + if (!hasValidSuite) { + /* Use the first compatible ciphersuite. */ + rv = PK11_HPKE_ValidateParameters(contents.kemId, tmpn, tmpn2); + if (rv == SECSuccess) { + hasValidSuite = PR_TRUE; + contents.kdfId = tmpn; + contents.aeadId = tmpn2; + break; + } + } + } + + rv = SECITEM_MakeItem(NULL, &contents.suites, (PRUint8 *)tmpBuf.buf, tmpBuf.len); + if (rv != SECSuccess) { + goto loser; + } + + /* Read the max name length. */ + rv = sslRead_ReadNumber(&configReader, 2, &tmpn); + if (rv != SECSuccess) { + goto loser; + } + contents.maxNameLen = (PRUint16)tmpn; + + /* Extensions. We don't support any, but must + * check for any that are marked critical. */ + rv = sslRead_ReadVariable(&configReader, 2, &tmpBuf); + if (rv != SECSuccess) { + goto loser; + } + + extensionReader = (sslReader)SSL_READER(tmpBuf.buf, tmpBuf.len); + extensionTypes = PORT_NewArray(PRUint16, tmpBuf.len / 2 * sizeof(PRUint16)); + if (!extensionTypes) { + goto loser; + } + + while (SSL_READER_REMAINING(&extensionReader)) { + /* Get the extension's type field */ + rv = sslRead_ReadNumber(&extensionReader, 2, &tmpn); + if (rv != SECSuccess) { + goto loser; + } + + for (unsigned int i = 0; i < extensionIndex; i++) { + if (extensionTypes[i] == tmpn) { + PORT_SetError(SEC_ERROR_EXTENSION_VALUE_INVALID); + goto loser; + } + } + extensionTypes[extensionIndex++] = (PRUint16)tmpn; + + /* If it's mandatory, fail. */ + if (tmpn & (1 << 15)) { + PORT_SetError(SEC_ERROR_UNKNOWN_CRITICAL_EXTENSION); + goto loser; + } + + /* Skip. */ + rv = sslRead_ReadVariable(&extensionReader, 2, &tmpBuf); + if (rv != SECSuccess) { + goto loser; + } + } + + /* Check that we consumed the entire ECHConfig */ + if (SSL_READER_REMAINING(&configReader)) { + PORT_SetError(SSL_ERROR_RX_MALFORMED_ECH_CONFIG); + goto loser; + } + + /* If the ciphersuites weren't compatible, don't + * set the outparam. Return success to indicate + * the config was well-formed. */ + if (hasValidSuite) { + decodedConfig = PORT_ZNew(sslEchConfig); + if (!decodedConfig) { + goto loser; + } + decodedConfig->contents = contents; + *outConfig = decodedConfig; + } else { + PORT_Free(contents.publicName); + SECITEM_FreeItem(&contents.publicKey, PR_FALSE); + SECITEM_FreeItem(&contents.suites, PR_FALSE); + } + PORT_Free(extensionTypes); + return SECSuccess; + +loser: + PORT_Free(extensionTypes); + PORT_Free(contents.publicName); + SECITEM_FreeItem(&contents.publicKey, PR_FALSE); + SECITEM_FreeItem(&contents.suites, PR_FALSE); + return SECFailure; +} + +/* Decode an ECHConfigs struct and store each ECHConfig + * into |configs|. */ +SECStatus +tls13_DecodeEchConfigs(const SECItem *data, PRCList *configs) +{ + SECStatus rv; + sslEchConfig *decodedConfig = NULL; + sslReader rdr = SSL_READER(data->data, data->len); + sslReadBuffer tmp; + sslReadBuffer singleConfig; + PRUint64 version; + PRUint64 length; + PORT_Assert(PR_CLIST_IS_EMPTY(configs)); + + rv = sslRead_ReadVariable(&rdr, 2, &tmp); + if (rv != SECSuccess) { + return SECFailure; + } + + if (SSL_READER_REMAINING(&rdr)) { + PORT_SetError(SEC_ERROR_BAD_DATA); + return SECFailure; + } + + sslReader configsReader = SSL_READER(tmp.buf, tmp.len); + + if (!SSL_READER_REMAINING(&configsReader)) { + PORT_SetError(SEC_ERROR_BAD_DATA); + return SECFailure; + } + + /* Handle each ECHConfig. */ + while (SSL_READER_REMAINING(&configsReader)) { + singleConfig.buf = SSL_READER_CURRENT(&configsReader); + /* Version */ + rv = sslRead_ReadNumber(&configsReader, 2, &version); + if (rv != SECSuccess) { + goto loser; + } + /* Length */ + rv = sslRead_ReadNumber(&configsReader, 2, &length); + if (rv != SECSuccess) { + goto loser; + } + singleConfig.len = 4 + length; + + rv = sslRead_Read(&configsReader, length, &tmp); + if (rv != SECSuccess) { + goto loser; + } + + if (version == TLS13_ECH_VERSION) { + rv = tls13_DecodeEchConfigContents(&tmp, &decodedConfig); + if (rv != SECSuccess) { + goto loser; /* code set */ + } + + if (decodedConfig) { + decodedConfig->version = version; + rv = SECITEM_MakeItem(NULL, &decodedConfig->raw, singleConfig.buf, + singleConfig.len); + if (rv != SECSuccess) { + goto loser; + } + + rv = tls13_DigestEchConfig(decodedConfig, decodedConfig->configId, + sizeof(decodedConfig->configId)); + if (rv != SECSuccess) { + goto loser; + } + PR_APPEND_LINK(&decodedConfig->link, configs); + decodedConfig = NULL; + } + } + } + return SECSuccess; + +loser: + tls13_DestroyEchConfigs(configs); + return SECFailure; +} + +/* Encode an ECHConfigs structure. We only allow one config, and as the + * primary use for this function is to generate test inputs, we don't + * validate against what HPKE and libssl can actually support. */ +SECStatus +SSLExp_EncodeEchConfig(const char *publicName, const PRUint32 *hpkeSuites, + unsigned int hpkeSuiteCount, HpkeKemId kemId, + const SECKEYPublicKey *pubKey, PRUint16 maxNameLen, + PRUint8 *out, unsigned int *outlen, unsigned int maxlen) +{ + SECStatus rv; + unsigned int savedOffset; + unsigned int len; + sslBuffer b = SSL_BUFFER_EMPTY; + 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) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return SECFailure; + } + + rv = sslBuffer_Skip(&b, 2, NULL); + if (rv != SECSuccess) { + goto loser; + } + + rv = sslBuffer_AppendNumber(&b, TLS13_ECH_VERSION, 2); + if (rv != SECSuccess) { + goto loser; + } + + rv = sslBuffer_Skip(&b, 2, &savedOffset); + if (rv != SECSuccess) { + goto loser; + } + + len = PORT_Strlen(publicName); + rv = sslBuffer_AppendVariable(&b, (const PRUint8 *)publicName, len, 2); + if (rv != SECSuccess) { + goto loser; + } + + rv = PK11_HPKE_Serialize(pubKey, tmpBuf, &tmpLen, sizeof(tmpBuf)); + if (rv != SECSuccess) { + goto loser; + } + rv = sslBuffer_AppendVariable(&b, tmpBuf, tmpLen, 2); + if (rv != SECSuccess) { + goto loser; + } + + rv = sslBuffer_AppendNumber(&b, kemId, 2); + if (rv != SECSuccess) { + goto loser; + } + + rv = sslBuffer_AppendNumber(&b, hpkeSuiteCount * 4, 2); + if (rv != SECSuccess) { + goto loser; + } + for (unsigned int i = 0; i < hpkeSuiteCount; i++) { + rv = sslBuffer_AppendNumber(&b, hpkeSuites[i], 4); + if (rv != SECSuccess) { + goto loser; + } + } + + rv = sslBuffer_AppendNumber(&b, maxNameLen, 2); + if (rv != SECSuccess) { + goto loser; + } + + /* extensions */ + rv = sslBuffer_AppendNumber(&b, 0, 2); + if (rv != SECSuccess) { + goto loser; + } + + /* Write the length now that we know it. */ + rv = sslBuffer_InsertLength(&b, 0, 2); + if (rv != SECSuccess) { + goto loser; + } + rv = sslBuffer_InsertLength(&b, savedOffset, 2); + if (rv != SECSuccess) { + goto loser; + } + + if (SSL_BUFFER_LEN(&b) > maxlen) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + goto loser; + } + PORT_Memcpy(out, SSL_BUFFER_BASE(&b), SSL_BUFFER_LEN(&b)); + *outlen = SSL_BUFFER_LEN(&b); + sslBuffer_Clear(&b); + return SECSuccess; + +loser: + sslBuffer_Clear(&b); + return SECFailure; +} + +SECStatus +SSLExp_GetEchRetryConfigs(PRFileDesc *fd, SECItem *retryConfigs) +{ + SECStatus rv; + sslSocket *ss; + SECItem out = { siBuffer, NULL, 0 }; + + if (!fd || !retryConfigs) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return SECFailure; + } + ss = ssl_FindSocket(fd); + if (!ss) { + SSL_DBG(("%d: SSL[%d]: bad socket in %s", + SSL_GETPID(), fd, __FUNCTION__)); + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return SECFailure; + } + if (!ss->xtnData.echRetryConfigsValid) { + PORT_SetError(SSL_ERROR_HANDSHAKE_NOT_COMPLETED); + return SECFailure; + } + /* May be empty. */ + rv = SECITEM_CopyItem(NULL, &out, &ss->xtnData.echRetryConfigs); + if (rv == SECFailure) { + return SECFailure; + } + *retryConfigs = out; + return SECSuccess; +} + +SECStatus +SSLExp_RemoveEchConfigs(PRFileDesc *fd) +{ + sslSocket *ss; + + if (!fd) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return SECFailure; + } + + ss = ssl_FindSocket(fd); + if (!ss) { + SSL_DBG(("%d: SSL[%d]: bad socket in %s", + SSL_GETPID(), fd, __FUNCTION__)); + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return SECFailure; + } + + if (!PR_CLIST_IS_EMPTY(&ss->echConfigs)) { + tls13_DestroyEchConfigs(&ss->echConfigs); + } + + /* Also remove any retry_configs and handshake context. */ + if (ss->xtnData.echRetryConfigs.len) { + SECITEM_FreeItem(&ss->xtnData.echRetryConfigs, PR_FALSE); + } + + if (ss->ssl3.hs.echHpkeCtx) { + PK11_HPKE_DestroyContext(ss->ssl3.hs.echHpkeCtx, PR_TRUE); + ss->ssl3.hs.echHpkeCtx = NULL; + } + PORT_Free(CONST_CAST(char, ss->ssl3.hs.echPublicName)); + ss->ssl3.hs.echPublicName = NULL; + + return SECSuccess; +} + +/* Import one or more ECHConfigs for the given keypair. The AEAD/KDF + * may differ , but only X25519 is supported for the KEM.*/ +SECStatus +SSLExp_SetServerEchConfigs(PRFileDesc *fd, + const SECKEYPublicKey *pubKey, const SECKEYPrivateKey *privKey, + const PRUint8 *echConfigs, unsigned int echConfigsLen) +{ +#ifndef NSS_ENABLE_DRAFT_HPKE + PORT_SetError(SSL_ERROR_FEATURE_DISABLED); + return SECFailure; +#else + sslSocket *ss; + SECStatus rv; + SECItem data = { siBuffer, CONST_CAST(PRUint8, echConfigs), echConfigsLen }; + + if (!fd || !pubKey || !privKey || !echConfigs || echConfigsLen == 0) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return SECFailure; + } + + ss = ssl_FindSocket(fd); + if (!ss) { + SSL_DBG(("%d: SSL[%d]: bad socket in %s", + SSL_GETPID(), fd, __FUNCTION__)); + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return SECFailure; + } + + /* Overwrite if we're already configured. */ + rv = SSLExp_RemoveEchConfigs(fd); + if (rv != SECSuccess) { + return SECFailure; + } + + rv = tls13_DecodeEchConfigs(&data, &ss->echConfigs); + if (rv != SECSuccess) { + goto loser; + } + if (PR_CLIST_IS_EMPTY(&ss->echConfigs)) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + goto loser; + } + + ss->echPubKey = SECKEY_CopyPublicKey(pubKey); + if (!ss->echPubKey) { + goto loser; + } + ss->echPrivKey = SECKEY_CopyPrivateKey(privKey); + if (!ss->echPrivKey) { + goto loser; + } + return SECSuccess; + +loser: + tls13_DestroyEchConfigs(&ss->echConfigs); + SECKEY_DestroyPrivateKey(ss->echPrivKey); + SECKEY_DestroyPublicKey(ss->echPubKey); + ss->echPubKey = NULL; + ss->echPrivKey = NULL; + return SECFailure; +#endif +} + +/* Client enable. For now, we'll use the first + * compatible config (server preference). */ +SECStatus +SSLExp_SetClientEchConfigs(PRFileDesc *fd, + const PRUint8 *echConfigs, + unsigned int echConfigsLen) +{ +#ifndef NSS_ENABLE_DRAFT_HPKE + PORT_SetError(SSL_ERROR_FEATURE_DISABLED); + return SECFailure; +#else + SECStatus rv; + sslSocket *ss; + SECItem data = { siBuffer, CONST_CAST(PRUint8, echConfigs), echConfigsLen }; + + if (!fd || !echConfigs || echConfigsLen == 0) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return SECFailure; + } + + ss = ssl_FindSocket(fd); + if (!ss) { + SSL_DBG(("%d: SSL[%d]: bad socket in %s", + SSL_GETPID(), fd, __FUNCTION__)); + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return SECFailure; + } + + /* Overwrite if we're already configured. */ + rv = SSLExp_RemoveEchConfigs(fd); + if (rv != SECSuccess) { + return SECFailure; + } + + rv = tls13_DecodeEchConfigs(&data, &ss->echConfigs); + if (rv != SECSuccess) { + return SECFailure; + } + if (PR_CLIST_IS_EMPTY(&ss->echConfigs)) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return SECFailure; + } + + return SECSuccess; +#endif +} + +/* Set up ECH. This generates an ephemeral sender + * keypair and the HPKE context */ +SECStatus +tls13_ClientSetupEch(sslSocket *ss, sslClientHelloType type) +{ + SECStatus rv; + 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) || + IS_DTLS(ss)) { + return SECSuccess; + } + + /* Maybe apply our own priority if >1. For now, we only support + * one version and one KEM. Each ECHConfig can specify multiple + * KDF/AEADs, so just use the first. */ + cfg = (sslEchConfig *)PR_LIST_HEAD(&ss->echConfigs); + + /* Skip ECH if the public name matches the private name. */ + if (0 == PORT_Strcmp(cfg->contents.publicName, ss->url)) { + return SECSuccess; + } + + SSL_TRC(50, ("%d: TLS13[%d]: Setup client ECH", + SSL_GETPID(), ss->fd)); + + switch (type) { + case client_hello_initial: + PORT_Assert(!ss->ssl3.hs.echHpkeCtx && !ss->ssl3.hs.echPublicName); + cx = PK11_HPKE_NewContext(cfg->contents.kemId, cfg->contents.kdfId, + 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; + } + + 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; + default: + PORT_Assert(0); + goto loser; + } + if (!cx) { + goto loser; + } + + rv = PK11_HPKE_Deserialize(cx, cfg->contents.publicKey.data, cfg->contents.publicKey.len, &pkR); + if (rv != SECSuccess) { + 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); + + /* Setup with an ephemeral sender keypair. */ + rv = PK11_HPKE_SetupS(cx, NULL, NULL, pkR, &hpkeInfo); + if (rv != SECSuccess) { + goto loser; + } + + if (!ss->ssl3.hs.helloRetry) { + 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 + * to fetch this field and perform cert chain verification. */ + ss->ssl3.hs.echPublicName = PORT_Strdup(cfg->contents.publicName); + if (!ss->ssl3.hs.echPublicName) { + goto loser; + } + + 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); + return SECFailure; +} + +/* + * enum { + * encrypted_client_hello(0xfe08), (65535) + * } ExtensionType; + * + * struct { + * HpkeKdfId kdf_id; + * HpkeAeadId aead_id; + * } ECHCipherSuite; + * struct { + * ECHCipherSuite cipher_suite; + * opaque config_id<0..255>; + * opaque enc<1..2^16-1>; + * opaque payload<1..2^16-1>; + * } ClientECH; + * + * Takes as input the constructed ClientHelloInner and + * returns a constructed encrypted_client_hello extension + * (replacing the contents of |chInner|). + */ +static SECStatus +tls13_EncryptClientHello(sslSocket *ss, sslBuffer *outerAAD, sslBuffer *chInner) +{ + SECStatus rv; + SECItem chPt = { siBuffer, chInner->buf, chInner->len }; + SECItem *chCt = NULL; + SECItem aadItem = { siBuffer, outerAAD ? outerAAD->buf : NULL, outerAAD ? outerAAD->len : 0 }; + const SECItem *hpkeEnc = NULL; + const sslEchConfig *cfg = (sslEchConfig *)PR_LIST_HEAD(&ss->echConfigs); + PORT_Assert(!PR_CLIST_IS_EMPTY(&ss->echConfigs)); + + SSL_TRC(50, ("%d: TLS13[%d]: Encrypting Client Hello Inner", + SSL_GETPID(), ss->fd)); + + hpkeEnc = PK11_HPKE_GetEncapPubKey(ss->ssl3.hs.echHpkeCtx); + if (!hpkeEnc) { + FATAL_ERROR(ss, SEC_ERROR_LIBRARY_FAILURE, internal_error); + goto loser; + } + +#ifndef UNSAFE_FUZZER_MODE + rv = PK11_HPKE_Seal(ss->ssl3.hs.echHpkeCtx, &aadItem, &chPt, &chCt); + if (rv != SECSuccess) { + goto loser; + } +#else + /* Fake a tag. */ + SECITEM_AllocItem(NULL, chCt, chPt.len + 16); + if (!chCt) { + goto loser; + } + PORT_Memcpy(chCt->data, chPt.data, chPt.len); +#endif + + /* Format the encrypted_client_hello extension. */ + sslBuffer_Clear(chInner); + rv = sslBuffer_AppendNumber(chInner, cfg->contents.kdfId, 2); + if (rv != SECSuccess) { + goto loser; + } + rv = sslBuffer_AppendNumber(chInner, cfg->contents.aeadId, 2); + 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; + } + rv = sslBuffer_AppendVariable(chInner, chCt->data, chCt->len, 2); + if (rv != SECSuccess) { + goto loser; + } + SECITEM_FreeItem(chCt, PR_TRUE); + return SECSuccess; + +loser: + SECITEM_FreeItem(chCt, PR_TRUE); + return SECFailure; +} + +SECStatus +tls13_GetMatchingEchConfig(const sslSocket *ss, HpkeKdfId kdf, HpkeAeadId aead, + const SECItem *configId, sslEchConfig **cfg) +{ + sslEchConfig *candidate; + PRINT_BUF(50, (ss, "Server GetMatchingEchConfig with digest:", + configId->data, configId->len)); + + for (PRCList *cur_p = PR_LIST_HEAD(&ss->echConfigs); + cur_p != &ss->echConfigs; + cur_p = PR_NEXT_LINK(cur_p)) { + sslEchConfig *echConfig = (sslEchConfig *)cur_p; + if (configId->len != sizeof(echConfig->configId) || + 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; + } + *cfg = candidate; + return SECSuccess; + } + + SSL_TRC(50, ("%d: TLS13[%d]: Server found no matching ECHConfig", + SSL_GETPID(), ss->fd)); + + *cfg = 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, + * the sid is copied from |reader| to |writer|. */ +static SECStatus +tls13_CopyChPreamble(sslReader *reader, const SECItem *explicitSid, sslBuffer *writer, sslReadBuffer *extensions) +{ + SECStatus rv; + sslReadBuffer tmpReadBuf; + + /* Locate the extensions. */ + rv = sslRead_Read(reader, 2 + SSL3_RANDOM_LENGTH, &tmpReadBuf); + if (rv != SECSuccess) { + return SECFailure; + } + rv = sslBuffer_Append(writer, tmpReadBuf.buf, tmpReadBuf.len); + if (rv != SECSuccess) { + return SECFailure; + } + + /* legacy_session_id */ + rv = sslRead_ReadVariable(reader, 1, &tmpReadBuf); + if (explicitSid) { + /* Encoded SID should be empty when copying from CHOuter. */ + if (tmpReadBuf.len > 0) { + PORT_SetError(SSL_ERROR_RX_MALFORMED_ECH_EXTENSION); + return SECFailure; + } + rv = sslBuffer_AppendVariable(writer, explicitSid->data, explicitSid->len, 1); + } else { + rv = sslBuffer_AppendVariable(writer, tmpReadBuf.buf, tmpReadBuf.len, 1); + } + if (rv != SECSuccess) { + return SECFailure; + } + + /* cipher suites */ + rv = sslRead_ReadVariable(reader, 2, &tmpReadBuf); + if (rv != SECSuccess) { + return SECFailure; + } + rv = sslBuffer_AppendVariable(writer, tmpReadBuf.buf, tmpReadBuf.len, 2); + if (rv != SECSuccess) { + return SECFailure; + } + + /* compression */ + rv = sslRead_ReadVariable(reader, 1, &tmpReadBuf); + if (rv != SECSuccess) { + return SECFailure; + } + rv = sslBuffer_AppendVariable(writer, tmpReadBuf.buf, tmpReadBuf.len, 1); + if (rv != SECSuccess) { + return SECFailure; + } + + /* extensions */ + rv = sslRead_ReadVariable(reader, 2, extensions); + if (rv != SECSuccess) { + return SECFailure; + } + + if (SSL_READER_REMAINING(reader) != 0) { + PORT_SetError(SSL_ERROR_RX_MALFORMED_ECH_EXTENSION); + return SECFailure; + } + + return SECSuccess; +} + +static SECStatus +tls13_MakeChOuterAAD(const SECItem *outer, sslBuffer *outerAAD) +{ + SECStatus rv; + sslBuffer aad = SSL_BUFFER_EMPTY; + sslReadBuffer aadXtns; + sslReader chReader = SSL_READER(outer->data, outer->len); + PRUint64 tmpn; + sslReadBuffer tmpvar; + unsigned int offset; + unsigned int preambleLen; + + rv = sslBuffer_Skip(&aad, 4, NULL); + if (rv != SECSuccess) { + goto loser; + } + + /* aad := preamble, aadXtn := extensions */ + rv = tls13_CopyChPreamble(&chReader, NULL, &aad, &aadXtns); + 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); + if (rv != SECSuccess) { + goto loser; + } + + /* Append each extension, minus encrypted_client_hello_xtn. */ + while (SSL_READER_REMAINING(&xtnsReader)) { + rv = sslRead_ReadNumber(&xtnsReader, 2, &tmpn); + if (rv != SECSuccess) { + goto loser; + } + rv = sslRead_ReadVariable(&xtnsReader, 2, &tmpvar); + if (rv != SECSuccess) { + goto loser; + } + + if (tmpn != ssl_tls13_encrypted_client_hello_xtn) { + rv = sslBuffer_AppendNumber(&aad, tmpn, 2); + if (rv != SECSuccess) { + goto loser; + } + rv = sslBuffer_AppendVariable(&aad, tmpvar.buf, tmpvar.len, 2); + if (rv != SECSuccess) { + goto loser; + } + } + } + + rv = sslBuffer_InsertNumber(&aad, offset, SSL_BUFFER_LEN(&aad) - preambleLen - 2, 2); + if (rv != SECSuccess) { + goto loser; + } + + /* Give it a message header. */ + rv = sslBuffer_InsertNumber(&aad, 0, ssl_hs_client_hello, 1); + if (rv != SECSuccess) { + goto loser; + } + + rv = sslBuffer_InsertLength(&aad, 1, 3); + if (rv != SECSuccess) { + goto loser; + } + *outerAAD = aad; + return SECSuccess; + +loser: + sslBuffer_Clear(&aad); + return SECFailure; +} + +SECStatus +tls13_OpenClientHelloInner(sslSocket *ss, const SECItem *outer, sslEchConfig *cfg, PK11SymKey *echHrrPsk, 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); + + rv = PK11_HPKE_SetupR(cx, ss->echPubKey, ss->echPrivKey, + &ss->xtnData.echSenderPubKey, &hpkeInfo); + if (rv != SECSuccess) { + goto loser; /* code set */ + } + + rv = tls13_MakeChOuterAAD(outer, &outerAAD); + if (rv != SECSuccess) { + goto loser; /* code set */ + } + + outerAADItem.data = outerAAD.buf; + outerAADItem.len = outerAAD.len; + +#ifndef UNSAFE_FUZZER_MODE + rv = PK11_HPKE_Open(cx, &outerAADItem, &ss->xtnData.innerCh, &decryptedChInner); + if (rv != SECSuccess) { + goto loser; /* code set */ + } +#else + rv = SECITEM_CopyItem(NULL, decryptedChInner, &ss->xtnData.innerCh); + if (rv != SECSuccess) { + goto loser; + } + decryptedChInner->len -= 16; /* Fake tag */ +#endif + + /* Stash the context, we may need it for HRR. */ + 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); + return SECFailure; +} + +/* Given a buffer of extensions prepared for CHOuter, translate those extensions to a + * buffer suitable for CHInner. This is intended to be called twice: once without + * compression for the transcript hash and binders, and once with compression for + * encoding the actual CHInner value. On the first run, if |inOutPskXtn| and + * chOuterXtnsBuf contains a PSK extension, remove it and return in the outparam. + * The caller will compute the binder value based on the uncompressed output. Next, + * if |compress|, consolidate duplicated extensions (that would otherwise be copied) + * into a single outer_extensions extension. If |inOutPskXtn|, the extension contains + * a binder, it is appended after the deduplicated outer_extensions. In the case of + * GREASE ECH, one call is made to estimate size (wiith compression, null inOutPskXtn). + */ +SECStatus +tls13_ConstructInnerExtensionsFromOuter(sslSocket *ss, sslBuffer *chOuterXtnsBuf, + sslBuffer *chInnerXtns, sslBuffer *inOutPskXtn, + PRBool compress) +{ + SECStatus rv; + PRUint64 extensionType; + sslReadBuffer extensionData; + sslBuffer pskXtn = SSL_BUFFER_EMPTY; + sslBuffer dupXtns = SSL_BUFFER_EMPTY; /* Dupcliated extensions, types-only if |compress|. */ + unsigned int tmpOffset; + unsigned int tmpLen; + unsigned int srcXtnBase; /* To truncate CHOuter and remove the PSK extension. */ + SSL_TRC(50, ("%d: TLS13[%d]: Constructing ECH inner extensions %s compression", + SSL_GETPID(), compress ? "with" : "without")); + + /* 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); + if (rv != SECSuccess) { + goto loser; + } + rv = sslBuffer_AppendNumber(chInnerXtns, 0, 2); + if (rv != SECSuccess) { + goto loser; + } + + sslReader rdr = SSL_READER(chOuterXtnsBuf->buf, chOuterXtnsBuf->len); + while (SSL_READER_REMAINING(&rdr)) { + srcXtnBase = rdr.offset; + rv = sslRead_ReadNumber(&rdr, 2, &extensionType); + if (rv != SECSuccess) { + goto loser; + } + + /* Get the extension data. */ + rv = sslRead_ReadVariable(&rdr, 2, &extensionData); + if (rv != SECSuccess) { + goto loser; + } + + switch (extensionType) { + case ssl_server_name_xtn: + /* Write the real (private) SNI value. */ + rv = sslBuffer_AppendNumber(chInnerXtns, extensionType, 2); + if (rv != SECSuccess) { + goto loser; + } + rv = sslBuffer_Skip(chInnerXtns, 2, &tmpOffset); + if (rv != SECSuccess) { + goto loser; + } + tmpLen = SSL_BUFFER_LEN(chInnerXtns); + rv = ssl3_ClientFormatServerNameXtn(ss, ss->url, + strlen(ss->url), + NULL, chInnerXtns); + if (rv != SECSuccess) { + goto loser; + } + tmpLen = SSL_BUFFER_LEN(chInnerXtns) - tmpLen; + rv = sslBuffer_InsertNumber(chInnerXtns, tmpOffset, tmpLen, 2); + if (rv != SECSuccess) { + goto loser; + } + break; + case ssl_tls13_supported_versions_xtn: + /* Only TLS 1.3 on CHInner. */ + rv = sslBuffer_AppendNumber(chInnerXtns, extensionType, 2); + if (rv != SECSuccess) { + goto loser; + } + rv = sslBuffer_AppendNumber(chInnerXtns, 3, 2); + if (rv != SECSuccess) { + goto loser; + } + rv = sslBuffer_AppendNumber(chInnerXtns, 2, 1); + if (rv != SECSuccess) { + goto loser; + } + rv = sslBuffer_AppendNumber(chInnerXtns, SSL_LIBRARY_VERSION_TLS_1_3, 2); + if (rv != SECSuccess) { + goto loser; + } + break; + case ssl_tls13_pre_shared_key_xtn: + /* If GREASEing, the estimated internal length + * will be short. However, the presence of a PSK extension in + * CHOuter is already a distinguisher. */ + if (inOutPskXtn) { + rv = sslBuffer_AppendNumber(&pskXtn, extensionType, 2); + if (rv != SECSuccess) { + goto loser; + } + rv = sslBuffer_AppendVariable(&pskXtn, extensionData.buf, + extensionData.len, 2); + if (rv != SECSuccess) { + goto loser; + } + /* In terms of CHOuter, the PSK extension no longer exists. + * 0 lastXtnOffset means insert padding at the end. */ + SSL_BUFFER_LEN(chOuterXtnsBuf) = srcXtnBase; + ss->xtnData.lastXtnOffset = 0; + } + break; + default: + PORT_Assert(extensionType != ssl_tls13_encrypted_client_hello_xtn); + rv = sslBuffer_AppendNumber(&dupXtns, extensionType, 2); + if (rv != SECSuccess) { + goto loser; + } + if (!compress) { + rv = sslBuffer_AppendVariable(&dupXtns, extensionData.buf, + extensionData.len, 2); + if (rv != SECSuccess) { + goto loser; + } + } + break; + } + } + + /* Append duplicated extensions, compressing or not. */ + if (SSL_BUFFER_LEN(&dupXtns) && compress) { + rv = sslBuffer_AppendNumber(chInnerXtns, ssl_tls13_outer_extensions_xtn, 2); + if (rv != SECSuccess) { + goto loser; + } + rv = sslBuffer_AppendNumber(chInnerXtns, dupXtns.len + 1, 2); + if (rv != SECSuccess) { + goto loser; + } + rv = sslBuffer_AppendBufferVariable(chInnerXtns, &dupXtns, 1); + } else if (SSL_BUFFER_LEN(&dupXtns)) { + /* Each duplicated extension has its own length. */ + rv = sslBuffer_AppendBuffer(chInnerXtns, &dupXtns); + } + if (rv != SECSuccess) { + goto loser; + } + + /* On the compression run, append the completed PSK extension (if + * provided). Else an incomplete (no binder) extension; the caller + * will compute the binder and call again. */ + if (compress && inOutPskXtn) { + rv = sslBuffer_AppendBuffer(chInnerXtns, inOutPskXtn); + } else if (pskXtn.len) { + rv = sslBuffer_AppendBuffer(chInnerXtns, &pskXtn); + if (inOutPskXtn) { + *inOutPskXtn = pskXtn; + } + } + if (rv != SECSuccess) { + goto loser; + } + + sslBuffer_Clear(&dupXtns); + return SECSuccess; + +loser: + sslBuffer_Clear(&pskXtn); + sslBuffer_Clear(&dupXtns); + return SECFailure; +} + +static SECStatus +tls13_EncodeClientHelloInner(sslSocket *ss, sslBuffer *chInner, sslBuffer *chInnerXtns, sslBuffer *out) +{ + PORT_Assert(ss && chInner && chInnerXtns && out); + SECStatus rv; + sslReadBuffer tmpReadBuf; + sslReader chReader = SSL_READER(chInner->buf, chInner->len); + + rv = sslRead_Read(&chReader, 4, &tmpReadBuf); + if (rv != SECSuccess) { + goto loser; + } + + rv = sslRead_Read(&chReader, 2 + SSL3_RANDOM_LENGTH, &tmpReadBuf); + if (rv != SECSuccess) { + goto loser; + } + rv = sslBuffer_Append(out, tmpReadBuf.buf, tmpReadBuf.len); + if (rv != SECSuccess) { + goto loser; + } + + /* Skip the legacy_session_id */ + rv = sslRead_ReadVariable(&chReader, 1, &tmpReadBuf); + if (rv != SECSuccess) { + goto loser; + } + rv = sslBuffer_AppendNumber(out, 0, 1); + if (rv != SECSuccess) { + goto loser; + } + + /* cipher suites */ + rv = sslRead_ReadVariable(&chReader, 2, &tmpReadBuf); + if (rv != SECSuccess) { + goto loser; + } + rv = sslBuffer_AppendVariable(out, tmpReadBuf.buf, tmpReadBuf.len, 2); + if (rv != SECSuccess) { + goto loser; + } + + /* compression methods */ + rv = sslRead_ReadVariable(&chReader, 1, &tmpReadBuf); + if (rv != SECSuccess) { + goto loser; + } + rv = sslBuffer_AppendVariable(out, tmpReadBuf.buf, tmpReadBuf.len, 1); + if (rv != SECSuccess) { + goto loser; + } + + /* Append the extensions. */ + rv = sslBuffer_AppendBufferVariable(out, chInnerXtns, 2); + if (rv != SECSuccess) { + goto loser; + } + return SECSuccess; + +loser: + sslBuffer_Clear(out); + return SECFailure; +} + +SECStatus +tls13_ConstructClientHelloWithEch(sslSocket *ss, const sslSessionID *sid, PRBool freshSid, + sslBuffer *chOuter, sslBuffer *chOuterXtnsBuf) +{ + SECStatus rv; + sslBuffer chInner = SSL_BUFFER_EMPTY; + sslBuffer encodedChInner = SSL_BUFFER_EMPTY; + sslBuffer chInnerXtns = SSL_BUFFER_EMPTY; + sslBuffer pskXtn = SSL_BUFFER_EMPTY; + sslBuffer outerAAD = SSL_BUFFER_EMPTY; + unsigned int encodedChLen; + unsigned int preambleLen; + SSL_TRC(50, ("%d: TLS13[%d]: Constructing ECH inner", SSL_GETPID())); + + /* Create the full (uncompressed) inner extensions and steal any PSK extension. + * NB: Neither chOuterXtnsBuf nor chInnerXtns are length-prefixed. */ + rv = tls13_ConstructInnerExtensionsFromOuter(ss, chOuterXtnsBuf, &chInnerXtns, + &pskXtn, PR_FALSE); + if (rv != SECSuccess) { + goto loser; /* code set */ + } + + rv = ssl3_CreateClientHelloPreamble(ss, sid, PR_FALSE, SSL_LIBRARY_VERSION_TLS_1_3, + PR_TRUE, &chInnerXtns, &chInner); + if (rv != SECSuccess) { + goto loser; /* code set */ + } + preambleLen = SSL_BUFFER_LEN(&chInner); + + /* Write handshake header length. tls13_EncryptClientHello will + * remove this upon encoding, but the transcript needs it. This assumes + * the 4B stream-variant header. */ + PORT_Assert(!IS_DTLS(ss)); + rv = sslBuffer_InsertNumber(&chInner, 1, + chInner.len + 2 + chInnerXtns.len - 4, 3); + if (rv != SECSuccess) { + goto loser; + } + + if (pskXtn.len) { + PORT_Assert(ssl3_ExtensionAdvertised(ss, ssl_tls13_pre_shared_key_xtn)); + PORT_Assert(ss->xtnData.lastXtnOffset == 0); /* stolen from outer */ + rv = tls13_WriteExtensionsWithBinder(ss, &chInnerXtns, &chInner); + /* Update the stolen PSK extension with the binder value. */ + PORT_Memcpy(pskXtn.buf, &chInnerXtns.buf[chInnerXtns.len - pskXtn.len], pskXtn.len); + } else { + rv = sslBuffer_AppendBufferVariable(&chInner, &chInnerXtns, 2); + } + if (rv != SECSuccess) { + goto loser; + } + + rv = ssl3_UpdateExplicitHandshakeTranscript(ss, chInner.buf, chInner.len, + &ss->ssl3.hs.echInnerMessages); + if (rv != SECSuccess) { + goto loser; /* code set */ + } + + /* Un-append the extensions, then append compressed via Encoded. */ + SSL_BUFFER_LEN(&chInner) = preambleLen; + sslBuffer_Clear(&chInnerXtns); + rv = tls13_ConstructInnerExtensionsFromOuter(ss, chOuterXtnsBuf, + &chInnerXtns, &pskXtn, PR_TRUE); + if (rv != SECSuccess) { + goto loser; + } + + /* TODO: Pad CHInner */ + rv = tls13_EncodeClientHelloInner(ss, &chInner, &chInnerXtns, &encodedChInner); + if (rv != SECSuccess) { + goto loser; + } + + /* 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; + 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); + if (rv != SECSuccess) { + goto loser; + } + rv = sslBuffer_AppendBufferVariable(&outerAAD, chOuterXtnsBuf, 2); + if (rv != SECSuccess) { + goto loser; + } + rv = sslBuffer_InsertLength(&outerAAD, 1, 3); + if (rv != SECSuccess) { + goto loser; + } + + /* Insert the encrypted_client_hello xtn and coalesce. */ + rv = tls13_EncryptClientHello(ss, &outerAAD, &encodedChInner); + if (rv != SECSuccess) { + goto loser; + } + PORT_Assert(encodedChLen == encodedChInner.len); + + rv = ssl3_EmplaceExtension(ss, chOuterXtnsBuf, ssl_tls13_encrypted_client_hello_xtn, + encodedChInner.buf, encodedChInner.len, PR_TRUE); + if (rv != SECSuccess) { + goto loser; + } + + rv = ssl3_InsertChHeaderSize(ss, chOuter, chOuterXtnsBuf); + if (rv != SECSuccess) { + goto loser; + } + + rv = sslBuffer_AppendBufferVariable(chOuter, chOuterXtnsBuf, 2); + if (rv != SECSuccess) { + goto loser; + } + +loser: + sslBuffer_Clear(&chInner); + sslBuffer_Clear(&encodedChInner); + sslBuffer_Clear(&chInnerXtns); + sslBuffer_Clear(&pskXtn); + sslBuffer_Clear(&outerAAD); + return rv; +} + +static SECStatus +tls13_ComputeEchSignal(sslSocket *ss, 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) { + goto loser; + } + + randKey = PK11_ImportDataKey(slot, CKM_HKDF_DATA, PK11_OriginUnwrap, + CKA_DERIVE, &randItem, NULL); + if (!randKey) { + goto loser; + } + + rv = tls13_HkdfExtract(NULL, randKey, hashAlg, &extracted); + 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); + if (rv != SECSuccess) { + goto loser; + } + + PORT_Memcpy(out, derived, TLS13_ECH_SIGNAL_LEN); + 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); + return SECSuccess; + +loser: + PK11_FreeSymKey(extracted); + PK11_FreeSymKey(randKey); + if (slot) { + PK11_FreeSlot(slot); + } + return SECFailure; +} + +/* Called just prior to padding the CH. Use the size of the CH to estimate + * the size of a corresponding ECH extension, then add it to the buffer. */ +SECStatus +tls13_MaybeGreaseEch(sslSocket *ss, unsigned int preambleLen, sslBuffer *buf) +{ + SECStatus rv; + sslBuffer chInnerXtns = SSL_BUFFER_EMPTY; + sslBuffer greaseBuf = SSL_BUFFER_EMPTY; + unsigned int payloadLen; + HpkeAeadId aead; + PK11SlotInfo *slot = NULL; + PK11SymKey *hmacPrk = NULL; + PK11SymKey *derivedData = NULL; + SECItem *rawData; + CK_HKDF_PARAMS params; + SECItem paramsi; + + if (!ss->opt.enableTls13GreaseEch || ss->ssl3.hs.echHpkeCtx) { + return SECSuccess; + } + + if (ss->vrange.max < SSL_LIBRARY_VERSION_TLS_1_3 || + IS_DTLS(ss)) { + return SECSuccess; + } + + /* Compress the extensions for payload length. */ + rv = tls13_ConstructInnerExtensionsFromOuter(ss, buf, &chInnerXtns, + NULL, PR_TRUE); + if (rv != SECSuccess) { + goto loser; /* Code set */ + } + payloadLen = preambleLen + 2 /* Xtns len */ + chInnerXtns.len - 4 /* msg header */; + payloadLen += 16; /* Aead tag */ + + /* HMAC-Expand to get something that will pass for ciphertext. */ + slot = PK11_GetBestSlot(CKM_HKDF_DERIVE, NULL); + if (!slot) { + goto loser; + } + + hmacPrk = PK11_KeyGen(slot, CKM_HKDF_DATA, NULL, SHA256_LENGTH, NULL); + if (!hmacPrk) { + goto loser; + } + + params.bExtract = CK_FALSE; + params.bExpand = CK_TRUE; + params.prfHashMechanism = CKM_SHA256; + params.pInfo = NULL; + params.ulInfoLen = 0; + paramsi.data = (unsigned char *)¶ms; + paramsi.len = sizeof(params); + derivedData = PK11_DeriveWithFlags(hmacPrk, CKM_HKDF_DATA, + ¶msi, CKM_HKDF_DATA, + CKA_DERIVE, 65 + payloadLen, + CKF_VERIFY); + if (!derivedData) { + goto loser; + } + + rv = PK11_ExtractKeyValue(derivedData); + if (rv != SECSuccess) { + 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); + + /* struct { + HpkeKdfId kdf_id; + HpkeAeadId aead_id; + opaque config_id<0..255>; + opaque enc<1..2^16-1>; + opaque payload<1..2^16-1>; + } ClientECH; */ + + /* Only support SHA256. */ + rv = sslBuffer_AppendNumber(&greaseBuf, HpkeKdfHkdfSha256, 2); + if (rv != SECSuccess) { + goto loser; + } + + /* HpkeAeadAes128Gcm = 1, HpkeAeadChaCha20Poly1305 = 3, */ + aead = (rawData->data[0] & 1) ? HpkeAeadAes128Gcm : HpkeAeadChaCha20Poly1305; + rv = sslBuffer_AppendNumber(&greaseBuf, aead, 2); + if (rv != SECSuccess) { + goto loser; + } + + rv = sslBuffer_AppendVariable(&greaseBuf, &rawData->data[1], 32, 1); + if (rv != SECSuccess) { + goto loser; + } + + /* enc len is fixed 32B for X25519. */ + rv = sslBuffer_AppendVariable(&greaseBuf, &rawData->data[33], 32, 2); + if (rv != SECSuccess) { + goto loser; + } + + rv = sslBuffer_AppendVariable(&greaseBuf, &rawData->data[65], 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? */ + 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); + sslBuffer_Clear(&chInnerXtns); + PK11_FreeSymKey(hmacPrk); + PK11_FreeSymKey(derivedData); + PK11_FreeSlot(slot); + return SECSuccess; + +loser: + sslBuffer_Clear(&greaseBuf); + sslBuffer_Clear(&chInnerXtns); + PK11_FreeSymKey(hmacPrk); + PK11_FreeSymKey(derivedData); + if (slot) { + PK11_FreeSlot(slot); + } + return SECFailure; +} + +SECStatus +tls13_MaybeHandleEch(sslSocket *ss, const PRUint8 *msg, PRUint32 msgLen, SECItem *sidBytes, + SECItem *comps, SECItem *cookieBytes, SECItem *suites, SECItem **echInner) +{ + SECStatus rv; + int error; + SSL3AlertDescription desc; + SECItem *tmpEchInner = NULL; + PRUint8 *b; + PRUint32 length; + TLSExtension *echExtension; + TLSExtension *versionExtension; + PORT_Assert(!ss->ssl3.hs.echAccepted); + SECItem tmpSid = { siBuffer, NULL, 0 }; + SECItem tmpCookie = { siBuffer, NULL, 0 }; + SECItem tmpSuites = { siBuffer, NULL, 0 }; + SECItem tmpComps = { siBuffer, NULL, 0 }; + + echExtension = ssl3_FindExtension(ss, ssl_tls13_encrypted_client_hello_xtn); + if (echExtension) { + rv = tls13_ServerHandleEchXtn(ss, &ss->xtnData, &echExtension->data); + if (rv != SECSuccess) { + goto loser; /* code set, alert sent. */ + } + rv = tls13_MaybeAcceptEch(ss, sidBytes, msg, msgLen, &tmpEchInner); + if (rv != SECSuccess) { + goto loser; /* code set, alert sent. */ + } + } + ss->ssl3.hs.preliminaryInfo |= ssl_preinfo_ech; + + if (ss->ssl3.hs.echAccepted) { + PORT_Assert(tmpEchInner); + PORT_Assert(!PR_CLIST_IS_EMPTY(&ss->ssl3.hs.remoteExtensions)); + + /* Start over on ECHInner */ + b = tmpEchInner->data; + length = tmpEchInner->len; + rv = ssl3_HandleClientHelloPreamble(ss, &b, &length, &tmpSid, + &tmpCookie, &tmpSuites, &tmpComps); + if (rv != SECSuccess) { + goto loser; /* code set, alert sent. */ + } + + /* 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); + if (!echExtension) { + FATAL_ERROR(ss, SSL_ERROR_MISSING_ECH_EXTENSION, decode_error); + 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) { + FATAL_ERROR(ss, SSL_ERROR_UNSUPPORTED_VERSION, protocol_version); + goto loser; + } + rv = tls13_NegotiateVersion(ss, versionExtension); + if (rv != SECSuccess) { + /* Could be malformed or not allowed in ECH. */ + error = PORT_GetError(); + desc = (error == SSL_ERROR_UNSUPPORTED_VERSION) ? protocol_version : illegal_parameter; + FATAL_ERROR(ss, error, desc); + goto loser; + } + + *comps = tmpComps; + *cookieBytes = tmpCookie; + *sidBytes = tmpSid; + *suites = tmpSuites; + *echInner = tmpEchInner; + } + return SECSuccess; + +loser: + SECITEM_FreeItem(tmpEchInner, PR_TRUE); + return SECFailure; +} + +SECStatus +tls13_MaybeHandleEchSignal(sslSocket *ss) +{ + SECStatus rv; + PRUint8 computed[TLS13_ECH_SIGNAL_LEN]; + const PRUint8 *signal = &ss->ssl3.hs.server_random[SSL3_RANDOM_LENGTH - TLS13_ECH_SIGNAL_LEN]; + 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) { + 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); + 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; + return SECSuccess; +} + +static SECStatus +tls13_UnencodeChInner(sslSocket *ss, const SECItem *sidBytes, SECItem **echInner) +{ + SECStatus rv; + sslReadBuffer outerExtensionsList; + sslReadBuffer tmpReadBuf; + sslBuffer unencodedChInner = SSL_BUFFER_EMPTY; + PRCList *outerCursor; + PRCList *innerCursor; + PRBool outerFound; + PRUint32 xtnsOffset; + PRUint64 tmp; + PRUint8 *tmpB; + PRUint32 tmpLength; + sslReader chReader = SSL_READER((*echInner)->data, (*echInner)->len); + PORT_Assert(!PR_CLIST_IS_EMPTY(&ss->ssl3.hs.echOuterExtensions)); + PORT_Assert(PR_CLIST_IS_EMPTY(&ss->ssl3.hs.remoteExtensions)); + + /* unencodedChInner := preamble, tmpReadBuf := encoded extensions. */ + rv = tls13_CopyChPreamble(&chReader, sidBytes, &unencodedChInner, &tmpReadBuf); + if (rv != SECSuccess) { + goto loser; /* code set */ + } + + /* Parse inner extensions into ss->ssl3.hs.remoteExtensions. */ + tmpB = CONST_CAST(PRUint8, tmpReadBuf.buf); + rv = ssl3_ParseExtensions(ss, &tmpB, &tmpReadBuf.len); + if (rv != SECSuccess) { + goto loser; /* malformed, alert sent. */ + } + + /* Exit early if there are no outer_extensions to decompress. */ + if (!ssl3_FindExtension(ss, ssl_tls13_outer_extensions_xtn)) { + rv = sslBuffer_AppendVariable(&unencodedChInner, tmpReadBuf.buf, tmpReadBuf.len, 2); + if (rv != SECSuccess) { + goto loser; + } + sslBuffer_Clear(&unencodedChInner); + return SECSuccess; + } + + /* Save room for uncompressed length. */ + rv = sslBuffer_Skip(&unencodedChInner, 2, &xtnsOffset); + if (rv != SECSuccess) { + goto loser; + } + + /* For each inner extension: If not outer_extensions, copy it to the output. + * Else if outer_extensions, iterate the compressed extension list and append + * each full extension as contained in CHOuter. Compressed extensions must be + * contiguous, so decompress at the point at which outer_extensions appears. */ + for (innerCursor = PR_NEXT_LINK(&ss->ssl3.hs.remoteExtensions); + innerCursor != &ss->ssl3.hs.remoteExtensions; + innerCursor = PR_NEXT_LINK(innerCursor)) { + TLSExtension *innerExtension = (TLSExtension *)innerCursor; + if (innerExtension->type != ssl_tls13_outer_extensions_xtn) { + rv = sslBuffer_AppendNumber(&unencodedChInner, + innerExtension->type, 2); + if (rv != SECSuccess) { + goto loser; + } + rv = sslBuffer_AppendVariable(&unencodedChInner, + innerExtension->data.data, + innerExtension->data.len, 2); + if (rv != SECSuccess) { + goto loser; + } + continue; + } + + /* Decompress */ + sslReader extensionRdr = SSL_READER(innerExtension->data.data, + innerExtension->data.len); + rv = sslRead_ReadVariable(&extensionRdr, 1, &outerExtensionsList); + if (rv != SECSuccess) { + goto loser; + } + if (SSL_READER_REMAINING(&extensionRdr) || (outerExtensionsList.len % 2) != 0) { + PORT_SetError(SSL_ERROR_RX_MALFORMED_ECH_EXTENSION); + goto loser; + } + + sslReader compressedTypes = SSL_READER(outerExtensionsList.buf, outerExtensionsList.len); + while (SSL_READER_REMAINING(&compressedTypes)) { + outerFound = PR_FALSE; + rv = sslRead_ReadNumber(&compressedTypes, 2, &tmp); + if (rv != SECSuccess) { + goto loser; + } + if (tmp == ssl_tls13_encrypted_client_hello_xtn || + tmp == ssl_tls13_outer_extensions_xtn) { + FATAL_ERROR(ss, SSL_ERROR_RX_MALFORMED_ECH_EXTENSION, illegal_parameter); + goto loser; + } + for (outerCursor = PR_NEXT_LINK(&ss->ssl3.hs.echOuterExtensions); + outerCursor != &ss->ssl3.hs.echOuterExtensions; + outerCursor = PR_NEXT_LINK(outerCursor)) { + if (((TLSExtension *)outerCursor)->type == tmp) { + outerFound = PR_TRUE; + rv = sslBuffer_AppendNumber(&unencodedChInner, + ((TLSExtension *)outerCursor)->type, 2); + if (rv != SECSuccess) { + goto loser; + } + rv = sslBuffer_AppendVariable(&unencodedChInner, + ((TLSExtension *)outerCursor)->data.data, + ((TLSExtension *)outerCursor)->data.len, 2); + if (rv != SECSuccess) { + goto loser; + } + break; + } + } + if (!outerFound) { + FATAL_ERROR(ss, SSL_ERROR_RX_MALFORMED_ECH_EXTENSION, illegal_parameter); + goto loser; + } + } + } + ssl3_DestroyRemoteExtensions(&ss->ssl3.hs.echOuterExtensions); + ssl3_DestroyRemoteExtensions(&ss->ssl3.hs.remoteExtensions); + + /* Correct the message and extensions sizes. */ + rv = sslBuffer_InsertNumber(&unencodedChInner, xtnsOffset, + unencodedChInner.len - xtnsOffset - 2, 2); + if (rv != SECSuccess) { + goto loser; + } + + tmpB = &unencodedChInner.buf[xtnsOffset]; + tmpLength = unencodedChInner.len - xtnsOffset; + rv = ssl3_ConsumeHandshakeNumber64(ss, &tmp, 2, &tmpB, &tmpLength); + if (rv != SECSuccess || tmpLength != tmp) { + FATAL_ERROR(ss, SSL_ERROR_RX_MALFORMED_CLIENT_HELLO, internal_error); + goto loser; + } + + rv = ssl3_ParseExtensions(ss, &tmpB, &tmpLength); + if (rv != SECSuccess) { + goto loser; + } + + SECITEM_FreeItem(*echInner, PR_FALSE); + (*echInner)->data = unencodedChInner.buf; + (*echInner)->len = unencodedChInner.len; + return SECSuccess; + +loser: + sslBuffer_Clear(&unencodedChInner); + return SECFailure; +} + +SECStatus +tls13_MaybeAcceptEch(sslSocket *ss, const SECItem *sidBytes, const PRUint8 *chOuter, + unsigned int chOuterLen, SECItem **chInner) +{ + 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; + sslEchConfig *candidate = NULL; /* non-owning */ + TLSExtension *hrrXtn; + SECItem *configId = ss->ssl3.hs.helloRetry ? &hrrCh1ConfigId : &ss->xtnData.echConfigId; + if (!ss->xtnData.innerCh.len) { + return SECSuccess; + } + + PORT_Assert(ss->xtnData.echSenderPubKey.data); + PORT_Assert(ss->xtnData.echConfigId.data); + PORT_Assert(ss->xtnData.echCipherSuite); + + if (ss->ssl3.hs.helloRetry) { + 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); + 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; + } + + /* 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) { + FATAL_ERROR(ss, SSL_ERROR_BAD_2ND_CLIENT_HELLO, + illegal_parameter); + goto loser; + } + if (!echHrrPsk) { + goto exit_success; + } + } + kdf = (HpkeKdfId)(ss->xtnData.echCipherSuite & 0xFFFF); + aead = (HpkeAeadId)(((ss->xtnData.echCipherSuite) >> 16) & 0xFFFF); + rv = tls13_GetMatchingEchConfig(ss, kdf, aead, configId, &candidate); + if (rv != SECSuccess) { + goto loser; + } + 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; + } + + rv = tls13_OpenClientHelloInner(ss, &outer, candidate, echHrrPsk, &decryptedChInner); + if (rv != SECSuccess) { + if (ss->ssl3.hs.helloRetry) { + FATAL_ERROR(ss, SSL_ERROR_RX_MALFORMED_ESNI_EXTENSION, decrypt_error); + goto loser; + } else { + rv = ssl3_RegisterExtensionSender(ss, &ss->xtnData, + ssl_tls13_encrypted_client_hello_xtn, + tls13_ServerSendEchXtn); + goto exit_success; + } + } + SSL_TRC(20, ("%d: TLS13[%d]: Successfully opened ECH inner CH", + SSL_GETPID(), ss->fd)); + ss->ssl3.hs.echAccepted = PR_TRUE; + + /* Stash the CHOuter extensions. They're not yet handled (only parsed). If + * the CHInner contains outer_extensions_xtn, we'll need to reference them. */ + ssl3_MoveRemoteExtensions(&ss->ssl3.hs.echOuterExtensions, &ss->ssl3.hs.remoteExtensions); + + rv = tls13_UnencodeChInner(ss, sidBytes, &decryptedChInner); + if (rv != SECSuccess) { + SECITEM_FreeItem(decryptedChInner, PR_TRUE); + goto loser; /* 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) +{ + SECStatus rv; + PRUint8 signal[TLS13_ECH_SIGNAL_LEN]; + rv = tls13_ComputeEchSignal(ss, signal); + if (rv != SECSuccess) { + return SECFailure; + } + PRUint8 *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 new file mode 100644 index 0000000000..5c53b3f8a5 --- /dev/null +++ b/lib/ssl/tls13ech.h @@ -0,0 +1,82 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is PRIVATE to SSL. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef __tls13ech_h_ +#define __tls13ech_h_ + +#include "pk11hpke.h" + +/* draft-08, shared-mode 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). + * - When multiple ECHConfigs are provided by the server, the first compatible + * config is selected by the client. Ciphersuite choices are limited and only + * the AEAD may vary (AES-128-GCM or ChaCha20Poly1305). + * - 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_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"; + +struct sslEchConfigContentsStr { + char *publicName; + SECItem publicKey; /* NULL on server. Use the keypair in sslEchConfig instead. */ + HpkeKemId kemId; + HpkeKdfId kdfId; + HpkeAeadId aeadId; + SECItem suites; /* One or more HpkeCipherSuites. The selected s + * suite is placed in kdfId and aeadId. */ + PRUint16 maxNameLen; + /* No supported extensions. */ +}; + +struct sslEchConfigStr { + PRCList link; + SECItem raw; + PRUint8 configId[32]; + PRUint16 version; + sslEchConfigContents contents; +}; + +SECStatus SSLExp_EncodeEchConfig(const char *publicName, const PRUint32 *hpkeSuites, + unsigned int hpkeSuiteCount, HpkeKemId kemId, + const SECKEYPublicKey *pubKey, PRUint16 maxNameLen, + PRUint8 *out, unsigned int *outlen, unsigned int maxlen); +SECStatus SSLExp_GetEchRetryConfigs(PRFileDesc *fd, SECItem *retryConfigs); +SECStatus SSLExp_SetClientEchConfigs(PRFileDesc *fd, const PRUint8 *echConfigs, + unsigned int echConfigsLen); +SECStatus SSLExp_SetServerEchConfigs(PRFileDesc *fd, + const SECKEYPublicKey *pubKey, const SECKEYPrivateKey *privKey, + const PRUint8 *echConfigs, unsigned int numEchConfigs); +SECStatus SSLExp_RemoveEchConfigs(PRFileDesc *fd); + +SECStatus tls13_ClientSetupEch(sslSocket *ss, sslClientHelloType type); +SECStatus tls13_ConstructClientHelloWithEch(sslSocket *ss, const sslSessionID *sid, + PRBool freshSid, sslBuffer *chOuterBuf, + sslBuffer *chInnerXtnsBuf); +SECStatus tls13_CopyEchConfigs(PRCList *oconfigs, PRCList *configs); +SECStatus tls13_DecodeEchConfigs(const SECItem *data, PRCList *configs); +void tls13_DestroyEchConfigs(PRCList *list); +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_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); + +#endif diff --git a/lib/ssl/tls13esni.c b/lib/ssl/tls13esni.c deleted file mode 100644 index 7182f22af8..0000000000 --- a/lib/ssl/tls13esni.c +++ /dev/null @@ -1,846 +0,0 @@ -/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ -/* - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -#define TLS13_ESNI_VERSION 0xff01 - -/* - * struct { - * uint16 version; - * uint8 checksum[4]; - * KeyShareEntry keys<4..2^16-1>; - * CipherSuite cipher_suites<2..2^16-2>; - * uint16 padded_length; - * uint64 not_before; - * uint64 not_after; - * Extension extensions<0..2^16-1>; - * } ESNIKeys; - */ -#include "nss.h" -#include "pk11func.h" -#include "ssl.h" -#include "sslproto.h" -#include "sslimpl.h" -#include "ssl3exthandle.h" -#include "tls13esni.h" -#include "tls13exthandle.h" -#include "tls13hkdf.h" - -const char kHkdfPurposeEsniKey[] = "esni key"; -const char kHkdfPurposeEsniIv[] = "esni iv"; - -void -tls13_DestroyESNIKeys(sslEsniKeys *keys) -{ - if (!keys) { - return; - } - SECITEM_FreeItem(&keys->data, PR_FALSE); - PORT_Free((void *)keys->dummySni); - tls13_DestroyKeyShares(&keys->keyShares); - ssl_FreeEphemeralKeyPair(keys->privKey); - SECITEM_FreeItem(&keys->suites, PR_FALSE); - PORT_ZFree(keys, sizeof(sslEsniKeys)); -} - -sslEsniKeys * -tls13_CopyESNIKeys(sslEsniKeys *okeys) -{ - sslEsniKeys *nkeys; - SECStatus rv; - - PORT_Assert(okeys); - - nkeys = PORT_ZNew(sslEsniKeys); - if (!nkeys) { - return NULL; - } - PR_INIT_CLIST(&nkeys->keyShares); - rv = SECITEM_CopyItem(NULL, &nkeys->data, &okeys->data); - if (rv != SECSuccess) { - goto loser; - } - if (okeys->dummySni) { - nkeys->dummySni = PORT_Strdup(okeys->dummySni); - if (!nkeys->dummySni) { - goto loser; - } - } - for (PRCList *cur_p = PR_LIST_HEAD(&okeys->keyShares); - cur_p != &okeys->keyShares; - cur_p = PR_NEXT_LINK(cur_p)) { - TLS13KeyShareEntry *copy = tls13_CopyKeyShareEntry( - (TLS13KeyShareEntry *)cur_p); - if (!copy) { - goto loser; - } - PR_APPEND_LINK(©->link, &nkeys->keyShares); - } - if (okeys->privKey) { - nkeys->privKey = ssl_CopyEphemeralKeyPair(okeys->privKey); - if (!nkeys->privKey) { - goto loser; - } - } - rv = SECITEM_CopyItem(NULL, &nkeys->suites, &okeys->suites); - if (rv != SECSuccess) { - goto loser; - } - nkeys->paddedLength = okeys->paddedLength; - nkeys->notBefore = okeys->notBefore; - nkeys->notAfter = okeys->notAfter; - return nkeys; - -loser: - tls13_DestroyESNIKeys(nkeys); - return NULL; -} - -/* Checksum is a 4-byte array. */ -static SECStatus -tls13_ComputeESNIKeysChecksum(const PRUint8 *buf, unsigned int len, - PRUint8 *checksum) -{ - SECItem copy; - SECStatus rv; - PRUint8 sha256[32]; - - rv = SECITEM_MakeItem(NULL, ©, buf, len); - if (rv != SECSuccess) { - return SECFailure; - } - - /* Stomp the checksum. */ - PORT_Memset(copy.data + 2, 0, 4); - - rv = PK11_HashBuf(ssl3_HashTypeToOID(ssl_hash_sha256), - sha256, - copy.data, copy.len); - SECITEM_FreeItem(©, PR_FALSE); - if (rv != SECSuccess) { - return SECFailure; - } - PORT_Memcpy(checksum, sha256, 4); - return SECSuccess; -} - -static SECStatus -tls13_DecodeESNIKeys(SECItem *data, sslEsniKeys **keysp) -{ - SECStatus rv; - sslReadBuffer tmp; - PRUint64 tmpn; - sslEsniKeys *keys; - PRUint8 checksum[4]; - sslReader rdr = SSL_READER(data->data, data->len); - - rv = sslRead_ReadNumber(&rdr, 2, &tmpn); - if (rv != SECSuccess) { - return SECFailure; - } - if (tmpn != TLS13_ESNI_VERSION) { - PORT_SetError(SSL_ERROR_UNSUPPORTED_VERSION); - return SECFailure; - } - keys = PORT_ZNew(sslEsniKeys); - if (!keys) { - return SECFailure; - } - PR_INIT_CLIST(&keys->keyShares); - - /* Make a copy. */ - rv = SECITEM_CopyItem(NULL, &keys->data, data); - if (rv != SECSuccess) { - goto loser; - } - - rv = tls13_ComputeESNIKeysChecksum(data->data, data->len, checksum); - if (rv != SECSuccess) { - goto loser; - } - - /* Read and check checksum. */ - rv = sslRead_Read(&rdr, 4, &tmp); - if (rv != SECSuccess) { - goto loser; - } - - if (0 != NSS_SecureMemcmp(tmp.buf, checksum, 4)) { - goto loser; - } - - /* Parse the key shares. */ - rv = sslRead_ReadVariable(&rdr, 2, &tmp); - if (rv != SECSuccess) { - goto loser; - } - - sslReader rdr2 = SSL_READER(tmp.buf, tmp.len); - while (SSL_READER_REMAINING(&rdr2)) { - TLS13KeyShareEntry *ks = NULL; - - rv = tls13_DecodeKeyShareEntry(&rdr2, &ks); - if (rv != SECSuccess) { - goto loser; - } - - if (ks) { - PR_APPEND_LINK(&ks->link, &keys->keyShares); - } - } - - /* Parse cipher suites. */ - rv = sslRead_ReadVariable(&rdr, 2, &tmp); - if (rv != SECSuccess) { - goto loser; - } - /* This can't be odd. */ - if (tmp.len & 1) { - goto loser; - } - rv = SECITEM_MakeItem(NULL, &keys->suites, (PRUint8 *)tmp.buf, tmp.len); - if (rv != SECSuccess) { - goto loser; - } - - /* Padded Length */ - rv = sslRead_ReadNumber(&rdr, 2, &tmpn); - if (rv != SECSuccess) { - goto loser; - } - keys->paddedLength = (PRUint16)tmpn; - - /* Not Before */ - rv = sslRead_ReadNumber(&rdr, 8, &keys->notBefore); - if (rv != SECSuccess) { - goto loser; - } - - /* Not After */ - rv = sslRead_ReadNumber(&rdr, 8, &keys->notAfter); - if (rv != SECSuccess) { - goto loser; - } - - /* Extensions, which we ignore. */ - rv = sslRead_ReadVariable(&rdr, 2, &tmp); - if (rv != SECSuccess) { - goto loser; - } - - /* Check that this is empty. */ - if (SSL_READER_REMAINING(&rdr) > 0) { - goto loser; - } - - *keysp = keys; - return SECSuccess; - -loser: - tls13_DestroyESNIKeys(keys); - PORT_SetError(SSL_ERROR_RX_MALFORMED_ESNI_KEYS); - - return SECFailure; -} - -/* Encode an ESNI keys structure. We only allow one key - * share. */ -SECStatus -SSLExp_EncodeESNIKeys(PRUint16 *cipherSuites, unsigned int cipherSuiteCount, - SSLNamedGroup group, SECKEYPublicKey *pubKey, - PRUint16 pad, PRUint64 notBefore, PRUint64 notAfter, - PRUint8 *out, unsigned int *outlen, unsigned int maxlen) -{ - unsigned int savedOffset; - SECStatus rv; - sslBuffer b = SSL_BUFFER_EMPTY; - - rv = sslBuffer_AppendNumber(&b, TLS13_ESNI_VERSION, 2); - if (rv != SECSuccess) { - goto loser; - } - - rv = sslBuffer_Skip(&b, 4, &savedOffset); - if (rv != SECSuccess) { - goto loser; - } - - /* Length of vector. */ - rv = sslBuffer_AppendNumber( - &b, tls13_SizeOfKeyShareEntry(pubKey), 2); - if (rv != SECSuccess) { - goto loser; - } - - /* Our one key share. */ - rv = tls13_EncodeKeyShareEntry(&b, group, pubKey); - if (rv != SECSuccess) { - goto loser; - } - - /* Cipher suites. */ - rv = sslBuffer_AppendNumber(&b, cipherSuiteCount * 2, 2); - if (rv != SECSuccess) { - goto loser; - } - for (unsigned int i = 0; i < cipherSuiteCount; i++) { - rv = sslBuffer_AppendNumber(&b, cipherSuites[i], 2); - if (rv != SECSuccess) { - goto loser; - } - } - - /* Padding Length. Fixed for now. */ - rv = sslBuffer_AppendNumber(&b, pad, 2); - if (rv != SECSuccess) { - goto loser; - } - - /* Start time. */ - rv = sslBuffer_AppendNumber(&b, notBefore, 8); - if (rv != SECSuccess) { - goto loser; - } - - /* End time. */ - rv = sslBuffer_AppendNumber(&b, notAfter, 8); - if (rv != SECSuccess) { - goto loser; - } - - /* No extensions. */ - rv = sslBuffer_AppendNumber(&b, 0, 2); - if (rv != SECSuccess) { - goto loser; - } - - rv = tls13_ComputeESNIKeysChecksum(SSL_BUFFER_BASE(&b), - SSL_BUFFER_LEN(&b), - SSL_BUFFER_BASE(&b) + 2); - if (rv != SECSuccess) { - PORT_Assert(PR_FALSE); - goto loser; - } - - if (SSL_BUFFER_LEN(&b) > maxlen) { - PORT_SetError(SEC_ERROR_INVALID_ARGS); - goto loser; - } - PORT_Memcpy(out, SSL_BUFFER_BASE(&b), SSL_BUFFER_LEN(&b)); - *outlen = SSL_BUFFER_LEN(&b); - - sslBuffer_Clear(&b); - return SECSuccess; -loser: - sslBuffer_Clear(&b); - return SECFailure; -} - -SECStatus -SSLExp_SetESNIKeyPair(PRFileDesc *fd, - SECKEYPrivateKey *privKey, - const PRUint8 *record, unsigned int recordLen) -{ - sslSocket *ss; - SECStatus rv; - sslEsniKeys *keys = NULL; - SECKEYPublicKey *pubKey = NULL; - SECItem data = { siBuffer, CONST_CAST(PRUint8, record), recordLen }; - PLArenaPool *arena = NULL; - - ss = ssl_FindSocket(fd); - if (!ss) { - SSL_DBG(("%d: SSL[%d]: bad socket in %s", - SSL_GETPID(), fd, __FUNCTION__)); - return SECFailure; - } - - rv = tls13_DecodeESNIKeys(&data, &keys); - if (rv != SECSuccess) { - return SECFailure; - } - - /* Check the cipher suites. */ - (void)ssl3_config_match_init(ss); - /* Make sure the cipher suite is OK. */ - SSLVersionRange vrange = { SSL_LIBRARY_VERSION_TLS_1_3, - SSL_LIBRARY_VERSION_TLS_1_3 }; - - sslReader csrdr = SSL_READER(keys->suites.data, - keys->suites.len); - while (SSL_READER_REMAINING(&csrdr)) { - PRUint64 asuite; - - rv = sslRead_ReadNumber(&csrdr, 2, &asuite); - if (rv != SECSuccess) { - goto loser; - } - const ssl3CipherSuiteCfg *suiteCfg = - ssl_LookupCipherSuiteCfg(asuite, ss->cipherSuites); - if (!ssl3_config_match(suiteCfg, ss->ssl3.policy, &vrange, ss)) { - /* Illegal suite. */ - PORT_SetError(SEC_ERROR_INVALID_ARGS); - goto loser; - } - } - - if (PR_CLIST_IS_EMPTY(&keys->keyShares)) { - PORT_SetError(SEC_ERROR_INVALID_ARGS); - goto loser; - } - if (PR_PREV_LINK(&keys->keyShares) != PR_NEXT_LINK(&keys->keyShares)) { - PORT_SetError(SEC_ERROR_INVALID_ARGS); - goto loser; - } - TLS13KeyShareEntry *entry = (TLS13KeyShareEntry *)PR_LIST_HEAD( - &keys->keyShares); - if (entry->group->keaType != ssl_kea_ecdh) { - PORT_SetError(SEC_ERROR_INVALID_ARGS); - goto loser; - } - arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); - if (!arena) { - goto loser; - } - pubKey = PORT_ArenaZNew(arena, SECKEYPublicKey); - if (!pubKey) { - goto loser; - } - pubKey->arena = arena; - arena = NULL; /* From here, this will be destroyed with the pubkey. */ - /* Dummy PKCS11 values because this key isn't on a slot. */ - pubKey->pkcs11Slot = NULL; - pubKey->pkcs11ID = CK_INVALID_HANDLE; - rv = ssl_ImportECDHKeyShare(pubKey, - entry->key_exchange.data, - entry->key_exchange.len, - entry->group); - if (rv != SECSuccess) { - goto loser; - } - - privKey = SECKEY_CopyPrivateKey(privKey); - if (!privKey) { - goto loser; - } - keys->privKey = ssl_NewEphemeralKeyPair(entry->group, privKey, pubKey); - if (!keys->privKey) { - goto loser; - } - pubKey = NULL; - ss->esniKeys = keys; - return SECSuccess; - -loser: - PORT_FreeArena(arena, PR_FALSE); - SECKEY_DestroyPublicKey(pubKey); - tls13_DestroyESNIKeys(keys); - return SECFailure; -} - -SECStatus -SSLExp_EnableESNI(PRFileDesc *fd, - const PRUint8 *esniKeys, - unsigned int esniKeysLen, - const char *dummySNI) -{ - sslSocket *ss; - sslEsniKeys *keys = NULL; - SECItem data = { siBuffer, CONST_CAST(PRUint8, esniKeys), esniKeysLen }; - SECStatus rv; - - ss = ssl_FindSocket(fd); - if (!ss) { - SSL_DBG(("%d: SSL[%d]: bad socket in %s", - SSL_GETPID(), fd, __FUNCTION__)); - return SECFailure; - } - - rv = tls13_DecodeESNIKeys(&data, &keys); - if (rv != SECSuccess) { - return SECFailure; - } - - if (dummySNI) { - keys->dummySni = PORT_Strdup(dummySNI); - if (!keys->dummySni) { - tls13_DestroyESNIKeys(keys); - return SECFailure; - } - } - - /* Delete in case it was set before. */ - tls13_DestroyESNIKeys(ss->esniKeys); - ss->esniKeys = keys; - - return SECSuccess; -} - -/* - * struct { - * opaque record_digest<0..2^16-1>; - * KeyShareEntry esni_key_share; - * Random client_hello_random; - * } ESNIContents; - */ -SECStatus -tls13_ComputeESNIKeys(const sslSocket *ss, - TLS13KeyShareEntry *entry, - sslKeyPair *keyPair, - const ssl3CipherSuiteDef *suite, - const PRUint8 *esniKeysHash, - const PRUint8 *keyShareBuf, - unsigned int keyShareBufLen, - const PRUint8 *clientRandom, - ssl3KeyMaterial *keyMat) -{ - PK11SymKey *Z = NULL; - PK11SymKey *Zx = NULL; - SECStatus ret = SECFailure; - PRUint8 esniContentsBuf[256]; /* Just big enough. */ - sslBuffer esniContents = SSL_BUFFER(esniContentsBuf); - PRUint8 hash[64]; - const ssl3BulkCipherDef *cipherDef = ssl_GetBulkCipherDef(suite); - size_t keySize = cipherDef->key_size; - size_t ivSize = cipherDef->iv_size + - cipherDef->explicit_nonce_size; /* This isn't always going to - * work, but it does for - * AES-GCM */ - unsigned int hashSize = tls13_GetHashSizeForHash(suite->prf_hash); - SECStatus rv; - - rv = tls13_HandleKeyShare(CONST_CAST(sslSocket, ss), entry, keyPair, - suite->prf_hash, &Z); - if (rv != SECSuccess) { - goto loser; - } - rv = tls13_HkdfExtract(NULL, Z, suite->prf_hash, &Zx); - if (rv != SECSuccess) { - goto loser; - } - - /* Encode ESNIContents. */ - rv = sslBuffer_AppendVariable(&esniContents, - esniKeysHash, hashSize, 2); - if (rv != SECSuccess) { - goto loser; - } - rv = sslBuffer_Append(&esniContents, keyShareBuf, keyShareBufLen); - if (rv != SECSuccess) { - goto loser; - } - rv = sslBuffer_Append(&esniContents, clientRandom, SSL3_RANDOM_LENGTH); - if (rv != SECSuccess) { - goto loser; - } - - PORT_Assert(hashSize <= sizeof(hash)); - rv = PK11_HashBuf(ssl3_HashTypeToOID(suite->prf_hash), - hash, - SSL_BUFFER_BASE(&esniContents), - SSL_BUFFER_LEN(&esniContents)); - ; - if (rv != SECSuccess) { - goto loser; - } - - rv = tls13_HkdfExpandLabel(Zx, suite->prf_hash, - hash, hashSize, - kHkdfPurposeEsniKey, strlen(kHkdfPurposeEsniKey), - ssl3_Alg2Mech(cipherDef->calg), - keySize, ss->protocolVariant, - &keyMat->key); - if (rv != SECSuccess) { - goto loser; - } - rv = tls13_HkdfExpandLabelRaw(Zx, suite->prf_hash, - hash, hashSize, - kHkdfPurposeEsniIv, strlen(kHkdfPurposeEsniIv), - ss->protocolVariant, keyMat->iv, ivSize); - if (rv != SECSuccess) { - goto loser; - } - - ret = SECSuccess; - -loser: - PK11_FreeSymKey(Z); - PK11_FreeSymKey(Zx); - return ret; -} - -/* Set up ESNI. This generates a private key as a side effect. */ -SECStatus -tls13_ClientSetupESNI(sslSocket *ss) -{ - ssl3CipherSuite suite; - sslEphemeralKeyPair *keyPair; - size_t i; - PRCList *cur; - SECStatus rv; - TLS13KeyShareEntry *share = NULL; - const sslNamedGroupDef *group = NULL; - PRTime now = ssl_Time(ss) / PR_USEC_PER_SEC; - - PORT_Assert(!ss->xtnData.esniPrivateKey); - - if (!ss->esniKeys) { - return SECSuccess; - } - - if ((ss->esniKeys->notBefore > now) || (ss->esniKeys->notAfter < now)) { - return SECSuccess; - } - - /* If we're not sending SNI, don't send ESNI. */ - if (!ssl_ShouldSendSNIExtension(ss, ss->url)) { - return SECSuccess; - } - - /* Pick the group. */ - for (i = 0; i < SSL_NAMED_GROUP_COUNT; ++i) { - for (cur = PR_NEXT_LINK(&ss->esniKeys->keyShares); - cur != &ss->esniKeys->keyShares; - cur = PR_NEXT_LINK(cur)) { - if (!ss->namedGroupPreferences[i]) { - continue; - } - share = (TLS13KeyShareEntry *)cur; - if (share->group->name == ss->namedGroupPreferences[i]->name) { - group = ss->namedGroupPreferences[i]; - break; - } - } - } - - if (!group) { - /* No compatible group. */ - return SECSuccess; - } - - rv = ssl3_NegotiateCipherSuiteInner(ss, &ss->esniKeys->suites, - SSL_LIBRARY_VERSION_TLS_1_3, &suite); - if (rv != SECSuccess) { - return SECSuccess; - } - - rv = tls13_CreateKeyShare(ss, group, &keyPair); - if (rv != SECSuccess) { - return SECFailure; - } - - ss->xtnData.esniPrivateKey = keyPair; - ss->xtnData.esniSuite = suite; - ss->xtnData.peerEsniShare = share; - - return SECSuccess; -} - -/* - * struct { - * CipherSuite suite; - * KeyShareEntry key_share; - * opaque record_digest<0..2^16-1>; - * opaque encrypted_sni<0..2^16-1>; - * } ClientEncryptedSNI; - * - * struct { - * ServerNameList sni; - * opaque zeros[ESNIKeys.padded_length - length(sni)]; - * } PaddedServerNameList; - * - * struct { - * uint8 nonce[16]; - * PaddedServerNameList realSNI; - * } ClientESNIInner; - */ -SECStatus -tls13_FormatEsniAADInput(sslBuffer *aadInput, - PRUint8 *keyShare, unsigned int keyShareLen) -{ - SECStatus rv; - - /* Key share. */ - PORT_Assert(keyShareLen > 0); - rv = sslBuffer_Append(aadInput, keyShare, keyShareLen); - if (rv != SECSuccess) { - return SECFailure; - } - - return SECSuccess; -} - -static SECStatus -tls13_ServerGetEsniAEAD(const sslSocket *ss, PRUint64 suite, - const ssl3CipherSuiteDef **suiteDefp) -{ - SECStatus rv; - const ssl3CipherSuiteDef *suiteDef; - - /* Check against the suite list for ESNI */ - PRBool csMatch = PR_FALSE; - sslReader csrdr = SSL_READER(ss->esniKeys->suites.data, - ss->esniKeys->suites.len); - while (SSL_READER_REMAINING(&csrdr)) { - PRUint64 asuite; - - rv = sslRead_ReadNumber(&csrdr, 2, &asuite); - if (rv != SECSuccess) { - return SECFailure; - } - if (asuite == suite) { - csMatch = PR_TRUE; - break; - } - } - if (!csMatch) { - return SECFailure; - } - - suiteDef = ssl_LookupCipherSuiteDef(suite); - PORT_Assert(suiteDef); - if (!suiteDef) { - return SECFailure; - } - - *suiteDefp = suiteDef; - return SECSuccess; -} - -SECStatus -tls13_ServerDecryptEsniXtn(const sslSocket *ss, const PRUint8 *in, unsigned int inLen, - PRUint8 *out, unsigned int *outLen, unsigned int maxLen) -{ - sslReader rdr = SSL_READER(in, inLen); - PRUint64 suite; - const ssl3CipherSuiteDef *suiteDef = NULL; - TLSExtension *keyShareExtension; - TLS13KeyShareEntry *entry = NULL; - ssl3KeyMaterial keyMat = { NULL }; - - sslBuffer aadInput = SSL_BUFFER_EMPTY; - const PRUint8 *keyShareBuf; - sslReadBuffer buf; - unsigned int keyShareBufLen; - PRUint8 hash[64]; - SECStatus rv; - - /* Read the cipher suite. */ - rv = sslRead_ReadNumber(&rdr, 2, &suite); - if (rv != SECSuccess) { - goto loser; - } - - /* Find the AEAD */ - rv = tls13_ServerGetEsniAEAD(ss, suite, &suiteDef); - if (rv != SECSuccess) { - goto loser; - } - - /* Note where the KeyShare starts. */ - keyShareBuf = SSL_READER_CURRENT(&rdr); - rv = tls13_DecodeKeyShareEntry(&rdr, &entry); - if (rv != SECSuccess) { - goto loser; - } - keyShareBufLen = SSL_READER_CURRENT(&rdr) - keyShareBuf; - if (!entry || entry->group->name != ss->esniKeys->privKey->group->name) { - goto loser; - } - - /* The hash of the ESNIKeys structure. */ - rv = sslRead_ReadVariable(&rdr, 2, &buf); - if (rv != SECSuccess) { - goto loser; - } - - /* Check that the hash matches. */ - unsigned int hashLen = tls13_GetHashSizeForHash(suiteDef->prf_hash); - PORT_Assert(hashLen <= sizeof(hash)); - rv = PK11_HashBuf(ssl3_HashTypeToOID(suiteDef->prf_hash), - hash, - ss->esniKeys->data.data, ss->esniKeys->data.len); - if (rv != SECSuccess) { - goto loser; - } - - if (buf.len != hashLen) { - /* This is malformed. */ - goto loser; - } - if (0 != NSS_SecureMemcmp(hash, buf.buf, hashLen)) { - goto loser; - } - - rv = tls13_ComputeESNIKeys(ss, entry, - ss->esniKeys->privKey->keys, - suiteDef, - hash, keyShareBuf, keyShareBufLen, - ((sslSocket *)ss)->ssl3.hs.client_random, - &keyMat); - if (rv != SECSuccess) { - goto loser; - } - - /* Read the ciphertext. */ - rv = sslRead_ReadVariable(&rdr, 2, &buf); - if (rv != SECSuccess) { - goto loser; - } - - /* Check that this is empty. */ - if (SSL_READER_REMAINING(&rdr) > 0) { - goto loser; - } - - /* Find the key share extension. */ - keyShareExtension = ssl3_FindExtension(CONST_CAST(sslSocket, ss), - ssl_tls13_key_share_xtn); - if (!keyShareExtension) { - goto loser; - } - rv = tls13_FormatEsniAADInput(&aadInput, - keyShareExtension->data.data, - keyShareExtension->data.len); - if (rv != SECSuccess) { - goto loser; - } - - const ssl3BulkCipherDef *cipher_def = ssl_GetBulkCipherDef(suiteDef); - unsigned char *aad = SSL_BUFFER_BASE(&aadInput); - int aadLen = SSL_BUFFER_LEN(&aadInput); - int ivLen = cipher_def->iv_size + cipher_def->explicit_nonce_size; - SSLCipherAlgorithm calg = cipher_def->calg; - unsigned char zero[sizeof(sslSequenceNumber)] = { 0 }; - SECItem null_params = { siBuffer, NULL, 0 }; - PK11Context *ctxt = PK11_CreateContextBySymKey(ssl3_Alg2Mech(calg), - CKA_NSS_MESSAGE | CKA_DECRYPT, - keyMat.key, &null_params); - if (!ctxt) { - sslBuffer_Clear(&aadInput); - goto loser; - } - - rv = tls13_AEAD(ctxt, PR_TRUE /* Decrypt */, CKG_NO_GENERATE, 0, - keyMat.iv, NULL, ivLen, zero, sizeof(zero), aad, aadLen, - out, outLen, maxLen, cipher_def->tag_size, buf.buf, buf.len); - PK11_DestroyContext(ctxt, PR_TRUE); - sslBuffer_Clear(&aadInput); - if (rv != SECSuccess) { - goto loser; - } - - ssl_DestroyKeyMaterial(&keyMat); - tls13_DestroyKeyShareEntry(entry); - return SECSuccess; - -loser: - FATAL_ERROR(CONST_CAST(sslSocket, ss), SSL_ERROR_RX_MALFORMED_ESNI_EXTENSION, illegal_parameter); - ssl_DestroyKeyMaterial(&keyMat); /* Safe because zeroed. */ - if (entry) { - tls13_DestroyKeyShareEntry(entry); - } - return SECFailure; -} diff --git a/lib/ssl/tls13esni.h b/lib/ssl/tls13esni.h deleted file mode 100644 index 0fb12d25e3..0000000000 --- a/lib/ssl/tls13esni.h +++ /dev/null @@ -1,51 +0,0 @@ -/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ -/* - * This file is PRIVATE to SSL. - * - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -#ifndef __tls13esni_h_ -#define __tls13esni_h_ - -struct sslEsniKeysStr { - SECItem data; /* The encoded record. */ - sslEphemeralKeyPair *privKey; - const char *dummySni; - PRCList keyShares; /* List of TLS13KeyShareEntry */ - SECItem suites; - PRUint16 paddedLength; - PRUint64 notBefore; - PRUint64 notAfter; -}; - -SECStatus SSLExp_SetESNIKeyPair(PRFileDesc *fd, - SECKEYPrivateKey *privKey, - const PRUint8 *record, unsigned int recordLen); - -SECStatus SSLExp_EnableESNI(PRFileDesc *fd, const PRUint8 *esniKeys, - unsigned int esniKeysLen, const char *dummySNI); -SECStatus SSLExp_EncodeESNIKeys(PRUint16 *cipherSuites, unsigned int cipherSuiteCount, - SSLNamedGroup group, SECKEYPublicKey *pubKey, - PRUint16 pad, PRUint64 notBefore, PRUint64 notAfter, - PRUint8 *out, unsigned int *outlen, unsigned int maxlen); -sslEsniKeys *tls13_CopyESNIKeys(sslEsniKeys *okeys); -void tls13_DestroyESNIKeys(sslEsniKeys *keys); -SECStatus tls13_ClientSetupESNI(sslSocket *ss); -SECStatus tls13_ComputeESNIKeys(const sslSocket *ss, - TLS13KeyShareEntry *entry, - sslKeyPair *keyPair, - const ssl3CipherSuiteDef *suite, - const PRUint8 *esniKeysHash, - const PRUint8 *keyShareBuf, - unsigned int keyShareBufLen, - const PRUint8 *clientRandom, - ssl3KeyMaterial *keyMat); -SECStatus tls13_FormatEsniAADInput(sslBuffer *aadInput, - PRUint8 *keyShare, unsigned int keyShareLen); - -SECStatus tls13_ServerDecryptEsniXtn(const sslSocket *ss, const PRUint8 *in, unsigned int inLen, - PRUint8 *out, unsigned int *outLen, unsigned int maxLen); - -#endif diff --git a/lib/ssl/tls13exthandle.c b/lib/ssl/tls13exthandle.c index 5c3930ac26..54a4abb099 100644 --- a/lib/ssl/tls13exthandle.c +++ b/lib/ssl/tls13exthandle.c @@ -12,7 +12,7 @@ #include "pk11pub.h" #include "ssl3ext.h" #include "ssl3exthandle.h" -#include "tls13esni.h" +#include "tls13ech.h" #include "tls13exthandle.h" #include "tls13psk.h" #include "tls13subcerts.h" @@ -126,7 +126,6 @@ tls13_ClientSendKeyShareXtn(const sslSocket *ss, TLSExtensionData *xtnData, { SECStatus rv; PRCList *cursor; - unsigned int extStart; unsigned int lengthOffset; if (ss->vrange.max < SSL_LIBRARY_VERSION_TLS_1_3) { @@ -138,8 +137,6 @@ tls13_ClientSendKeyShareXtn(const sslSocket *ss, TLSExtensionData *xtnData, SSL_TRC(3, ("%d: TLS13[%d]: send client key share xtn", SSL_GETPID(), ss->fd)); - extStart = SSL_BUFFER_LEN(buf); - /* Save the offset to the length. */ rv = sslBuffer_Skip(buf, 2, &lengthOffset); if (rv != SECSuccess) { @@ -162,13 +159,6 @@ tls13_ClientSendKeyShareXtn(const sslSocket *ss, TLSExtensionData *xtnData, return SECFailure; } - rv = SECITEM_MakeItem(NULL, &xtnData->keyShareExtension, - SSL_BUFFER_BASE(buf) + extStart, - SSL_BUFFER_LEN(buf) - extStart); - if (rv != SECSuccess) { - return SECFailure; - } - *added = PR_TRUE; return SECSuccess; } @@ -849,6 +839,7 @@ tls13_ClientSendSupportedVersionsXtn(const sslSocket *ss, TLSExtensionData *xtnD return SECFailure; } + PORT_Assert(!ss->ssl3.hs.echHpkeCtx || ss->vrange.max >= SSL_LIBRARY_VERSION_TLS_1_3); for (version = ss->vrange.max; version >= ss->vrange.min; --version) { PRUint16 wire = tls13_EncodeVersion(version, ss->protocolVariant); @@ -1213,289 +1204,30 @@ tls13_ServerSendHrrCookieXtn(const sslSocket *ss, TLSExtensionData *xtnData, } SECStatus -tls13_ClientSendEsniXtn(const sslSocket *ss, TLSExtensionData *xtnData, - sslBuffer *buf, PRBool *added) -{ - SECStatus rv; - PRUint8 sniBuf[1024]; - PRUint8 hash[64]; - sslBuffer sni = SSL_BUFFER(sniBuf); - const ssl3CipherSuiteDef *suiteDef; - ssl3KeyMaterial keyMat; - PRUint8 outBuf[1024]; - unsigned int outLen; - unsigned int sniStart; - unsigned int sniLen; - sslBuffer aadInput = SSL_BUFFER_EMPTY; - unsigned int keyShareBufStart; - unsigned int keyShareBufLen; - - PORT_Memset(&keyMat, 0, sizeof(keyMat)); - - if (!ss->xtnData.esniPrivateKey) { - return SECSuccess; - } - - /* nonce */ - rv = PK11_GenerateRandom( - (unsigned char *)xtnData->esniNonce, sizeof(xtnData->esniNonce)); - if (rv != SECSuccess) { - return SECFailure; - } - rv = sslBuffer_Append(&sni, xtnData->esniNonce, sizeof(xtnData->esniNonce)); - if (rv != SECSuccess) { - return SECFailure; - } - - /* sni */ - sniStart = SSL_BUFFER_LEN(&sni); - rv = ssl3_ClientFormatServerNameXtn(ss, ss->url, xtnData, &sni); - if (rv != SECSuccess) { - return SECFailure; - } - - sniLen = SSL_BUFFER_LEN(&sni) - sniStart; - /* Padding. */ - if (ss->esniKeys->paddedLength > sniLen) { - unsigned int paddingRequired = ss->esniKeys->paddedLength - sniLen; - while (paddingRequired--) { - rv = sslBuffer_AppendNumber(&sni, 0, 1); - if (rv != SECSuccess) { - return SECFailure; - } - } - } - - suiteDef = ssl_LookupCipherSuiteDef(xtnData->esniSuite); - PORT_Assert(suiteDef); - if (!suiteDef) { - PORT_SetError(SEC_ERROR_LIBRARY_FAILURE); - return SECFailure; - } - - /* Format the first part of the extension so we have the - * encoded KeyShareEntry. */ - rv = sslBuffer_AppendNumber(buf, xtnData->esniSuite, 2); - if (rv != SECSuccess) { - return SECFailure; - } - keyShareBufStart = SSL_BUFFER_LEN(buf); - rv = tls13_EncodeKeyShareEntry(buf, - xtnData->esniPrivateKey->group->name, - xtnData->esniPrivateKey->keys->pubKey); - if (rv != SECSuccess) { - return SECFailure; - } - keyShareBufLen = SSL_BUFFER_LEN(buf) - keyShareBufStart; - - if (tls13_GetHashSizeForHash(suiteDef->prf_hash) > sizeof(hash)) { - PORT_Assert(PR_FALSE); - PORT_SetError(SEC_ERROR_LIBRARY_FAILURE); - return SECFailure; - } - - rv = PK11_HashBuf(ssl3_HashTypeToOID(suiteDef->prf_hash), - hash, - ss->esniKeys->data.data, - ss->esniKeys->data.len); - if (rv != SECSuccess) { - PORT_Assert(PR_FALSE); - return SECFailure; - } - - rv = sslBuffer_AppendVariable(buf, hash, - tls13_GetHashSizeForHash(suiteDef->prf_hash), 2); - if (rv != SECSuccess) { - return SECFailure; - } - - /* Compute the ESNI keys. */ - rv = tls13_ComputeESNIKeys(ss, xtnData->peerEsniShare, - xtnData->esniPrivateKey->keys, - suiteDef, - hash, - SSL_BUFFER_BASE(buf) + keyShareBufStart, - keyShareBufLen, - CONST_CAST(PRUint8, ss->ssl3.hs.client_random), - &keyMat); - if (rv != SECSuccess) { - return SECFailure; - } - - rv = tls13_FormatEsniAADInput(&aadInput, - xtnData->keyShareExtension.data, - xtnData->keyShareExtension.len); - if (rv != SECSuccess) { - ssl_DestroyKeyMaterial(&keyMat); - return SECFailure; - } - - /* Now encrypt. */ - unsigned char *aad = SSL_BUFFER_BASE(&aadInput); - int aadLen = SSL_BUFFER_LEN(&aadInput); - const ssl3BulkCipherDef *cipher_def = ssl_GetBulkCipherDef(suiteDef); - int ivLen = cipher_def->iv_size + cipher_def->explicit_nonce_size; - unsigned char zero[sizeof(sslSequenceNumber)] = { 0 }; - SSLCipherAlgorithm calg = cipher_def->calg; - SECItem null_params = { siBuffer, NULL, 0 }; - PK11Context *ctxt = PK11_CreateContextBySymKey(ssl3_Alg2Mech(calg), - CKA_NSS_MESSAGE | CKA_ENCRYPT, - keyMat.key, &null_params); - if (!ctxt) { - ssl_DestroyKeyMaterial(&keyMat); - sslBuffer_Clear(&aadInput); - return SECFailure; - } - - /* This function is a single shot, with fresh/unique keys, no need to - * generate the IV internally */ - rv = tls13_AEAD(ctxt, PR_FALSE /* Encrypt */, CKG_NO_GENERATE, 0, - keyMat.iv, NULL, ivLen, zero, sizeof(zero), aad, aadLen, - outBuf, &outLen, sizeof(outBuf), cipher_def->tag_size, - SSL_BUFFER_BASE(&sni), SSL_BUFFER_LEN(&sni)); - ssl_DestroyKeyMaterial(&keyMat); - sslBuffer_Clear(&aadInput); - PK11_DestroyContext(ctxt, PR_TRUE); - if (rv != SECSuccess) { - return SECFailure; - } - - /* Encode the rest. */ - rv = sslBuffer_AppendVariable(buf, outBuf, outLen, 2); - if (rv != SECSuccess) { - return SECFailure; - } - - *added = PR_TRUE; - return SECSuccess; -} - -static SECStatus -tls13_ServerSendEsniXtn(const sslSocket *ss, TLSExtensionData *xtnData, - sslBuffer *buf, PRBool *added) -{ - SECStatus rv; - - rv = sslBuffer_Append(buf, xtnData->esniNonce, sizeof(xtnData->esniNonce)); - if (rv != SECSuccess) { - return SECFailure; - } - - *added = PR_TRUE; - return SECSuccess; -} - -SECStatus -tls13_ServerHandleEsniXtn(const sslSocket *ss, TLSExtensionData *xtnData, - SECItem *data) +tls13_ClientHandleEchXtn(const sslSocket *ss, TLSExtensionData *xtnData, + SECItem *data) { - sslReadBuffer buf; - PRUint8 *plainText = NULL; - unsigned int ptLen; SECStatus rv; + PRCList parsedConfigs; + PR_INIT_CLIST(&parsedConfigs); - /* If we are doing < TLS 1.3, then ignore this. */ - if (ss->version < SSL_LIBRARY_VERSION_TLS_1_3) { - return SECSuccess; - } - - if (!ss->esniKeys) { - /* Apparently we used to be configured for ESNI, but - * no longer. This violates the spec, or the client is - * broken. */ - return SECFailure; - } - - plainText = PORT_ZAlloc(data->len); - if (!plainText) { - return SECFailure; - } - rv = tls13_ServerDecryptEsniXtn(ss, data->data, data->len, - plainText, &ptLen, data->len); - if (rv) { - goto loser; - } - - /* Read out the interior extension. */ - sslReader sniRdr = SSL_READER(plainText, ptLen); - - rv = sslRead_Read(&sniRdr, sizeof(xtnData->esniNonce), &buf); - if (rv != SECSuccess) { - goto loser; - } - PORT_Memcpy(xtnData->esniNonce, buf.buf, sizeof(xtnData->esniNonce)); - - /* We need to capture the whole block with the length. */ - SECItem sniItem = { siBuffer, (unsigned char *)SSL_READER_CURRENT(&sniRdr), 0 }; - rv = sslRead_ReadVariable(&sniRdr, 2, &buf); - if (rv != SECSuccess) { - goto loser; - } - sniItem.len = buf.len + 2; - - /* Check the padding. Note we don't need to do this in constant time - * because it's inside the AEAD boundary. */ - /* TODO(ekr@rtfm.com): check that the padding is the right length. */ - PRUint64 tmp; - while (SSL_READER_REMAINING(&sniRdr)) { - rv = sslRead_ReadNumber(&sniRdr, 1, &tmp); - if (rv != SECSuccess) { - goto loser; - } - if (tmp != 0) { - goto loser; - } - } - - rv = ssl3_HandleServerNameXtn(ss, xtnData, &sniItem); - if (rv != SECSuccess) { - goto loser; - } - - rv = ssl3_RegisterExtensionSender(ss, xtnData, - ssl_tls13_encrypted_sni_xtn, - tls13_ServerSendEsniXtn); - if (rv != SECSuccess) { - goto loser; - } - - /* Keep track of negotiated extensions. */ - xtnData->negotiated[xtnData->numNegotiated++] = - ssl_tls13_encrypted_sni_xtn; - - PORT_ZFree(plainText, data->len); - return SECSuccess; -loser: - PORT_ZFree(plainText, data->len); - return SECFailure; -} - -/* Function to check the extension. We don't install a handler here - * because we need to check for the presence of the extension as - * well and it's easier to do it in one place. */ -SECStatus -tls13_ClientCheckEsniXtn(sslSocket *ss) -{ - TLSExtension *esniExtension = - ssl3_FindExtension(ss, ssl_tls13_encrypted_sni_xtn); - if (!esniExtension) { - FATAL_ERROR(ss, SSL_ERROR_MISSING_ESNI_EXTENSION, missing_extension); + /* 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); + if (rv == SECFailure) { + ssl3_ExtSendAlert(ss, alert_fatal, decode_error); + PORT_SetError(SSL_ERROR_RX_MALFORMED_ECH_CONFIG); return SECFailure; } - - if (esniExtension->data.len != sizeof(ss->xtnData.esniNonce)) { - FATAL_ERROR(ss, SSL_ERROR_RX_MALFORMED_ESNI_EXTENSION, illegal_parameter); - 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. */ + if (ss->ssl3.hs.echHpkeCtx && !PR_CLIST_IS_EMPTY(&parsedConfigs)) { + rv = SECITEM_CopyItem(NULL, &xtnData->echRetryConfigs, data); } + tls13_DestroyEchConfigs(&parsedConfigs); - if (0 != NSS_SecureMemcmp(esniExtension->data.data, - ss->xtnData.esniNonce, - sizeof(ss->xtnData.esniNonce))) { - FATAL_ERROR(ss, SSL_ERROR_RX_MALFORMED_ESNI_EXTENSION, illegal_parameter); - return SECFailure; - } - - return SECSuccess; + return rv; } /* Indicates support for the delegated credentials extension. This should be @@ -1691,3 +1423,123 @@ tls13_ServerHandleDelegatedCredentialsXtn(const sslSocket *ss, ss, xtnData, ssl_delegated_credentials_xtn, tls13_ServerSendDelegatedCredentialsXtn); } + +/* Adds the ECH extension containing server retry_configs */ +SECStatus +tls13_ServerSendEchXtn(const sslSocket *ss, + TLSExtensionData *xtnData, + sslBuffer *buf, PRBool *added) +{ + SECStatus rv; + PORT_Assert(ss->version >= SSL_LIBRARY_VERSION_TLS_1_3); + if (PR_CLIST_IS_EMPTY(&ss->echConfigs)) { + return SECSuccess; + } + + const sslEchConfig *cfg = (sslEchConfig *)PR_LIST_HEAD(&ss->echConfigs); + rv = sslBuffer_AppendVariable(buf, cfg->raw.data, cfg->raw.len, 2); + if (rv != SECSuccess) { + return SECFailure; + } + + *added = PR_TRUE; + return SECSuccess; +} + +SECStatus +tls13_ServerHandleEchXtn(const sslSocket *ss, TLSExtensionData *xtnData, + SECItem *data) +{ + SECStatus rv; + HpkeKdfId kdf; + HpkeAeadId aead; + PRUint32 tmp; + SECItem configId; + SECItem senderPubKey; + SECItem encryptedCh; + + /* Ignore it if not doing 1.3+. If we have no ECHConfigs, + * proceed to save the config_id for HRR validation. */ + if (ss->version < SSL_LIBRARY_VERSION_TLS_1_3 || + IS_DTLS(ss)) { + return SECSuccess; + } + + /* On CHInner, the extension must be empty. */ + if (ss->ssl3.hs.echAccepted && data->len > 0) { + ssl3_ExtSendAlert(ss, alert_fatal, illegal_parameter); + PORT_SetError(SSL_ERROR_RX_MALFORMED_ECH_EXTENSION); + 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. */ + rv = ssl3_ExtConsumeHandshakeNumber(ss, &tmp, 2, + &data->data, &data->len); + if (rv != SECSuccess) { + goto alert_loser; + } + kdf = (HpkeKdfId)tmp; + rv = ssl3_ExtConsumeHandshakeNumber(ss, &tmp, 2, + &data->data, &data->len); + if (rv != SECSuccess) { + goto alert_loser; + } + aead = (HpkeAeadId)tmp; + + /* config_id */ + rv = ssl3_ExtConsumeHandshakeVariable(ss, &configId, 1, + &data->data, &data->len); + if (rv != SECSuccess) { + goto alert_loser; + } + + /* enc */ + rv = ssl3_ExtConsumeHandshakeVariable(ss, &senderPubKey, 2, + &data->data, &data->len); + if (rv != SECSuccess) { + goto alert_loser; + } + + /* payload */ + rv = ssl3_ExtConsumeHandshakeVariable(ss, &encryptedCh, 2, + &data->data, &data->len); + if (rv != SECSuccess) { + goto alert_loser; + } + + if (data->len) { + goto alert_loser; + } + + /* All fields required. */ + if (!configId.len || !senderPubKey.len || !encryptedCh.len) { + goto alert_loser; + } + + rv = SECITEM_CopyItem(NULL, &xtnData->echSenderPubKey, &senderPubKey); + if (rv == SECFailure) { + return SECFailure; + } + + rv = SECITEM_CopyItem(NULL, &xtnData->innerCh, &encryptedCh); + if (rv == SECFailure) { + return SECFailure; + } + + rv = SECITEM_CopyItem(NULL, &xtnData->echConfigId, &configId); + if (rv == SECFailure) { + return SECFailure; + } + xtnData->echCipherSuite = (aead & 0xFFFF) << 16 | (kdf & 0xFFFF); + + /* Not negotiated until tls13_MaybeAcceptEch. */ + return SECSuccess; + +alert_loser: + ssl3_ExtSendAlert(ss, alert_fatal, decode_error); + PORT_SetError(SSL_ERROR_RX_MALFORMED_ECH_EXTENSION); + return SECFailure; +} diff --git a/lib/ssl/tls13exthandle.h b/lib/ssl/tls13exthandle.h index abc7f9debd..5567372101 100644 --- a/lib/ssl/tls13exthandle.h +++ b/lib/ssl/tls13exthandle.h @@ -88,11 +88,12 @@ SECStatus tls13_DecodeKeyShareEntry(sslReader *rdr, TLS13KeyShareEntry **ksp); PRUint32 tls13_SizeOfKeyShareEntry(const SECKEYPublicKey *pubKey); SECStatus tls13_EncodeKeyShareEntry(sslBuffer *buf, SSLNamedGroup group, SECKEYPublicKey *pubKey); -SECStatus tls13_ClientSendEsniXtn(const sslSocket *ss, TLSExtensionData *xtnData, - sslBuffer *buf, PRBool *added); -SECStatus tls13_ServerHandleEsniXtn(const sslSocket *ss, TLSExtensionData *xtnData, - SECItem *data); -SECStatus tls13_ClientCheckEsniXtn(sslSocket *ss); +SECStatus tls13_ServerHandleEchXtn(const sslSocket *ss, TLSExtensionData *xtnData, + SECItem *data); +SECStatus tls13_ServerSendEchXtn(const sslSocket *ss, TLSExtensionData *xtnData, + sslBuffer *buf, PRBool *added); +SECStatus tls13_ClientHandleEchXtn(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 53d3738f0c..011b4838e7 100644 --- a/lib/ssl/tls13hashstate.c +++ b/lib/ssl/tls13hashstate.c @@ -12,6 +12,7 @@ #include "sslimpl.h" #include "selfencrypt.h" #include "tls13con.h" +#include "tls13ech.h" #include "tls13err.h" #include "tls13hashstate.h" @@ -24,8 +25,13 @@ * uint16 cipherSuite; // Selected cipher suite. * uint16 keyShare; // Requested key share group (0=none) * 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; + * + * An empty echConfigId means that ECH was not offered in the first ClientHello. + * An empty echHrrPsk means that ECH was not accepted in CH1. */ SECStatus tls13_MakeHrrCookie(sslSocket *ss, const sslNamedGroupDef *selectedGroup, @@ -37,6 +43,10 @@ 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; /* Encode header. */ rv = sslBuffer_Append(&cookieBuf, &indicator, 1); @@ -59,6 +69,48 @@ tls13_MakeHrrCookie(sslSocket *ss, const sslNamedGroupDef *selectedGroup, 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 (rv != SECSuccess) { + return SECFailure; + } + rv = PK11_ExtractKeyValue(echHrrPsk); + if (rv != SECSuccess) { + PK11_FreeSymKey(echHrrPsk); + return SECFailure; + } + rawEchPsk = PK11_GetKeyData(echHrrPsk); + if (!rawEchPsk) { + PK11_FreeSymKey(echHrrPsk); + 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); + } + if (rv != SECSuccess) { + return SECFailure; + } + /* Compute and encode hashes. */ rv = tls13_ComputeHandshakeHashes(ss, &hashes); if (rv != SECSuccess) { @@ -84,12 +136,15 @@ SECStatus tls13_RecoverHashState(sslSocket *ss, unsigned char *cookie, unsigned int cookieLen, ssl3CipherSuite *previousCipherSuite, - const sslNamedGroupDef **previousGroup) + const sslNamedGroupDef **previousGroup, + PRBool *previousEchOffered) { SECStatus rv; unsigned char plaintext[1024]; unsigned int plaintextLen = 0; sslBuffer messageBuf = SSL_BUFFER_EMPTY; + sslReadBuffer echPskBuf; + sslReadBuffer echConfigIdBuf; PRUint64 sentinel; PRUint64 cipherSuite; PRUint64 group; @@ -104,9 +159,9 @@ tls13_RecoverHashState(sslSocket *ss, sslReader reader = SSL_READER(plaintext, plaintextLen); - /* Should start with 0xff. */ + /* Should start with the sentinel value. */ rv = sslRead_ReadNumber(&reader, 1, &sentinel); - if ((rv != SECSuccess) || (sentinel != 0xff)) { + if ((rv != SECSuccess) || (sentinel != TLS13_COOKIE_SENTINEL)) { FATAL_ERROR(ss, SSL_ERROR_RX_MALFORMED_CLIENT_HELLO, illegal_parameter); return SECFailure; } @@ -147,6 +202,19 @@ tls13_RecoverHashState(sslSocket *ss, PORT_Assert(appTokenReader.len == appTokenLen); PORT_Memcpy(ss->xtnData.applicationToken.data, appTokenReader.buf, appTokenLen); + /* ECH Config ID, which 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); + 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)) { @@ -183,5 +251,6 @@ tls13_RecoverHashState(sslSocket *ss, *previousCipherSuite = cipherSuite; *previousGroup = selectedGroup; + *previousEchOffered = echConfigIdBuf.len > 0; return SECSuccess; } diff --git a/lib/ssl/tls13hashstate.h b/lib/ssl/tls13hashstate.h index e9a4aa84f3..8126bd0db3 100644 --- a/lib/ssl/tls13hashstate.h +++ b/lib/ssl/tls13hashstate.h @@ -18,8 +18,8 @@ SECStatus tls13_MakeHrrCookie(sslSocket *ss, const sslNamedGroupDef *selectedGro 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, + unsigned char *cookie, unsigned int cookieLen, ssl3CipherSuite *previousCipherSuite, - const sslNamedGroupDef **previousGroup); + const sslNamedGroupDef **previousGroup, + PRBool *previousEchOffered); #endif