| // Copyright 2020 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "net/quic/quic_client_session_cache.h" |
| |
| #include "base/run_loop.h" |
| #include "base/test/simple_test_clock.h" |
| #include "base/test/task_environment.h" |
| #include "base/time/time.h" |
| #include "build/build_config.h" |
| #include "net/base/hex_utils.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "third_party/boringssl/src/include/openssl/ssl.h" |
| |
| namespace net { |
| |
| namespace { |
| |
| const base::TimeDelta kTimeout = base::TimeDelta::FromSeconds(1000); |
| const quic::QuicVersionLabel kFakeVersionLabel = 0x01234567; |
| const quic::QuicVersionLabel kFakeVersionLabel2 = 0x89ABCDEF; |
| const uint64_t kFakeIdleTimeoutMilliseconds = 12012; |
| const uint8_t kFakeStatelessResetTokenData[16] = { |
| 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, |
| 0x98, 0x99, 0x9A, 0x9B, 0x9C, 0x9D, 0x9E, 0x9F}; |
| const uint64_t kFakeMaxPacketSize = 9001; |
| const uint64_t kFakeInitialMaxData = 101; |
| const bool kFakeDisableMigration = true; |
| const auto kCustomParameter1 = |
| static_cast<quic::TransportParameters::TransportParameterId>(0xffcd); |
| const char* kCustomParameter1Value = "foo"; |
| const auto kCustomParameter2 = |
| static_cast<quic::TransportParameters::TransportParameterId>(0xff34); |
| const char* kCustomParameter2Value = "bar"; |
| |
| std::vector<uint8_t> CreateFakeStatelessResetToken() { |
| return std::vector<uint8_t>( |
| kFakeStatelessResetTokenData, |
| kFakeStatelessResetTokenData + base::size(kFakeStatelessResetTokenData)); |
| } |
| |
| std::unique_ptr<base::SimpleTestClock> MakeTestClock() { |
| std::unique_ptr<base::SimpleTestClock> clock = |
| std::make_unique<base::SimpleTestClock>(); |
| // SimpleTestClock starts at the null base::Time which converts to and from |
| // time_t confusingly. |
| clock->SetNow(base::Time::FromTimeT(1000000000)); |
| return clock; |
| } |
| |
| // Make a TransportParameters that has a few fields set to help test comparison. |
| std::unique_ptr<quic::TransportParameters> MakeFakeTransportParams() { |
| auto params = std::make_unique<quic::TransportParameters>(); |
| params->perspective = quic::Perspective::IS_CLIENT; |
| params->version = kFakeVersionLabel; |
| params->supported_versions.push_back(kFakeVersionLabel); |
| params->supported_versions.push_back(kFakeVersionLabel2); |
| params->max_idle_timeout_ms.set_value(kFakeIdleTimeoutMilliseconds); |
| params->stateless_reset_token = CreateFakeStatelessResetToken(); |
| params->max_udp_payload_size.set_value(kFakeMaxPacketSize); |
| params->initial_max_data.set_value(kFakeInitialMaxData); |
| params->disable_active_migration = kFakeDisableMigration; |
| params->custom_parameters[kCustomParameter1] = kCustomParameter1Value; |
| params->custom_parameters[kCustomParameter2] = kCustomParameter2Value; |
| return params; |
| } |
| |
| namespace { |
| |
| // Generated by running TlsClientHandshakerTest.ZeroRttResumption and in |
| // TlsClientHandshaker::InsertSession calling SSL_SESSION_to_bytes to serialize |
| // the received 0-RTT capable ticket. |
| static const char kCachedSession[] = |
| "3082068702010102020304040213010420b9c2a657e565db0babd09e192a9fc4d768fbd706" |
| "9f03f9278a4a0be62392e55b0420d87ed2ab8cafc986fd2e288bd2d654cd57c3a2bed1d532" |
| "20726e55fed39d021ea10602045ed16771a205020302a300a382025f3082025b30820143a0" |
| "03020102020104300d06092a864886f70d01010b0500302c3110300e060355040a13074163" |
| "6d6520436f311830160603550403130f496e7465726d656469617465204341301e170d3133" |
| "303130313130303030305a170d3233313233313130303030305a302d3110300e060355040a" |
| "130741636d6520436f3119301706035504031310746573742e6578616d706c652e636f6d30" |
| "59301306072a8648ce3d020106082a8648ce3d030107034200040526220e77278300d06bc0" |
| "86aff4f999a828a2ed5cc75adc2972794befe885aa3a9b843de321b36b0a795289cebff1a5" |
| "428bad5e34665ce5e36daad08fb3ffd8a3523050300e0603551d0f0101ff04040302078030" |
| "130603551d25040c300a06082b06010505070301300c0603551d130101ff04023000301b06" |
| "03551d11041430128210746573742e6578616d706c652e636f6d300d06092a864886f70d01" |
| "010b050003820101008c1f1e380831b6437a8b9284d28d4ead38d9503a9fc936db89048aa2" |
| "edd6ec2fb830d962ef7a4f384e679504f4d5520f3272e0b9e702b110aff31711578fa5aeb1" |
| "11e9d184c994b0f97e7b17d1995f3f477f25bc1258398ec0ec729caed55d594a009f48093a" |
| "17f33a7f3bb6e420cc3499838398a421d93c7132efa8bee5ed2645cbc55179c400da006feb" |
| "761badd356cac3bd7a0e6b22a511106a355ec62a4c0ac2541d2996adb4a918c866d10c3e31" |
| "62039a91d4ce600b276740d833380b37f66866d261bf6efa8855e7ae6c7d12a8a864cd9a1f" |
| "4663e07714b0204e51bbc189a2d04c2a5043202379ff1c8cbf30cbb44fde4ee9a1c0c976dc" |
| "4943df2c132ca4020400aa7f047d494e534543555245003072020101020203040402130104" |
| "000420d87ed2ab8cafc986fd2e288bd2d654cd57c3a2bed1d53220726e55fed39d021ea106" |
| "02045ed16771a205020302a300a4020400b20302011db5060404bd909308b807020500ffff" |
| "ffffb9050203093a80ba07040568332d3238bb030101ffbc03040100b20302011db3820307" |
| "30820303308201eba003020102020102300d06092a864886f70d01010b050030243110300e" |
| "060355040a130741636d6520436f3110300e06035504031307526f6f74204341301e170d31" |
| "33303130313130303030305a170d3233313233313130303030305a302c3110300e06035504" |
| "0a130741636d6520436f311830160603550403130f496e7465726d65646961746520434130" |
| "820122300d06092a864886f70d01010105000382010f003082010a0282010100cd3550e70a" |
| "6880e52bf0012b93110c50f723e1d8d2ed489aea3b649f82fae4ad2396a8a19b31d1d64ab2" |
| "79f1c18003184154a5303a82bd57109cfd5d34fd19d3211bcb06e76640e1278998822dd72e" |
| "0d5c059a740d45de325e784e81b4c86097f08b2a8ce057f6b9db5a53641d27e09347d993ee" |
| "acf67be7d297b1a6853775ffaaf78fae924e300b5654fd32f99d3cd82e95f56417ff26d265" |
| "e2b1786c835d67a4d8ae896b6eb34b35a5b1033c209779ed0bf8de25a13a507040ae9e0475" |
| "a26a2f15845b08c3e0554e47dbbc7925b02e580dbcaaa6f2eecde6b8028c5b00b33d44d0a6" |
| "bfb3e72e9d4670de45d1bd79bdc0f2470b71286091c29873152db4b1f30203010001a33830" |
| "36300e0603551d0f0101ff04040302020430130603551d25040c300a06082b060105050703" |
| "01300f0603551d130101ff040530030101ff300d06092a864886f70d01010b050003820101" |
| "00bc4f8234860558dd404a626403819bfc759029d625a002143e75ebdb2898d1befdd326c3" |
| "4b14dc3507d732bb29af7e6af31552db53052a2be0d950efee5e0f699304231611ed8bf73a" |
| "6f216a904c6c2f1a2186d1ed08a8005a7914394d71e7d4b643c808f86365c5fecad8b52934" |
| "2d3b3f03447126d278d75b1dab3ed53f23e36e9b3d695f28727916e5ee56ce22d387c81f05" |
| "919b2a37bd4981eb67d9f57b7072285dbbb61f48b6b14768c069a092aad5a094cf295dafd2" |
| "3ca008f89a5f5ab37a56e5f68df45091c7cb85574677127087a2887ba3baa6d4fc436c6e40" |
| "40885e81621d38974f0c7f0d792418c5adebb10e92a165f8d79b169617ff575c0d4a85b506" |
| "0404bd909308b603010100b70402020403b807020500ffffffffb9050203093a80ba070405" |
| "68332d3238bb030101ff"; |
| |
| } // namespace |
| |
| class QuicClientSessionCacheTest : public testing::Test { |
| public: |
| QuicClientSessionCacheTest() |
| : ssl_ctx_(SSL_CTX_new(TLS_method())), clock_(MakeTestClock()) {} |
| |
| protected: |
| bssl::UniquePtr<SSL_SESSION> NewSSLSession() { |
| std::string cached_session = HexDecode(kCachedSession); |
| SSL_SESSION* session = SSL_SESSION_from_bytes( |
| reinterpret_cast<const uint8_t*>(cached_session.data()), |
| cached_session.size(), ssl_ctx_.get()); |
| return bssl::UniquePtr<SSL_SESSION>(session); |
| } |
| |
| bssl::UniquePtr<SSL_SESSION> MakeTestSession( |
| base::TimeDelta timeout = kTimeout) { |
| bssl::UniquePtr<SSL_SESSION> session = NewSSLSession(); |
| SSL_SESSION_set_time(session.get(), clock_->Now().ToTimeT()); |
| SSL_SESSION_set_timeout(session.get(), timeout.InSeconds()); |
| return session; |
| } |
| |
| bssl::UniquePtr<SSL_CTX> ssl_ctx_; |
| std::unique_ptr<base::SimpleTestClock> clock_; |
| }; |
| |
| } // namespace |
| |
| // Tests that simple insertion and lookup work correctly. |
| TEST_F(QuicClientSessionCacheTest, SingleSession) { |
| QuicClientSessionCache cache; |
| cache.SetClockForTesting(clock_.get()); |
| |
| auto params = MakeFakeTransportParams(); |
| auto session = MakeTestSession(); |
| quic::QuicServerId id1("a.com", 443); |
| |
| auto params2 = MakeFakeTransportParams(); |
| auto session2 = MakeTestSession(); |
| SSL_SESSION* unowned2 = session2.get(); |
| quic::QuicServerId id2("b.com", 443); |
| |
| EXPECT_EQ(nullptr, cache.Lookup(id1, ssl_ctx_.get())); |
| EXPECT_EQ(nullptr, cache.Lookup(id2, ssl_ctx_.get())); |
| EXPECT_EQ(0u, cache.size()); |
| |
| cache.Insert(id1, std::move(session), *params, nullptr); |
| EXPECT_EQ(1u, cache.size()); |
| EXPECT_EQ(*params, *(cache.Lookup(id1, ssl_ctx_.get())->transport_params)); |
| EXPECT_EQ(nullptr, cache.Lookup(id2, ssl_ctx_.get())); |
| // No session is available for id1, even though the entry exists. |
| EXPECT_EQ(1u, cache.size()); |
| EXPECT_EQ(nullptr, cache.Lookup(id1, ssl_ctx_.get())); |
| // Lookup() will trigger a deletion of invalid entry. |
| EXPECT_EQ(0u, cache.size()); |
| |
| auto session3 = MakeTestSession(); |
| SSL_SESSION* unowned3 = session3.get(); |
| quic::QuicServerId id3("c.com", 443); |
| cache.Insert(id3, std::move(session3), *params, nullptr); |
| cache.Insert(id2, std::move(session2), *params2, nullptr); |
| EXPECT_EQ(2u, cache.size()); |
| EXPECT_EQ(unowned2, cache.Lookup(id2, ssl_ctx_.get())->tls_session.get()); |
| EXPECT_EQ(unowned3, cache.Lookup(id3, ssl_ctx_.get())->tls_session.get()); |
| |
| // Verify that the cache is cleared after Lookups. |
| EXPECT_EQ(nullptr, cache.Lookup(id1, ssl_ctx_.get())); |
| EXPECT_EQ(nullptr, cache.Lookup(id2, ssl_ctx_.get())); |
| EXPECT_EQ(nullptr, cache.Lookup(id3, ssl_ctx_.get())); |
| EXPECT_EQ(0u, cache.size()); |
| } |
| |
| TEST_F(QuicClientSessionCacheTest, MultipleSessions) { |
| QuicClientSessionCache cache; |
| cache.SetClockForTesting(clock_.get()); |
| |
| auto params = MakeFakeTransportParams(); |
| auto session = MakeTestSession(); |
| quic::QuicServerId id1("a.com", 443); |
| auto session2 = MakeTestSession(); |
| SSL_SESSION* unowned2 = session2.get(); |
| auto session3 = MakeTestSession(); |
| SSL_SESSION* unowned3 = session3.get(); |
| |
| cache.Insert(id1, std::move(session), *params, nullptr); |
| cache.Insert(id1, std::move(session2), *params, nullptr); |
| cache.Insert(id1, std::move(session3), *params, nullptr); |
| // The latest session is popped first. |
| EXPECT_EQ(unowned3, cache.Lookup(id1, ssl_ctx_.get())->tls_session.get()); |
| EXPECT_EQ(unowned2, cache.Lookup(id1, ssl_ctx_.get())->tls_session.get()); |
| // Only two sessions are cached. |
| EXPECT_EQ(nullptr, cache.Lookup(id1, ssl_ctx_.get())); |
| } |
| |
| // Test that when a different TransportParameter is inserted for |
| // the same server id, the existing entry is removed. |
| TEST_F(QuicClientSessionCacheTest, DifferentTransportParams) { |
| QuicClientSessionCache cache; |
| cache.SetClockForTesting(clock_.get()); |
| |
| auto params = MakeFakeTransportParams(); |
| auto session = MakeTestSession(); |
| quic::QuicServerId id1("a.com", 443); |
| auto session2 = MakeTestSession(); |
| auto session3 = MakeTestSession(); |
| SSL_SESSION* unowned3 = session3.get(); |
| |
| cache.Insert(id1, std::move(session), *params, nullptr); |
| cache.Insert(id1, std::move(session2), *params, nullptr); |
| // tweak the transport parameters a little bit. |
| params->perspective = quic::Perspective::IS_SERVER; |
| cache.Insert(id1, std::move(session3), *params, nullptr); |
| auto resumption_state = cache.Lookup(id1, ssl_ctx_.get()); |
| EXPECT_EQ(unowned3, resumption_state->tls_session.get()); |
| EXPECT_EQ(*params.get(), *resumption_state->transport_params); |
| EXPECT_EQ(nullptr, cache.Lookup(id1, ssl_ctx_.get())); |
| } |
| |
| TEST_F(QuicClientSessionCacheTest, DifferentApplicationState) { |
| QuicClientSessionCache cache; |
| cache.SetClockForTesting(clock_.get()); |
| |
| auto params = MakeFakeTransportParams(); |
| auto session = MakeTestSession(); |
| quic::QuicServerId id1("a.com", 443); |
| auto session2 = MakeTestSession(); |
| auto session3 = MakeTestSession(); |
| SSL_SESSION* unowned3 = session3.get(); |
| quic::ApplicationState state; |
| state.push_back('a'); |
| |
| cache.Insert(id1, std::move(session), *params, &state); |
| cache.Insert(id1, std::move(session2), *params, &state); |
| cache.Insert(id1, std::move(session3), *params, nullptr); |
| auto resumption_state = cache.Lookup(id1, ssl_ctx_.get()); |
| EXPECT_EQ(unowned3, resumption_state->tls_session.get()); |
| EXPECT_EQ(nullptr, resumption_state->application_state); |
| EXPECT_EQ(nullptr, cache.Lookup(id1, ssl_ctx_.get())); |
| } |
| |
| TEST_F(QuicClientSessionCacheTest, BothStatesDifferent) { |
| QuicClientSessionCache cache; |
| cache.SetClockForTesting(clock_.get()); |
| |
| auto params = MakeFakeTransportParams(); |
| auto session = MakeTestSession(); |
| quic::QuicServerId id1("a.com", 443); |
| auto session2 = MakeTestSession(); |
| auto session3 = MakeTestSession(); |
| SSL_SESSION* unowned3 = session3.get(); |
| quic::ApplicationState state; |
| state.push_back('a'); |
| |
| cache.Insert(id1, std::move(session), *params, &state); |
| cache.Insert(id1, std::move(session2), *params, &state); |
| params->perspective = quic::Perspective::IS_SERVER; |
| cache.Insert(id1, std::move(session3), *params, nullptr); |
| auto resumption_state = cache.Lookup(id1, ssl_ctx_.get()); |
| EXPECT_EQ(unowned3, resumption_state->tls_session.get()); |
| EXPECT_EQ(*params.get(), *resumption_state->transport_params); |
| EXPECT_EQ(nullptr, resumption_state->application_state); |
| EXPECT_EQ(nullptr, cache.Lookup(id1, ssl_ctx_.get())); |
| } |
| |
| // When the size limit is exceeded, the oldest entry should be erased. |
| TEST_F(QuicClientSessionCacheTest, SizeLimit) { |
| QuicClientSessionCache cache(2); |
| cache.SetClockForTesting(clock_.get()); |
| |
| auto params = MakeFakeTransportParams(); |
| auto session = MakeTestSession(); |
| quic::QuicServerId id1("a.com", 443); |
| |
| auto session2 = MakeTestSession(); |
| SSL_SESSION* unowned2 = session2.get(); |
| quic::QuicServerId id2("b.com", 443); |
| |
| auto session3 = MakeTestSession(); |
| SSL_SESSION* unowned3 = session3.get(); |
| quic::QuicServerId id3("c.com", 443); |
| |
| cache.Insert(id1, std::move(session), *params, nullptr); |
| cache.Insert(id2, std::move(session2), *params, nullptr); |
| cache.Insert(id3, std::move(session3), *params, nullptr); |
| |
| EXPECT_EQ(2u, cache.size()); |
| EXPECT_EQ(unowned2, cache.Lookup(id2, ssl_ctx_.get())->tls_session.get()); |
| EXPECT_EQ(unowned3, cache.Lookup(id3, ssl_ctx_.get())->tls_session.get()); |
| EXPECT_EQ(nullptr, cache.Lookup(id1, ssl_ctx_.get())); |
| } |
| |
| TEST_F(QuicClientSessionCacheTest, ClearEarlyData) { |
| QuicClientSessionCache cache; |
| cache.SetClockForTesting(clock_.get()); |
| SSL_CTX_set_early_data_enabled(ssl_ctx_.get(), 1); |
| auto params = MakeFakeTransportParams(); |
| auto session = MakeTestSession(); |
| quic::QuicServerId id1("a.com", 443); |
| auto session2 = MakeTestSession(); |
| |
| EXPECT_TRUE(SSL_SESSION_early_data_capable(session.get())); |
| EXPECT_TRUE(SSL_SESSION_early_data_capable(session2.get())); |
| |
| cache.Insert(id1, std::move(session), *params, nullptr); |
| cache.Insert(id1, std::move(session2), *params, nullptr); |
| |
| cache.ClearEarlyData(id1); |
| |
| auto resumption_state = cache.Lookup(id1, ssl_ctx_.get()); |
| EXPECT_FALSE( |
| SSL_SESSION_early_data_capable(resumption_state->tls_session.get())); |
| resumption_state = cache.Lookup(id1, ssl_ctx_.get()); |
| EXPECT_FALSE( |
| SSL_SESSION_early_data_capable(resumption_state->tls_session.get())); |
| EXPECT_EQ(nullptr, cache.Lookup(id1, ssl_ctx_.get())); |
| } |
| |
| // Expired session isn't considered valid and nullptr will be returned upon |
| // Lookup. |
| TEST_F(QuicClientSessionCacheTest, Expiration) { |
| QuicClientSessionCache cache; |
| cache.SetClockForTesting(clock_.get()); |
| |
| auto params = MakeFakeTransportParams(); |
| auto session = MakeTestSession(); |
| quic::QuicServerId id1("a.com", 443); |
| |
| auto session2 = MakeTestSession(3 * kTimeout); |
| SSL_SESSION* unowned2 = session2.get(); |
| quic::QuicServerId id2("b.com", 443); |
| |
| cache.Insert(id1, std::move(session), *params, nullptr); |
| cache.Insert(id2, std::move(session2), *params, nullptr); |
| |
| EXPECT_EQ(2u, cache.size()); |
| // Expire the session. |
| clock_->Advance(kTimeout * 2); |
| // The entry has not been removed yet. |
| EXPECT_EQ(2u, cache.size()); |
| |
| EXPECT_EQ(nullptr, cache.Lookup(id1, ssl_ctx_.get())); |
| EXPECT_EQ(1u, cache.size()); |
| EXPECT_EQ(unowned2, cache.Lookup(id2, ssl_ctx_.get())->tls_session.get()); |
| EXPECT_EQ(1u, cache.size()); |
| } |
| |
| TEST_F(QuicClientSessionCacheTest, FlushOnMemoryNotifications) { |
| base::test::TaskEnvironment task_environment; |
| QuicClientSessionCache cache; |
| cache.SetClockForTesting(clock_.get()); |
| |
| auto params = MakeFakeTransportParams(); |
| auto session = MakeTestSession(); |
| quic::QuicServerId id1("a.com", 443); |
| |
| auto session2 = MakeTestSession(3 * kTimeout); |
| quic::QuicServerId id2("b.com", 443); |
| |
| cache.Insert(id1, std::move(session), *params, nullptr); |
| cache.Insert(id2, std::move(session2), *params, nullptr); |
| |
| EXPECT_EQ(2u, cache.size()); |
| // Expire the session. |
| clock_->Advance(kTimeout * 2); |
| // The entry has not been removed yet. |
| EXPECT_EQ(2u, cache.size()); |
| |
| // Fire a notification that will flush expired sessions. |
| base::MemoryPressureListener::NotifyMemoryPressure( |
| base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_MODERATE); |
| base::RunLoop().RunUntilIdle(); |
| |
| // session is expired and should be flushed. |
| EXPECT_EQ(nullptr, cache.Lookup(id1, ssl_ctx_.get())); |
| EXPECT_EQ(1u, cache.size()); |
| |
| // Fire notification that will flush everything. |
| base::MemoryPressureListener::NotifyMemoryPressure( |
| base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_CRITICAL); |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_EQ(0u, cache.size()); |
| } |
| |
| } // namespace net |