Add QUIC client support to support requesting server padding through an extension in the TLS handshake. This is to support the cert padding experiment for Chrome (see go/cert-padding-experiment-2026) CL is a no-op for GFEs because there is no GFE server configuration that supports sending the server padding extension back in the TLS handshake, and there are no clients that support this extension. GFE changes (which will come in later CLs) are detailed in go/cert-padding-experiment-2026-gfe PiperOrigin-RevId: 919658597
diff --git a/quiche/quic/core/quic_crypto_client_handshaker.h b/quiche/quic/core/quic_crypto_client_handshaker.h index d3bc204..e0adf83 100644 --- a/quiche/quic/core/quic_crypto_client_handshaker.h +++ b/quiche/quic/core/quic_crypto_client_handshaker.h
@@ -78,6 +78,7 @@ return false; } bool MatchedTrustAnchorIdForTesting() const override { return false; } + bool ServerPaddingSentForTesting() const override { return false; } std::optional<ssl_compliance_policy_t> SslCompliancePolicyForTesting() const override { return std::nullopt;
diff --git a/quiche/quic/core/quic_crypto_client_stream.cc b/quiche/quic/core/quic_crypto_client_stream.cc index ee4d956..356998e 100644 --- a/quiche/quic/core/quic_crypto_client_stream.cc +++ b/quiche/quic/core/quic_crypto_client_stream.cc
@@ -144,6 +144,10 @@ return handshaker_->MatchedTrustAnchorIdForTesting(); } +bool QuicCryptoClientStream::ServerPaddingSentForTesting() const { + return handshaker_->ServerPaddingSentForTesting(); +} + std::optional<ssl_compliance_policy_t> QuicCryptoClientStream::SslCompliancePolicyForTesting() const { return handshaker_->SslCompliancePolicyForTesting();
diff --git a/quiche/quic/core/quic_crypto_client_stream.h b/quiche/quic/core/quic_crypto_client_stream.h index 0d419d3..b7d4120 100644 --- a/quiche/quic/core/quic_crypto_client_stream.h +++ b/quiche/quic/core/quic_crypto_client_stream.h
@@ -245,6 +245,10 @@ // (https://tlswg.org/tls-trust-anchor-ids/draft-ietf-tls-trust-anchor-ids.html#name-overview). virtual bool MatchedTrustAnchorIdForTesting() const = 0; + // Returns true if the server indicated during the handshake that it + // sent the requested amount of padding. + virtual bool ServerPaddingSentForTesting() const = 0; + // Returns the SSL compliance policy configured for the connection, if any. virtual std::optional<ssl_compliance_policy_t> SslCompliancePolicyForTesting() const = 0; @@ -329,6 +333,7 @@ std::string chlo_hash() const; bool MatchedTrustAnchorIdForTesting() const; + bool ServerPaddingSentForTesting() const; std::optional<ssl_compliance_policy_t> SslCompliancePolicyForTesting() const; protected:
diff --git a/quiche/quic/core/quic_types.h b/quiche/quic/core/quic_types.h index 065e93d..f59ea0b 100644 --- a/quiche/quic/core/quic_types.h +++ b/quiche/quic/core/quic_types.h
@@ -891,6 +891,11 @@ // signal support for the extension without advertising particular trust // anchors. If nullopt, the Trust Anchor IDs extension will not be sent. std::optional<std::string> trust_anchor_ids; + + // If not nullopt, the number of bytes of padding to add to the TLS handshake. + // Only used by the client. + // This is experimental, and will be removed once the experiment is complete. + std::optional<uint16_t> server_padding_to_request = std::nullopt; }; QUICHE_EXPORT bool operator==(const QuicSSLConfig& lhs,
diff --git a/quiche/quic/core/tls_client_handshaker.cc b/quiche/quic/core/tls_client_handshaker.cc index 5f00c09..39c3dba 100644 --- a/quiche/quic/core/tls_client_handshaker.cc +++ b/quiche/quic/core/tls_client_handshaker.cc
@@ -188,6 +188,11 @@ } } + if (tls_connection_.ssl_config().server_padding_to_request.has_value()) { + SSL_set_server_padding_request( + ssl(), tls_connection_.ssl_config().server_padding_to_request.value()); + } + // The compliance policy must be the last thing configured before the // handshake in order to have defined behavior. if (ssl_compliance_policy_.has_value()) { @@ -425,6 +430,10 @@ return matched_trust_anchor_id_; } +bool TlsClientHandshaker::ServerPaddingSentForTesting() const { + return server_sent_padding_; +} + std::optional<ssl_compliance_policy_t> TlsClientHandshaker::SslCompliancePolicyForTesting() const { return ssl_compliance_policy_; @@ -629,6 +638,8 @@ } } + server_sent_padding_ = SSL_server_sent_requested_padding(ssl()); + state_ = HANDSHAKE_COMPLETE; handshaker_delegate()->OnTlsHandshakeComplete(); }
diff --git a/quiche/quic/core/tls_client_handshaker.h b/quiche/quic/core/tls_client_handshaker.h index aaa5311..522bf60 100644 --- a/quiche/quic/core/tls_client_handshaker.h +++ b/quiche/quic/core/tls_client_handshaker.h
@@ -54,6 +54,7 @@ bool ExportKeyingMaterial(absl::string_view label, absl::string_view context, size_t result_len, std::string* result) override; bool MatchedTrustAnchorIdForTesting() const override; + bool ServerPaddingSentForTesting() const override; std::optional<ssl_compliance_policy_t> SslCompliancePolicyForTesting() const override; @@ -182,6 +183,10 @@ // is needed only for testing. bool matched_trust_anchor_id_ = false; + // True if the server indicated during the handshake that it sent the + // requested amount of padding. This value is needed only for testing. + bool server_sent_padding_ = false; + // If not nullopt, the SSL compliance policy to use. See documentation for // ssl_compliance_policy_t values in BoringSSL: // https://commondatastorage.googleapis.com/chromium-boringssl-docs/ssl.h.html#Compliance-policy-configurations
diff --git a/quiche/quic/core/tls_client_handshaker_test.cc b/quiche/quic/core/tls_client_handshaker_test.cc index 6ca11a1..226c9cd 100644 --- a/quiche/quic/core/tls_client_handshaker_test.cc +++ b/quiche/quic/core/tls_client_handshaker_test.cc
@@ -323,6 +323,22 @@ EXPECT_NE(stream()->ciphersuite(), nullptr); } +// Test that the connection succeeds when the client sends a server padding +// request and the server does not respond with the requested padding. +// +// TODO(b/515119618): Add a test that checks the padding response once the +// server can respond to the padding request with padding in the response. +TEST_P(TlsClientHandshakerTest, HandshakeWithServerPaddingRequest) { + ssl_config_.emplace(); + ssl_config_->server_padding_to_request = 128; + CreateConnection(); + CompleteCryptoHandshake(); + + EXPECT_TRUE(stream()->encryption_established()); + EXPECT_TRUE(stream()->one_rtt_keys_available()); + EXPECT_FALSE(stream()->ServerPaddingSentForTesting()); +} + TEST_P(TlsClientHandshakerTest, ConnectionClosedOnTlsError) { // Have client send ClientHello. stream()->CryptoConnect();