staticauto MakeTestData(const size_t aDataSize) { auto data = nsTArray<uint8_t>();
data.SetLength(aDataSize); // LCG pseudo-random fill: near-incompressible, so the compressed record // size stays close to key.len() + 4 + tokenSize + cert overhead.
uint32_t state = 0xDEADBEEFu; for (auto& b : data) {
state = state * 1664525u + 1013904223u;
b = static_cast<uint8_t>(state >> 24);
} return data;
}
putToken("anon:www.example1.com:443"_ns, 300); // This record will be removed because // "network.ssl_tokens_cache_records_per_entry" is 3.
putToken("anon:www.example1.com:443"_ns, 100);
putToken("anon:www.example1.com:443"_ns, 200);
putToken("anon:www.example1.com:443"_ns, 400);
// Test if records are ordered by the expiration time
getAndCheckResult("anon:www.example1.com:443"_ns, 200);
getAndCheckResult("anon:www.example1.com:443"_ns, 300);
getAndCheckResult("anon:www.example1.com:443"_ns, 400);
}
// Use a high per-entry limit so global-capacity eviction is the only // mechanism under test here (per-entry eviction is already covered by // MultiplePut).
mozilla::Preferences::SetInt("network.ssl_tokens_cache_records_per_entry", 10);
// Token sizes dominate cert overhead, making record sizes predictable. // Capacity of 5 KB holds new alone but not old+new+trigger together.
putToken("anon:evict-old.com:443"_ns, 2000);
putToken("anon:evict-new.com:443"_ns, 4000);
// Trigger has the earliest expiry, so EvictIfNecessary removes it first, // then evict-old.com, until the cache drops below 5 KB.
putToken("anon:evict-trigger.com:443"_ns, 10);
nsTArray<uint8_t> result;
mozilla::net::SessionCacheInfo unused;
ASSERT_EQ(mozilla::net::SSLTokensCache::Get("anon:evict-old.com:443"_ns,
result, unused),
NS_ERROR_NOT_AVAILABLE)
<< "evict-old.com should have been evicted (second-oldest after trigger)";
ASSERT_EQ(mozilla::net::SSLTokensCache::Get("anon:evict-new.com:443"_ns,
result, unused),
NS_OK)
<< "evict-new.com should survive: it fits within the 5 KB capacity";
}
// Verify that ssl_token_cache_evictions only counts evictions of still-valid // tokens. Already-expired tokens removed under capacity pressure must not // be counted (they are tracked by ssl_token_cache_expired instead).
TEST(TestTokensCache, EvictionCountsOnlyValidTokens)
{
ClearAll();
// 2 KB tokens dominate cert overhead, so capacity 3 KB holds one record // but not two — each insertion past the first triggers exactly one eviction.
mozilla::Preferences::SetInt("network.ssl_tokens_cache_capacity", 3);
auto evictionCount = []() { return mozilla::glean::network::ssl_token_cache_evictions.TestGetValue()
.unwrap()
.valueOr(0);
};
PRTime now = PR_Now();
int32_t before = evictionCount();
// Step 1: expired token fits; no eviction yet.
putTokenWithExpiry("anon:evict-expired.com:443"_ns, 2000,
now - PRTime(PR_USEC_PER_SEC));
// Step 2: exceeds capacity → expired record evicted first (smallest expiry). // Must NOT be counted.
putTokenWithExpiry("anon:evict-valid1.com:443"_ns, 2000,
now + PRTime(2000) * PR_USEC_PER_SEC);
// Step 3: exceeds capacity again → valid_1 evicted (next smallest expiry). // MUST be counted.
putTokenWithExpiry("anon:evict-valid2.com:443"_ns, 2000,
now + PRTime(4000) * PR_USEC_PER_SEC);
// One expired eviction (not counted) + one valid eviction (counted) = 1.
ASSERT_EQ(evictionCount() - before, 1);
}
// Verifies that QUIC resumption tokens (used as address validation tokens) are // partitioned by first-party context. A token stored under one partition key // must not be retrievable using a different partition key, preventing // cross-origin tracking across first-party sites.
TEST(TestTokensCache, QuicTokenPartitioning)
{
mozilla::net::SSLTokensCache::Clear();
mozilla::Preferences::SetInt("network.ssl_tokens_cache_records_per_entry", 3);
// Simulate two first-party contexts embedding the same third-party QUIC // server. The peerId format includes the OriginAttributes suffix which // contains the partitionKey. const nsLiteralCString kServerPartitionedUnderA( "quic.example.com:443^partitionKey=%28https%2Ca.example.com%29"); const nsLiteralCString kServerPartitionedUnderB( "quic.example.com:443^partitionKey=%28https%2Cb.example.com%29");
// Store a token in the context of first-party A.
putToken(kServerPartitionedUnderA, 100);
// The token must be retrievable using the same partition key.
nsTArray<uint8_t> result;
mozilla::net::SessionCacheInfo unused;
nsresult rv = mozilla::net::SSLTokensCache::Get(kServerPartitionedUnderA,
result, unused);
ASSERT_EQ(rv, NS_OK);
ASSERT_EQ(result.Length(), (size_t)100);
// Re-insert the token so it can be tested from the B context below.
putToken(kServerPartitionedUnderA, 100);
// The token must NOT be accessible under a different first-party (B).
rv = mozilla::net::SSLTokensCache::Get(kServerPartitionedUnderB, result,
unused);
ASSERT_EQ(rv, NS_ERROR_NOT_AVAILABLE);
// A separate token stored under first-party B must also be isolated.
putToken(kServerPartitionedUnderB, 200);
// The B token must not bleed into the A partition.
rv = mozilla::net::SSLTokensCache::Get(kServerPartitionedUnderA, result,
unused);
ASSERT_EQ(rv, NS_OK);
ASSERT_EQ(result.Length(), (size_t)100);
// Overwrite with correct magic+version but no body, so decompression // fails with a Truncated error (not BadVersion).
FILE* f = fopen(path.get(), "wb"); if (f) {
fwrite("STCF\x03", 1, 5, f);
fclose(f);
}
ClearAll();
mozilla::net::SSLTokensCache::LoadForTest(path); // must not crash
// Only the unconsumed record (size 200) should be present
ASSERT_EQ(mozilla::net::SSLTokensCache::Get("anon:example.com:443"_ns, result,
unused),
NS_OK);
ASSERT_EQ(result.Length(), (size_t)200);
ASSERT_EQ(mozilla::net::SSLTokensCache::Get("anon:example.com:443"_ns, result,
unused),
NS_ERROR_NOT_AVAILABLE);
}
TEST(TestTokensCache, PersistenceServerCertRoundTrip)
{ // Server cert bytes and succeeded cert // chain must survive a persist/reload cycle so that PSK-resumed TLS 1.3 // connections — which receive no Certificate message — can reconstruct // full security info via RebuildCertificateInfoFromSSLTokenCache().
ClearAll();
nsCString path = GetTempCachePath("test_tls_tc_cert.bin");
nsTArray<uint8_t> result;
mozilla::net::SessionCacheInfo info;
ASSERT_EQ(mozilla::net::SSLTokensCache::Get("anon:example.com:443"_ns, result,
info),
NS_OK);
ASSERT_FALSE(info.mServerCertBytes.IsEmpty())
<< "Server cert bytes must survive persistence (bug 2033907)";
ASSERT_TRUE(info.mSucceededCertChainBytes.isSome())
<< "Succeeded cert chain must survive persistence (needed for " "connection coalescing)";
ASSERT_FALSE(info.mSucceededCertChainBytes->IsEmpty())
<< "Succeeded cert chain must not be empty after persistence";
}
// Simulates a PSK-resumed TLS 1.3 connection using a token from the cache: // retrieves the token, creates a fresh CommonSocketControl, calls // SetSessionCacheInfo() + RebuildCertificateInfoFromSSLTokenCache() — // replicating the SetResumptionTokenFromExternalCache + HandshakeCallback path. static RefPtr<CommonSocketControl> SimulateResumedConnection( const nsACString& aKey) {
nsTArray<uint8_t> token;
mozilla::net::SessionCacheInfo info; if (mozilla::net::SSLTokensCache::Get(aKey, token, info) != NS_OK) { return nullptr;
}
RefPtr<CommonSocketControl> sc( new CommonSocketControl(nsLiteralCString("example.com"), 443, 0));
sc->SetSessionCacheInfo(std::move(info));
sc->RebuildCertificateInfoFromSSLTokenCache(); return sc;
}
TEST(TestTokensCache, ResumedConnectionHasValidCertAfterReload)
{ // Verifies the bug 2033907 hypothesis: when a token loaded from disk is // used for a PSK-resumed TLS 1.3 connection (no Certificate message from // the server), RebuildCertificateInfoFromSSLTokenCache() must produce a // server cert with valid DER bytes on the socket.
ClearAll();
nsCString path = GetTempCachePath("test_tls_tc_resumed_cert.bin");
putToken("anon:example.com:443"_ns, 100);
mozilla::net::SSLTokensCache::TriggerWriteForTest(path);
ClearAll();
mozilla::net::SSLTokensCache::LoadForTest(path);
// The reconstructed cert must have non-empty DER bytes — HasServerCert() // alone is not sufficient since nsNSSCertificate(empty_DER) is non-null.
nsCOMPtr<nsIX509Cert> serverCert = sc->GetServerCert();
ASSERT_TRUE(serverCert)
<< "Resumed connection must have a server cert object after " "RebuildCertificateInfoFromSSLTokenCache()";
nsTArray<uint8_t> certDER;
ASSERT_NS_SUCCEEDED(serverCert->GetRawDER(certDER));
ASSERT_FALSE(certDER.IsEmpty())
<< "Cert DER must be non-empty after reconstruction from persisted " "token (bug 2033907)";
}
TEST(TestTokensCache, ResumedConnectionEnablesCoalescing)
{ // Verifies that HTTP/2 connection coalescing is enabled after a PSK // resumption using a token loaded from disk. IsAcceptableForHost() returns // false when mSucceededCertChain is empty.
ClearAll();
nsCString path = GetTempCachePath("test_tls_tc_coalesce.bin");
putToken("anon:example.com:443"_ns, 100);
mozilla::net::SSLTokensCache::TriggerWriteForTest(path);
ClearAll();
mozilla::net::SSLTokensCache::LoadForTest(path);
// The succeeded cert chain must be populated so IsAcceptableForHost() passes.
nsTArray<RefPtr<nsIX509Cert>> chain;
nsCOMPtr<nsITransportSecurityInfo> secInfo;
ASSERT_NS_SUCCEEDED(sc->GetSecurityInfo(getter_AddRefs(secInfo)));
ASSERT_NS_SUCCEEDED(secInfo->GetSucceededCertChain(chain));
ASSERT_FALSE(chain.IsEmpty())
<< "Succeeded cert chain must be non-empty after reload so that " "HTTP/2 connection coalescing is not disabled";
}
TEST(TestTokensCache, PersistenceWriteAfterLoad)
{ // Verify that tokens loaded from disk survive a subsequent flush — // i.e. their IDs are correctly re-registered in the Rust shadow when loaded.
ClearAll();
mozilla::Preferences::SetInt("network.ssl_tokens_cache_records_per_entry", 3);
nsCString path = GetTempCachePath("test_tls_tc_wal.bin");
// Initial write to disk.
mozilla::net::SSLTokensCache::TriggerWriteForTest(path);
// Simulate restart: clear all state, reload from disk.
ClearAll();
mozilla::net::SSLTokensCache::LoadForTest(path);
// Add one more token in the new session and flush again.
putToken("anon:c.example.com:443"_ns, 300);
mozilla::net::SSLTokensCache::TriggerWriteForTest(path);
// Simulate a second restart: clear and reload.
ClearAll();
mozilla::net::SSLTokensCache::LoadForTest(path);
// All three tokens must survive — the two from the first session and the one // added in the second session.
nsTArray<uint8_t> result;
mozilla::net::SessionCacheInfo unused;
ASSERT_EQ(mozilla::net::SSLTokensCache::Get("anon:a.example.com:443"_ns,
result, unused),
NS_OK);
ASSERT_EQ(result.Length(), (size_t)100);
ASSERT_EQ(mozilla::net::SSLTokensCache::Get("anon:b.example.com:443"_ns,
result, unused),
NS_OK);
ASSERT_EQ(result.Length(), (size_t)200);
ASSERT_EQ(mozilla::net::SSLTokensCache::Get("anon:c.example.com:443"_ns,
result, unused),
NS_OK);
ASSERT_EQ(result.Length(), (size_t)300);
}
TEST(TestTokensCache, CertBytesRoundTrip)
{ // Cert bytes must round-trip through Put/Get with byte-perfect fidelity.
mozilla::net::SSLTokensCache::Clear();
ASSERT_FALSE(info.mServerCertBytes.IsEmpty())
<< "Server cert bytes must survive Put/Get round-trip";
ASSERT_TRUE(info.mSucceededCertChainBytes.isSome())
<< "Succeeded cert chain must survive Put/Get round-trip";
ASSERT_EQ(info.mSucceededCertChainBytes->Length(), (size_t)3)
<< "Succeeded cert chain length must be preserved";
// createDummySocketControl() repeats the same cert 3 times in the succeeded // chain; each must equal mServerCertBytes after decompression. for (constauto& chainCert : *info.mSucceededCertChainBytes) {
ASSERT_EQ(chainCert, info.mServerCertBytes)
<< "Each cert in the succeeded chain must equal mServerCertBytes";
}
}
TEST(TestTokensCache, WithinRecordCertDedup)
{ // createDummySocketControl() stores the same cert DER as the server cert // and three times in the succeeded chain. The compressor sees all four // copies in one payload and encodes them once, so the stored record must // be well under 2x the raw single-cert size.
mozilla::net::SSLTokensCache::Clear();
putToken("anon:dedup.example.com:443"_ns, 10);
// Get the token to measure the raw DER size of the fixture cert.
nsTArray<uint8_t> token;
mozilla::net::SessionCacheInfo info;
ASSERT_EQ(mozilla::net::SSLTokensCache::Get("anon:dedup.example.com:443"_ns,
token, info),
NS_OK);
uint32_t rawCertSize = info.mServerCertBytes.Length();
ASSERT_GT(rawCertSize, (uint32_t)0);
// Re-insert so there is a live record to measure.
putToken("anon:dedup.example.com:443"_ns, 10);
uint32_t cacheSize = mozilla::net::SSLTokensCache::CacheSizeForTest();
// Four identical cert blobs in one compressed payload must store to well // under 2x the raw single-cert size.
ASSERT_LT(cacheSize, rawCertSize * 2)
<< "4x identical cert blobs must compress to ~1x; " "rawCertSize="
<< rawCertSize << " cacheSize=" << cacheSize;
}
Messung V0.5 in Prozent
¤ Dauer der Verarbeitung: 0.12 Sekunden
(vorverarbeitet am 2026-09-27)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.