Expose reject_unusable_ech_config via QuicSSLConfig This is necessary for Chromium's EchMode::kStrict to correctly configure BoringSSL when used by QUIC, via QuicChromiumClientSession. See https://boringssl-review.googlesource.com/c/boringssl/+/99327 and https://crrev.com/c/8229533. This is how ECH GREASE is already plumbed. Protected by FLAGS_quic_reloadable_flag_quic_reject_unusable_ech_config. PiperOrigin-RevId: 962850299
diff --git a/quiche/common/quiche_feature_flags_list.h b/quiche/common/quiche_feature_flags_list.h index e4e6774..412762e 100755 --- a/quiche/common/quiche_feature_flags_list.h +++ b/quiche/common/quiche_feature_flags_list.h
@@ -57,6 +57,7 @@ QUICHE_FLAG(bool, quiche_reloadable_flag_quic_priority_respect_incremental, false, false, "If true, respect the incremental parameter of each stream in QuicWriteBlockedList.") QUICHE_FLAG(bool, quiche_reloadable_flag_quic_receive_ack_frequency, false, false, "When true, advertises support for ACK_FREQUENCY and IMMEDIATE_ACK from draft-ietf-quic-ack-frequency-10 and processes them correctly.") QUICHE_FLAG(bool, quiche_reloadable_flag_quic_reject_empty_cid_in_ncid, false, true, "If true, QUIC framer will reject NEW_CONNECTION_ID frames with empty connection IDs.") +QUICHE_FLAG(bool, quiche_reloadable_flag_quic_reject_unusable_ech_config, false, true, "When true, calls SSL_set_reject_unusable_ech_config in TlsClientHandshaker.") QUICHE_FLAG(bool, quiche_reloadable_flag_quic_require_handshake_confirmation, true, true, "If true, require handshake confirmation for QUIC connections, functionally disabling 0-rtt handshakes.") QUICHE_FLAG(bool, quiche_reloadable_flag_quic_test_peer_addr_change_after_normalize, false, false, "If true, QuicConnection::ProcessValidatedPacket will use normalized address to test peer address changes.") QUICHE_FLAG(bool, quiche_reloadable_flag_quic_testonly_default_false, false, false, "A testonly reloadable flag that will always default to false.")
diff --git a/quiche/quic/core/quic_types.cc b/quiche/quic/core/quic_types.cc index 28a28b3..2b82db8 100644 --- a/quiche/quic/core/quic_types.cc +++ b/quiche/quic/core/quic_types.cc
@@ -492,7 +492,8 @@ lhs.signing_algorithm_prefs == rhs.signing_algorithm_prefs && lhs.client_cert_mode == rhs.client_cert_mode && lhs.ech_config_list == rhs.ech_config_list && - lhs.ech_grease_enabled == rhs.ech_grease_enabled; + lhs.ech_grease_enabled == rhs.ech_grease_enabled && + lhs.reject_unusable_ech_config == rhs.reject_unusable_ech_config; } #undef RETURN_STRING_LITERAL // undef for jumbo builds
diff --git a/quiche/quic/core/quic_types.h b/quiche/quic/core/quic_types.h index 1ad2b56..d944929 100644 --- a/quiche/quic/core/quic_types.h +++ b/quiche/quic/core/quic_types.h
@@ -902,6 +902,10 @@ // As a client, whether ECH GREASE is enabled. If `ech_config_list` is // not empty, this value does nothing. bool ech_grease_enabled = false; + // As a client, whether set BoringSSL's reject_unusable_ech_config. See + // BoringSSL's documentation for SSL_set_reject_unusable_ech_config in + // https://boringssl.googlesource.com/boringssl/+/HEAD/include/openssl/ssl.h. + bool reject_unusable_ech_config = false; // If not nullopt, the TLS Trust Anchor IDs to send in the TLS handshake. (See // https://tlswg.org/tls-trust-anchor-ids/draft-ietf-tls-trust-anchor-ids.html.) // The value should be a series of Trust Anchor IDs in wire format (a series
diff --git a/quiche/quic/core/quic_types_test.cc b/quiche/quic/core/quic_types_test.cc index 075054a..ddea65d 100644 --- a/quiche/quic/core/quic_types_test.cc +++ b/quiche/quic/core/quic_types_test.cc
@@ -20,5 +20,16 @@ "Unknown (255)"); } +TEST(QuicSSLConfigTest, Equality) { + QuicSSLConfig config1; + QuicSSLConfig config2; + EXPECT_EQ(config1, config2); + + config1.reject_unusable_ech_config = true; + EXPECT_NE(config1, config2); + config2.reject_unusable_ech_config = true; + EXPECT_EQ(config1, config2); +} + } // namespace } // namespace quic
diff --git a/quiche/quic/core/tls_client_handshaker.cc b/quiche/quic/core/tls_client_handshaker.cc index 9e90268..e4c53f6 100644 --- a/quiche/quic/core/tls_client_handshaker.cc +++ b/quiche/quic/core/tls_client_handshaker.cc
@@ -162,6 +162,11 @@ SSL_set_enable_ech_grease(ssl(), tls_connection_.ssl_config().ech_grease_enabled); + if (GetQuicReloadableFlag(quic_reject_unusable_ech_config)) { + QUIC_RELOADABLE_FLAG_COUNT(quic_reject_unusable_ech_config); + SSL_set_reject_unusable_ech_config( + ssl(), tls_connection_.ssl_config().reject_unusable_ech_config); + } if (!tls_connection_.ssl_config().ech_config_list.empty() && !SSL_set1_ech_config_list( ssl(),
diff --git a/quiche/quic/core/tls_client_handshaker_test.cc b/quiche/quic/core/tls_client_handshaker_test.cc index 7c63be1..a9b11f2 100644 --- a/quiche/quic/core/tls_client_handshaker_test.cc +++ b/quiche/quic/core/tls_client_handshaker_test.cc
@@ -942,6 +942,56 @@ stream()->CryptoConnect(); } +TEST_P(TlsClientHandshakerTest, ECHRejectUnusableConfigFlagEnabled) { + // When reject_unusable_ech_config is enabled, and no usable ECHConfig is + // available, the client should fail before sending a ClientHello. + SetQuicReloadableFlag(quic_reject_unusable_ech_config, true); + ssl_config_.emplace(); + ssl_config_->reject_unusable_ech_config = true; + CreateConnection(); + EXPECT_CALL(*connection_, CloseConnection(QUIC_HANDSHAKE_FAILED, _, _)); + stream()->CryptoConnect(); +} + +TEST_P(TlsClientHandshakerTest, ECHRejectUnusableConfigFlagDisabled) { + // When the flag is disabled, reject_unusable_ech_config is ignored and the + // handshake succeeds without ECH. + SetQuicReloadableFlag(quic_reject_unusable_ech_config, false); + ssl_config_.emplace(); + ssl_config_->reject_unusable_ech_config = true; + CreateConnection(); + + CompleteCryptoHandshake(); + EXPECT_TRUE(stream()->version().IsIetfQuic()); + EXPECT_TRUE(stream()->encryption_established()); + EXPECT_TRUE(stream()->one_rtt_keys_available()); + EXPECT_FALSE(stream()->crypto_negotiated_params().encrypted_client_hello); +} + +TEST_P(TlsClientHandshakerTest, ECHWithRejectUnusableConfig) { + SetQuicReloadableFlag(quic_reject_unusable_ech_config, true); + ssl_config_.emplace(); + bssl::UniquePtr<SSL_ECH_KEYS> ech_keys = + MakeTestEchKeys("public-name.example", /*max_name_len=*/64, + &ssl_config_->ech_config_list); + ASSERT_TRUE(ech_keys); + ssl_config_->reject_unusable_ech_config = true; + + // Configure the server to use the test ECH keys. + ASSERT_TRUE( + SSL_CTX_set1_ech_keys(server_crypto_config_->ssl_ctx(), ech_keys.get())); + + // Recreate the client to pick up the new `ssl_config_`. + CreateConnection(); + + // The handshake should complete and negotiate ECH. + CompleteCryptoHandshake(); + EXPECT_TRUE(stream()->version().IsIetfQuic()); + EXPECT_TRUE(stream()->encryption_established()); + EXPECT_TRUE(stream()->one_rtt_keys_available()); + EXPECT_TRUE(stream()->crypto_negotiated_params().encrypted_client_hello); +} + TEST_P(TlsClientHandshakerTest, ECHWrongKeys) { ssl_config_.emplace(); bssl::UniquePtr<SSL_ECH_KEYS> ech_keys1 =