Add a method to retrieve the creation time of TLS session tickets used for resumption. Specifically: * Adds GetSessionTicketCreationTime() to QuicCryptoClientStreamBase, QuicCryptoClientStream, and the handshaker interfaces. * Implements GetSessionTicketCreationTime() in TlsClientHandshaker using SSL_SESSION_get_time on the cached TLS session. * Adds unit tests in tls_client_handshaker_test.cc to verify the creation time is correctly retrieved during resumption and 0-RTT. PiperOrigin-RevId: 937773796
diff --git a/quiche/quic/core/http/end_to_end_test.cc b/quiche/quic/core/http/end_to_end_test.cc index 7ded223..0b0a62a 100644 --- a/quiche/quic/core/http/end_to_end_test.cc +++ b/quiche/quic/core/http/end_to_end_test.cc
@@ -7731,6 +7731,41 @@ ADD_FAILURE() << "Client should not have 10 resumption tickets."; } +TEST_P(EndToEndTest, TlsSessionTicketCreationTime) { + QuicWallTime start_time = QuicDefaultClock::Get()->WallNow(); + SetQuicFlag(quic_disable_server_tls_resumption, false); + ASSERT_TRUE(Initialize()); + + if (!version_.IsIetfQuic()) { + // This test is TLS specific. + return; + } + + // Send the first request and then disconnect. The client receives a + // resumption ticket. + SendSynchronousFooRequestAndCheckResponse(); + QuicSpdyClientSession* client_session = GetClientSession(); + ASSERT_TRUE(client_session); + EXPECT_FALSE(client_session->EarlyDataAccepted()); + EXPECT_FALSE(client_session->GetCryptoStream() + ->GetSessionTicketCreationTime() + .has_value()); + client_->Disconnect(); + + // Send the second request in 0RTT / Resumption. + client_->Connect(); + SendSynchronousFooRequestAndCheckResponse(); + + client_session = GetClientSession(); + ASSERT_TRUE(client_session); + EXPECT_TRUE(client_session->EarlyDataAccepted()); + std::optional<QuicWallTime> ticket_time = + client_session->GetCryptoStream()->GetSessionTicketCreationTime(); + ASSERT_TRUE(ticket_time.has_value()); + EXPECT_GE(ticket_time->ToUNIXSeconds(), start_time.ToUNIXSeconds()); + client_->Disconnect(); +} + TEST_P(EndToEndTest, BlockServerUntilSettingsReceived) { SetQuicReloadableFlag(quic_block_until_settings_received_copt, true); // Force loss to test data stream being blocked when SETTINGS are missing.
diff --git a/quiche/quic/core/quic_crypto_client_handshaker.h b/quiche/quic/core/quic_crypto_client_handshaker.h index e0adf83..89371ec 100644 --- a/quiche/quic/core/quic_crypto_client_handshaker.h +++ b/quiche/quic/core/quic_crypto_client_handshaker.h
@@ -43,6 +43,9 @@ bool IsResumption() const override; bool EarlyDataAccepted() const override; ssl_early_data_reason_t EarlyDataReason() const override; + std::optional<QuicWallTime> GetSessionTicketCreationTime() const override { + return std::nullopt; + } bool ReceivedInchoateReject() const override; int num_scup_messages_received() const override; std::string chlo_hash() const override;
diff --git a/quiche/quic/core/quic_crypto_client_stream.cc b/quiche/quic/core/quic_crypto_client_stream.cc index 356998e..d18f0fa 100644 --- a/quiche/quic/core/quic_crypto_client_stream.cc +++ b/quiche/quic/core/quic_crypto_client_stream.cc
@@ -85,6 +85,11 @@ return handshaker_->EarlyDataReason(); } +std::optional<QuicWallTime> +QuicCryptoClientStream::GetSessionTicketCreationTime() const { + return handshaker_->GetSessionTicketCreationTime(); +} + bool QuicCryptoClientStream::ReceivedInchoateReject() const { return handshaker_->ReceivedInchoateReject(); }
diff --git a/quiche/quic/core/quic_crypto_client_stream.h b/quiche/quic/core/quic_crypto_client_stream.h index b7d4120..6c38249 100644 --- a/quiche/quic/core/quic_crypto_client_stream.h +++ b/quiche/quic/core/quic_crypto_client_stream.h
@@ -20,6 +20,7 @@ #include "quiche/quic/core/quic_crypto_stream.h" #include "quiche/quic/core/quic_server_id.h" #include "quiche/quic/core/quic_session.h" +#include "quiche/quic/core/quic_time.h" #include "quiche/quic/core/quic_types.h" #include "quiche/quic/core/quic_versions.h" #include "quiche/quic/platform/api/quic_export.h" @@ -35,11 +36,14 @@ class QUICHE_EXPORT QuicCryptoClientStreamBase : public QuicCryptoStream { public: explicit QuicCryptoClientStreamBase(QuicSession* session); + QuicCryptoClientStreamBase(const QuicCryptoClientStreamBase&) = delete; + QuicCryptoClientStreamBase& operator=(const QuicCryptoClientStreamBase&) = + delete; ~QuicCryptoClientStreamBase() override {} - // Performs a crypto handshake with the server. Returns true if the connection - // is still connected. + // Performs a crypto handshake with the server. Returns true if the + // connection is still connected. virtual bool CryptoConnect() = 0; // DEPRECATED: Use IsResumption, EarlyDataAccepted, and/or @@ -53,9 +57,9 @@ // Whether TLS resumption was attempted by this client. IETF QUIC only. virtual bool ResumptionAttempted() const = 0; - // Returns true if the handshake performed was a resumption instead of a full - // handshake. Resumption only makes sense for TLS handshakes - there is no - // concept of resumption for QUIC crypto even though it supports a 0-RTT + // Returns true if the handshake performed was a resumption instead of a + // full handshake. Resumption only makes sense for TLS handshakes - there is + // no concept of resumption for QUIC crypto even though it supports a 0-RTT // handshake. This function only returns valid results once the handshake is // complete. virtual bool IsResumption() const = 0; @@ -102,10 +106,17 @@ CachedNetworkParameters /*cached_network_params*/) override { QUICHE_DCHECK(false); } + + // Returns the creation time of the session ticket used for resumption, if + // resumption was attempted. + virtual std::optional<QuicWallTime> GetSessionTicketCreationTime() const = 0; }; class QUICHE_EXPORT QuicCryptoClientStream : public QuicCryptoClientStreamBase { public: + // Returns the creation time of the session ticket used for resumption, if + // resumption was attempted. + std::optional<QuicWallTime> GetSessionTicketCreationTime() const override; // kMaxClientHellos is the maximum number of times that we'll send a client // hello. The value 4 accounts for: // * One failure due to an incorrect or missing source-address token. @@ -161,6 +172,11 @@ // rejected. virtual ssl_early_data_reason_t EarlyDataReason() const = 0; + // Returns the creation time of the session ticket used for resumption, if + // resumption was attempted. + virtual std::optional<QuicWallTime> GetSessionTicketCreationTime() + const = 0; + // Returns true if the client received an inchoate REJ during the handshake, // extending the handshake by one round trip. This only applies for QUIC // crypto handshakes. The equivalent feature in IETF QUIC is a Retry packet,
diff --git a/quiche/quic/core/tls_client_handshaker.cc b/quiche/quic/core/tls_client_handshaker.cc index a9829df..9e90268 100644 --- a/quiche/quic/core/tls_client_handshaker.cc +++ b/quiche/quic/core/tls_client_handshaker.cc
@@ -408,6 +408,19 @@ return TlsHandshaker::EarlyDataReason(); } +std::optional<QuicWallTime> TlsClientHandshaker::GetSessionTicketCreationTime() + const { + if (cached_state_ && cached_state_->tls_session) { + // SSL_SESSION_get_time returns the time the session was originally + // established (i.e. when the ticket was issued in the previous connection). + // It remains fixed to that creation time and is not updated when the + // session is reused/resumed. + return QuicWallTime::FromUNIXSeconds( + SSL_SESSION_get_time(cached_state_->tls_session.get())); + } + return std::nullopt; +} + bool TlsClientHandshaker::ReceivedInchoateReject() const { QUIC_BUG_IF(quic_bug_12736_3, !one_rtt_keys_available()); // REJ messages are a QUIC crypto feature, so TLS always returns false.
diff --git a/quiche/quic/core/tls_client_handshaker.h b/quiche/quic/core/tls_client_handshaker.h index 6322ac6..8a5ae74 100644 --- a/quiche/quic/core/tls_client_handshaker.h +++ b/quiche/quic/core/tls_client_handshaker.h
@@ -48,6 +48,7 @@ bool IsResumption() const override; bool EarlyDataAccepted() const override; ssl_early_data_reason_t EarlyDataReason() const override; + std::optional<QuicWallTime> GetSessionTicketCreationTime() const override; bool ReceivedInchoateReject() const override; int num_scup_messages_received() const override; std::string chlo_hash() const override;
diff --git a/quiche/quic/core/tls_client_handshaker_test.cc b/quiche/quic/core/tls_client_handshaker_test.cc index f8bb1b2..826d402 100644 --- a/quiche/quic/core/tls_client_handshaker_test.cc +++ b/quiche/quic/core/tls_client_handshaker_test.cc
@@ -494,6 +494,7 @@ } TEST_P(TlsClientHandshakerTest, Resumption) { + QuicWallTime start_time = client_helper_.GetClock()->WallNow(); // Disable 0-RTT on the server so that we're only testing 1-RTT resumption: SSL_CTX_set_early_data_enabled(server_crypto_config_->ssl_ctx(), false); // Finish establishing the first connection: @@ -514,6 +515,10 @@ EXPECT_TRUE(stream()->one_rtt_keys_available()); EXPECT_TRUE(stream()->ResumptionAttempted()); EXPECT_TRUE(stream()->IsResumption()); + std::optional<QuicWallTime> ticket_time = + stream()->GetSessionTicketCreationTime(); + ASSERT_TRUE(ticket_time.has_value()); + EXPECT_GE(ticket_time->ToUNIXSeconds(), start_time.ToUNIXSeconds()); } TEST_P(TlsClientHandshakerTest, ResumptionRejection) { @@ -545,6 +550,7 @@ } TEST_P(TlsClientHandshakerTest, ZeroRttResumption) { + QuicWallTime start_time = client_helper_.GetClock()->WallNow(); // Finish establishing the first connection: CompleteCryptoHandshake(); @@ -580,6 +586,10 @@ EXPECT_TRUE(stream()->IsResumption()); EXPECT_TRUE(stream()->EarlyDataAccepted()); EXPECT_EQ(stream()->EarlyDataReason(), ssl_early_data_accepted); + std::optional<QuicWallTime> ticket_time = + stream()->GetSessionTicketCreationTime(); + ASSERT_TRUE(ticket_time.has_value()); + EXPECT_GE(ticket_time->ToUNIXSeconds(), start_time.ToUNIXSeconds()); } // Regression test for b/186438140.