blob: c1e241df40363fdceedcf8e9bf9eeeb9bce16fcf [file] [log] [blame]
// 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/third_party/quiche/src/common/platform/api/quiche_text_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 =
quiche::QuicheTextUtils::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