Bug 1720601 - Allow token cache to store more than one token per key, r=necko-reviewers,dragana

1. Allow to store more than one token per key.
2. Allow to use the token only once. The token will be removed after reading it.
3. Add a gtest.

Differential Revision: https://phabricator.services.mozilla.com/D153605
This commit is contained in:
Kershaw Chang 2022-09-20 12:58:06 +00:00
Родитель 8010ef0166
Коммит 137b76a861
9 изменённых файлов: 424 добавлений и 91 удалений

Просмотреть файл

@ -10943,6 +10943,18 @@
value: 2048
mirror: always
# How many records we store per entry
- name: network.ssl_tokens_cache_records_per_entry
type: RelaxedAtomicUint32
value: 2
mirror: always
# If true, only use the token once
- name: network.ssl_tokens_cache_use_only_once
type: RelaxedAtomicBool
value: true
mirror: always
# The maximum allowed length for a URL - 1MB default.
- name: network.standard-url.max-length
type: RelaxedAtomicUint32

Просмотреть файл

@ -19,6 +19,9 @@ namespace net {
static LazyLogModule gSSLTokensCacheLog("SSLTokensCache");
#undef LOG
#define LOG(args) MOZ_LOG(gSSLTokensCacheLog, mozilla::LogLevel::Debug, args)
#undef LOG5_ENABLED
#define LOG5_ENABLED() \
MOZ_LOG_TEST(mozilla::net::gSSLTokensCacheLog, mozilla::LogLevel::Verbose)
class ExpirationComparator {
public:
@ -48,6 +51,15 @@ SessionCacheInfo SessionCacheInfo::Clone() const {
StaticRefPtr<SSLTokensCache> SSLTokensCache::gInstance;
StaticMutex SSLTokensCache::sLock;
uint64_t SSLTokensCache::sRecordId = 0;
SSLTokensCache::TokenCacheRecord::~TokenCacheRecord() {
if (!gInstance) {
return;
}
gInstance->OnRecordDestroyed(this);
}
uint32_t SSLTokensCache::TokenCacheRecord::Size() const {
uint32_t size = mToken.Length() + sizeof(mSessionCacheInfo.mEVStatus) +
@ -71,6 +83,50 @@ void SSLTokensCache::TokenCacheRecord::Reset() {
mSessionCacheInfo.mSucceededCertChainBytes.reset();
}
uint32_t SSLTokensCache::TokenCacheEntry::Size() const {
uint32_t size = 0;
for (const auto& rec : mRecords) {
size += rec->Size();
}
return size;
}
void SSLTokensCache::TokenCacheEntry::AddRecord(
UniquePtr<SSLTokensCache::TokenCacheRecord>&& aRecord,
nsTArray<TokenCacheRecord*>& aExpirationArray) {
if (mRecords.Length() ==
StaticPrefs::network_ssl_tokens_cache_records_per_entry()) {
aExpirationArray.RemoveElement(mRecords[0].get());
mRecords.RemoveElementAt(0);
}
aExpirationArray.AppendElement(aRecord.get());
for (int32_t i = mRecords.Length() - 1; i >= 0; --i) {
if (aRecord->mExpirationTime > mRecords[i]->mExpirationTime) {
mRecords.InsertElementAt(i + 1, std::move(aRecord));
return;
}
}
mRecords.InsertElementAt(0, std::move(aRecord));
}
UniquePtr<SSLTokensCache::TokenCacheRecord>
SSLTokensCache::TokenCacheEntry::RemoveWithId(uint64_t aId) {
for (int32_t i = mRecords.Length() - 1; i >= 0; --i) {
if (mRecords[i]->mId == aId) {
UniquePtr<TokenCacheRecord> record = std::move(mRecords[i]);
mRecords.RemoveElementAt(i);
return record;
}
}
return nullptr;
}
const UniquePtr<SSLTokensCache::TokenCacheRecord>&
SSLTokensCache::TokenCacheEntry::Get() {
return mRecords[0];
}
NS_IMPL_ISUPPORTS(SSLTokensCache, nsIMemoryReporter)
// static
@ -204,45 +260,53 @@ nsresult SSLTokensCache::Put(const nsACString& aKey, const uint8_t* aToken,
return rv;
}
TokenCacheRecord* const rec =
auto makeUniqueRecord = [&]() {
auto rec = MakeUnique<TokenCacheRecord>();
rec->mKey = aKey;
rec->mExpirationTime = aExpirationTime;
MOZ_ASSERT(rec->mToken.IsEmpty());
rec->mToken.AppendElements(aToken, aTokenLen);
rec->mId = ++sRecordId;
rec->mSessionCacheInfo.mServerCertBytes = std::move(certBytes);
rec->mSessionCacheInfo.mSucceededCertChainBytes =
succeededCertChainBytes
? Some(TransformIntoNewArray(
*succeededCertChainBytes,
[](auto& element) { return nsTArray(std::move(element)); }))
: Nothing();
if (isEV) {
rec->mSessionCacheInfo.mEVStatus = psm::EVStatus::EV;
}
rec->mSessionCacheInfo.mCertificateTransparencyStatus =
certificateTransparencyStatus;
rec->mSessionCacheInfo.mIsBuiltCertChainRootBuiltInRoot =
std::move(isBuiltCertChainRootBuiltInRoot);
return rec;
};
TokenCacheEntry* const cacheEntry =
gInstance->mTokenCacheRecords.WithEntryHandle(aKey, [&](auto&& entry) {
if (!entry) {
auto rec = MakeUnique<TokenCacheRecord>();
rec->mKey = aKey;
gInstance->mExpirationArray.AppendElement(rec.get());
entry.Insert(std::move(rec));
auto rec = makeUniqueRecord();
auto cacheEntry = MakeUnique<TokenCacheEntry>();
cacheEntry->AddRecord(std::move(rec), gInstance->mExpirationArray);
entry.Insert(std::move(cacheEntry));
} else {
// To make sure the cache size is synced, we take away the size of
// whole entry and add it back later.
gInstance->mCacheSize -= entry.Data()->Size();
entry.Data()->Reset();
entry.Data()->AddRecord(makeUniqueRecord(),
gInstance->mExpirationArray);
}
return entry->get();
});
rec->mExpirationTime = aExpirationTime;
MOZ_ASSERT(rec->mToken.IsEmpty());
rec->mToken.AppendElements(aToken, aTokenLen);
rec->mSessionCacheInfo.mServerCertBytes = std::move(certBytes);
rec->mSessionCacheInfo.mSucceededCertChainBytes =
succeededCertChainBytes
? Some(TransformIntoNewArray(
*succeededCertChainBytes,
[](auto& element) { return nsTArray(std::move(element)); }))
: Nothing();
if (isEV) {
rec->mSessionCacheInfo.mEVStatus = psm::EVStatus::EV;
}
rec->mSessionCacheInfo.mCertificateTransparencyStatus =
certificateTransparencyStatus;
rec->mSessionCacheInfo.mIsBuiltCertChainRootBuiltInRoot =
std::move(isBuiltCertChainRootBuiltInRoot);
gInstance->mCacheSize += rec->Size();
gInstance->mCacheSize += cacheEntry->Size();
gInstance->LogStats();
@ -252,8 +316,8 @@ nsresult SSLTokensCache::Put(const nsACString& aKey, const uint8_t* aToken,
}
// static
nsresult SSLTokensCache::Get(const nsACString& aKey,
nsTArray<uint8_t>& aToken) {
nsresult SSLTokensCache::Get(const nsACString& aKey, nsTArray<uint8_t>& aToken,
SessionCacheInfo& aResult, uint64_t* aTokenId) {
StaticMutexAutoLock lock(sLock);
LOG(("SSLTokensCache::Get [key=%s]", PromiseFlatCString(aKey).get()));
@ -263,13 +327,38 @@ nsresult SSLTokensCache::Get(const nsACString& aKey,
return NS_ERROR_NOT_INITIALIZED;
}
TokenCacheRecord* rec = nullptr;
return gInstance->GetLocked(aKey, aToken, aResult, aTokenId);
}
if (gInstance->mTokenCacheRecords.Get(aKey, &rec)) {
if (rec->mToken.Length()) {
aToken = rec->mToken.Clone();
return NS_OK;
nsresult SSLTokensCache::GetLocked(const nsACString& aKey,
nsTArray<uint8_t>& aToken,
SessionCacheInfo& aResult,
uint64_t* aTokenId) {
sLock.AssertCurrentThreadOwns();
TokenCacheEntry* cacheEntry = nullptr;
if (mTokenCacheRecords.Get(aKey, &cacheEntry)) {
if (cacheEntry->RecordCount() == 0) {
MOZ_ASSERT(false, "Found a cacheEntry with no records");
mTokenCacheRecords.Remove(aKey);
return NS_ERROR_NOT_AVAILABLE;
}
const UniquePtr<TokenCacheRecord>& rec = cacheEntry->Get();
aToken = rec->mToken.Clone();
aResult = rec->mSessionCacheInfo.Clone();
if (aTokenId) {
*aTokenId = rec->mId;
}
if (StaticPrefs::network_ssl_tokens_cache_use_only_once()) {
mCacheSize -= rec->Size();
cacheEntry->RemoveWithId(rec->mId);
if (cacheEntry->RecordCount() == 0) {
mTokenCacheRecords.Remove(aKey);
}
}
return NS_OK;
}
LOG((" token not found"));
@ -277,31 +366,7 @@ nsresult SSLTokensCache::Get(const nsACString& aKey,
}
// static
bool SSLTokensCache::GetSessionCacheInfo(const nsACString& aKey,
SessionCacheInfo& aResult) {
StaticMutexAutoLock lock(sLock);
LOG(("SSLTokensCache::GetSessionCacheInfo [key=%s]",
PromiseFlatCString(aKey).get()));
if (!gInstance) {
LOG((" service not initialized"));
return false;
}
TokenCacheRecord* rec = nullptr;
if (gInstance->mTokenCacheRecords.Get(aKey, &rec)) {
aResult = rec->mSessionCacheInfo.Clone();
return true;
}
LOG((" token not found"));
return false;
}
// static
nsresult SSLTokensCache::Remove(const nsACString& aKey) {
nsresult SSLTokensCache::Remove(const nsACString& aKey, uint64_t aId) {
StaticMutexAutoLock lock(sLock);
LOG(("SSLTokensCache::Remove [key=%s]", PromiseFlatCString(aKey).get()));
@ -311,33 +376,75 @@ nsresult SSLTokensCache::Remove(const nsACString& aKey) {
return NS_ERROR_NOT_INITIALIZED;
}
return gInstance->RemoveLocked(aKey);
return gInstance->RemoveLocked(aKey, aId);
}
nsresult SSLTokensCache::RemoveLocked(const nsACString& aKey) {
nsresult SSLTokensCache::RemoveLocked(const nsACString& aKey, uint64_t aId) {
sLock.AssertCurrentThreadOwns();
LOG(("SSLTokensCache::RemoveLocked [key=%s]",
PromiseFlatCString(aKey).get()));
LOG(("SSLTokensCache::RemoveLocked [key=%s, id=%" PRIu64 "]",
PromiseFlatCString(aKey).get(), aId));
UniquePtr<TokenCacheRecord> rec;
TokenCacheEntry* cacheEntry;
if (!mTokenCacheRecords.Get(aKey, &cacheEntry)) {
return NS_ERROR_NOT_AVAILABLE;
}
if (!mTokenCacheRecords.Remove(aKey, &rec)) {
LOG((" token not found"));
UniquePtr<TokenCacheRecord> rec = cacheEntry->RemoveWithId(aId);
if (!rec) {
return NS_ERROR_NOT_AVAILABLE;
}
mCacheSize -= rec->Size();
if (!mExpirationArray.RemoveElement(rec.get())) {
MOZ_ASSERT(false, "token not found in mExpirationArray");
if (cacheEntry->RecordCount() == 0) {
mTokenCacheRecords.Remove(aKey);
}
// Release the record immediately, so mExpirationArray can be also updated.
rec = nullptr;
LogStats();
return NS_OK;
}
// static
nsresult SSLTokensCache::RemoveAll(const nsACString& aKey) {
StaticMutexAutoLock lock(sLock);
LOG(("SSLTokensCache::RemoveAll [key=%s]", PromiseFlatCString(aKey).get()));
if (!gInstance) {
LOG((" service not initialized"));
return NS_ERROR_NOT_INITIALIZED;
}
return gInstance->RemovAllLocked(aKey);
}
nsresult SSLTokensCache::RemovAllLocked(const nsACString& aKey) {
sLock.AssertCurrentThreadOwns();
LOG(("SSLTokensCache::RemovAllLocked [key=%s]",
PromiseFlatCString(aKey).get()));
UniquePtr<TokenCacheEntry> cacheEntry;
if (!mTokenCacheRecords.Remove(aKey, &cacheEntry)) {
return NS_ERROR_NOT_AVAILABLE;
}
mCacheSize -= cacheEntry->Size();
cacheEntry = nullptr;
LogStats();
return NS_OK;
}
void SSLTokensCache::OnRecordDestroyed(TokenCacheRecord* aRec) {
mExpirationArray.RemoveElement(aRec);
}
void SSLTokensCache::EvictIfNecessary() {
// kilobytes to bytes
uint32_t capacity = StaticPrefs::network_ssl_tokens_cache_capacity() << 10;
@ -350,17 +457,23 @@ void SSLTokensCache::EvictIfNecessary() {
mExpirationArray.Sort(ExpirationComparator());
while (mCacheSize > capacity && mExpirationArray.Length() > 0) {
if (NS_FAILED(RemoveLocked(mExpirationArray[0]->mKey))) {
MOZ_ASSERT(false,
"mExpirationArray and mTokenCacheRecords are out of sync!");
mExpirationArray.RemoveElementAt(0);
}
DebugOnly<nsresult> rv =
RemoveLocked(mExpirationArray[0]->mKey, mExpirationArray[0]->mId);
MOZ_ASSERT(NS_SUCCEEDED(rv),
"mExpirationArray and mTokenCacheRecords are out of sync!");
}
}
void SSLTokensCache::LogStats() {
if (!LOG5_ENABLED()) {
return;
}
LOG(("SSLTokensCache::LogStats [count=%zu, cacheSize=%u]",
mExpirationArray.Length(), mCacheSize));
for (const auto& ent : mTokenCacheRecords.Values()) {
const UniquePtr<TokenCacheRecord>& rec = ent->Get();
LOG(("key=%s count=%d", rec->mKey.get(), ent->RecordCount()));
}
}
size_t SSLTokensCache::SizeOfIncludingThis(

Просмотреть файл

@ -45,17 +45,20 @@ class SSLTokensCache : public nsIMemoryReporter {
static nsresult Put(const nsACString& aKey, const uint8_t* aToken,
uint32_t aTokenLen, nsITransportSecurityInfo* aSecInfo,
PRUint32 aExpirationTime);
static nsresult Get(const nsACString& aKey, nsTArray<uint8_t>& aToken);
static bool GetSessionCacheInfo(const nsACString& aKey,
SessionCacheInfo& aResult);
static nsresult Remove(const nsACString& aKey);
static nsresult Get(const nsACString& aKey, nsTArray<uint8_t>& aToken,
SessionCacheInfo& aResult, uint64_t* aTokenId = nullptr);
static nsresult Remove(const nsACString& aKey, uint64_t aId);
static nsresult RemoveAll(const nsACString& aKey);
static void Clear();
private:
SSLTokensCache();
virtual ~SSLTokensCache();
nsresult RemoveLocked(const nsACString& aKey);
nsresult RemoveLocked(const nsACString& aKey, uint64_t aId);
nsresult RemovAllLocked(const nsACString& aKey);
nsresult GetLocked(const nsACString& aKey, nsTArray<uint8_t>& aToken,
SessionCacheInfo& aResult, uint64_t* aTokenId);
void EvictIfNecessary();
void LogStats();
@ -64,11 +67,14 @@ class SSLTokensCache : public nsIMemoryReporter {
static mozilla::StaticRefPtr<SSLTokensCache> gInstance;
static StaticMutex sLock MOZ_UNANNOTATED;
static uint64_t sRecordId;
uint32_t mCacheSize{0}; // Actual cache size in bytes
class TokenCacheRecord {
public:
~TokenCacheRecord();
uint32_t Size() const;
void Reset();
@ -76,9 +82,33 @@ class SSLTokensCache : public nsIMemoryReporter {
PRUint32 mExpirationTime = 0;
nsTArray<uint8_t> mToken;
SessionCacheInfo mSessionCacheInfo;
// An unique id to identify the record. Mostly used when we want to remove a
// record from TokenCacheEntry.
uint64_t mId = 0;
};
nsClassHashtable<nsCStringHashKey, TokenCacheRecord> mTokenCacheRecords;
class TokenCacheEntry {
public:
uint32_t Size() const;
// Add a record into |mRecords|. To make sure |mRecords| is sorted, we
// iterate |mRecords| everytime to find a right place to insert the new
// record.
void AddRecord(UniquePtr<TokenCacheRecord>&& aRecord,
nsTArray<TokenCacheRecord*>& aExpirationArray);
// This function returns the first record in |mRecords|.
const UniquePtr<TokenCacheRecord>& Get();
UniquePtr<TokenCacheRecord> RemoveWithId(uint64_t aId);
uint32_t RecordCount() const { return mRecords.Length(); }
const nsTArray<UniquePtr<TokenCacheRecord>>& Records() { return mRecords; }
private:
// The records in this array are ordered by the expiration time.
nsTArray<UniquePtr<TokenCacheRecord>> mRecords;
};
void OnRecordDestroyed(TokenCacheRecord* aRec);
nsClassHashtable<nsCStringHashKey, TokenCacheEntry> mTokenCacheRecords;
nsTArray<TokenCacheRecord*> mExpirationArray;
};

Просмотреть файл

@ -130,10 +130,12 @@ nsresult Http3Session::Init(const nsHttpConnectionInfo* aConnInfo,
nsAutoCString peerId;
mSocketControl->GetPeerId(peerId);
nsTArray<uint8_t> token;
SessionCacheInfo info;
if (StaticPrefs::network_http_http3_enable_0rtt() &&
NS_SUCCEEDED(SSLTokensCache::Get(peerId, token))) {
NS_SUCCEEDED(SSLTokensCache::Get(peerId, token, info))) {
LOG(("Found a resumption token in the cache."));
mHttp3Connection->SetResumptionToken(token);
mSocketControl->SetSessionCacheInfo(std::move(info));
if (mHttp3Connection->IsZeroRtt()) {
LOG(("Can send ZeroRtt data"));
RefPtr<Http3Session> self(this);

Просмотреть файл

@ -0,0 +1,165 @@
#include "gtest/gtest.h"
#include <numeric>
#include "mozilla/Preferences.h"
#include "nsITransportSecurityInfo.h"
#include "nsSerializationHelper.h"
#include "SSLTokensCache.h"
static already_AddRefed<nsITransportSecurityInfo> createDummySecInfo() {
// clang-format off
nsCString base64Serialization(
"FnhllAKWRHGAlo+ESXykKAAAAAAAAAAAwAAAAAAAAEaphjojH6pBabDSgSnsfLHeAAQAAgAAAAAAAAAAAAAAAAAAAAA"
"B4vFIJp5wRkeyPxAQ9RJGKPqbqVvKO0mKuIl8ec8o/uhmCjImkVxP+7sgiYWmMt8F+O2DZM7ZTG6GukivU8OT5gAAAAIAAAWpMII"
"FpTCCBI2gAwIBAgIQD4svsaKEC+QtqtsU2TF8ITANBgkqhkiG9w0BAQsFADBwMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUN"
"lcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMS8wLQYDVQQDEyZEaWdpQ2VydCBTSEEyIEhpZ2ggQXNzdXJhbmNlIFN"
"lcnZlciBDQTAeFw0xNTAyMjMwMDAwMDBaFw0xNjAzMDIxMjAwMDBaMGoxCzAJBgNVBAYTAlVTMRYwFAYDVQQHEw1TYW4gRnJhbmN"
"pc2NvMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRUwEwYDVQQKEwxGYXN0bHksIEluYy4xFzAVBgNVBAMTDnd3dy5naXRodWIuY29tMII"
"BIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA+9WUCgrgUNwP/JC3cUefLAXeDpq8Ko/U8p8IRvny0Ri0I6Uq0t+RP/nF0LJ"
"Avda8QHYujdgeDTePepBX7+OiwBFhA0YO+rM3C2Z8IRaN/i9eLln+Yyc68+1z+E10s1EXdZrtDGvN6MHqygGsdfkXKfBLUJ1BZEh"
"s9sBnfcjq3kh5gZdBArdG9l5NpdmQhtceaFGsPiWuJxGxRzS4i95veUHWkhMpEYDEEBdcDGxqArvQCvzSlngdttQCfx8OUkBTb3B"
"A2okpTwwJfqPsxVetA6qR7UNc+fVb6KHwvm0bzi2rQ3xw3D/syRHwdMkpoVDQPCk43H9WufgfBKRen87dFwIDAQABo4ICPzCCAjs"
"wHwYDVR0jBBgwFoAUUWj/kK8CB3U8zNllZGKiErhZcjswHQYDVR0OBBYEFGS/RLNGCZvPWh1xSaIEcouINIQjMHsGA1UdEQR0MHK"
"CDnd3dy5naXRodWIuY29tggpnaXRodWIuY29tggwqLmdpdGh1Yi5jb22CCyouZ2l0aHViLmlvgglnaXRodWIuaW+CFyouZ2l0aHV"
"idXNlcmNvbnRlbnQuY29tghVnaXRodWJ1c2VyY29udGVudC5jb20wDgYDVR0PAQH/BAQDAgWgMB0GA1UdJQQWMBQGCCsGAQUFBwM"
"BBggrBgEFBQcDAjB1BgNVHR8EbjBsMDSgMqAwhi5odHRwOi8vY3JsMy5kaWdpY2VydC5jb20vc2hhMi1oYS1zZXJ2ZXItZzMuY3J"
"sMDSgMqAwhi5odHRwOi8vY3JsNC5kaWdpY2VydC5jb20vc2hhMi1oYS1zZXJ2ZXItZzMuY3JsMEIGA1UdIAQ7MDkwNwYJYIZIAYb"
"9bAEBMCowKAYIKwYBBQUHAgEWHGh0dHBzOi8vd3d3LmRpZ2ljZXJ0LmNvbS9DUFMwgYMGCCsGAQUFBwEBBHcwdTAkBggrBgEFBQc"
"wAYYYaHR0cDovL29jc3AuZGlnaWNlcnQuY29tME0GCCsGAQUFBzAChkFodHRwOi8vY2FjZXJ0cy5kaWdpY2VydC5jb20vRGlnaUN"
"lcnRTSEEySGlnaEFzc3VyYW5jZVNlcnZlckNBLmNydDAMBgNVHRMBAf8EAjAAMA0GCSqGSIb3DQEBCwUAA4IBAQAc4dbVmuKvyI7"
"KZ4Txk+ZqcAYToJGKUIVaPL94e5SZGweUisjaCbplAOihnf6Mxt8n6vnuH2IsCaz2NRHqhdcosjT3CwAiJpJNkXPKWVL/txgdSTV"
"2cqB1GG4esFOalvI52dzn+J4fTIYZvNF+AtGyHSLm2XRXYZCw455laUKf6Sk9RDShDgUvzhOKL4GXfTwKXv12MyMknJybH8UCpjC"
"HZmFBVHMcUN/87HsQo20PdOekeEvkjrrMIxW+gxw22Yb67yF/qKgwrWr+43bLN709iyw+LWiU7sQcHL2xk9SYiWQDj2tYz2soObV"
"QYTJm0VUZMEVFhtALq46cx92Zu4vFwC8AAwAAAAABAQAA");
// clang-format on
nsCOMPtr<nsISupports> secInfo;
NS_DeserializeObject(base64Serialization, getter_AddRefs(secInfo));
nsCOMPtr<nsITransportSecurityInfo> securityInfo = do_QueryInterface(secInfo);
return securityInfo.forget();
}
static auto MakeTestData(const size_t aDataSize) {
auto data = nsTArray<uint8_t>();
data.SetLength(aDataSize);
std::iota(data.begin(), data.end(), 0);
return data;
}
static void putToken(const nsACString& aKey, uint32_t aSize) {
nsCOMPtr<nsITransportSecurityInfo> secInfo = createDummySecInfo();
nsTArray<uint8_t> token = MakeTestData(aSize);
nsresult rv = mozilla::net::SSLTokensCache::Put(aKey, token.Elements(), aSize,
secInfo, aSize);
ASSERT_EQ(rv, NS_OK);
}
static void getAndCheckResult(const nsACString& aKey, uint32_t aExpectedSize) {
nsTArray<uint8_t> result;
mozilla::net::SessionCacheInfo unused;
nsresult rv = mozilla::net::SSLTokensCache::Get(aKey, result, unused);
ASSERT_EQ(rv, NS_OK);
ASSERT_EQ(result.Length(), (size_t)aExpectedSize);
}
TEST(TestTokensCache, SinglePut)
{
mozilla::net::SSLTokensCache::Clear();
mozilla::Preferences::SetInt("network.ssl_tokens_cache_records_per_entry", 1);
mozilla::Preferences::SetBool("network.ssl_tokens_cache_use_only_once",
false);
putToken("anon:www.example.com:443"_ns, 100);
nsTArray<uint8_t> result;
mozilla::net::SessionCacheInfo unused;
uint64_t id = 0;
nsresult rv = mozilla::net::SSLTokensCache::Get("anon:www.example.com:443"_ns,
result, unused, &id);
ASSERT_EQ(rv, NS_OK);
ASSERT_EQ(result.Length(), (size_t)100);
ASSERT_EQ(id, (uint64_t)1);
rv = mozilla::net::SSLTokensCache::Get("anon:www.example.com:443"_ns, result,
unused, &id);
ASSERT_EQ(rv, NS_OK);
mozilla::Preferences::SetBool("network.ssl_tokens_cache_use_only_once", true);
// network.ssl_tokens_cache_use_only_once is true, so the record will be
// removed after SSLTokensCache::Get below.
rv = mozilla::net::SSLTokensCache::Get("anon:www.example.com:443"_ns, result,
unused);
ASSERT_EQ(rv, NS_OK);
rv = mozilla::net::SSLTokensCache::Get("anon:www.example.com:443"_ns, result,
unused);
ASSERT_EQ(rv, NS_ERROR_NOT_AVAILABLE);
}
TEST(TestTokensCache, MultiplePut)
{
mozilla::net::SSLTokensCache::Clear();
mozilla::Preferences::SetInt("network.ssl_tokens_cache_records_per_entry", 3);
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);
}
TEST(TestTokensCache, RemoveAll)
{
mozilla::net::SSLTokensCache::Clear();
mozilla::Preferences::SetInt("network.ssl_tokens_cache_records_per_entry", 3);
putToken("anon:www.example1.com:443"_ns, 100);
putToken("anon:www.example1.com:443"_ns, 200);
putToken("anon:www.example1.com:443"_ns, 300);
putToken("anon:www.example2.com:443"_ns, 100);
putToken("anon:www.example2.com:443"_ns, 200);
putToken("anon:www.example2.com:443"_ns, 300);
nsTArray<uint8_t> result;
mozilla::net::SessionCacheInfo unused;
nsresult rv = mozilla::net::SSLTokensCache::Get(
"anon:www.example1.com:443"_ns, result, unused);
ASSERT_EQ(rv, NS_OK);
ASSERT_EQ(result.Length(), (size_t)100);
rv = mozilla::net::SSLTokensCache::RemoveAll("anon:www.example1.com:443"_ns);
ASSERT_EQ(rv, NS_OK);
rv = mozilla::net::SSLTokensCache::Get("anon:www.example1.com:443"_ns, result,
unused);
ASSERT_EQ(rv, NS_ERROR_NOT_AVAILABLE);
rv = mozilla::net::SSLTokensCache::Get("anon:www.example2.com:443"_ns, result,
unused);
ASSERT_EQ(rv, NS_OK);
ASSERT_EQ(result.Length(), (size_t)100);
}
TEST(TestTokensCache, Eviction)
{
mozilla::net::SSLTokensCache::Clear();
mozilla::Preferences::SetInt("network.ssl_tokens_cache_records_per_entry", 3);
mozilla::Preferences::SetInt("network.ssl_tokens_cache_capacity", 8);
putToken("anon:www.example2.com:443"_ns, 300);
putToken("anon:www.example2.com:443"_ns, 400);
putToken("anon:www.example2.com:443"_ns, 500);
// The one has expiration time "300" will be removed because we only allow 3
// records per entry.
putToken("anon:www.example2.com:443"_ns, 600);
putToken("anon:www.example3.com:443"_ns, 600);
putToken("anon:www.example3.com:443"_ns, 500);
// The one has expiration time "400" was evicted, so we get "500".
getAndCheckResult("anon:www.example2.com:443"_ns, 500);
}

Просмотреть файл

@ -24,6 +24,7 @@ UNIFIED_SOURCES += [
"TestReadStreamToString.cpp",
"TestServerTimingHeader.cpp",
"TestSocketTransportService.cpp",
"TestSSLTokensCache.cpp",
"TestStandardURL.cpp",
"TestUDPSocket.cpp",
]

Просмотреть файл

@ -12,7 +12,7 @@
#include "SharedSSLState.h"
#include "sslt.h"
#include "ssl.h"
#include "mozilla/net/SSLTokensCache.h"
#include "mozilla/StaticPrefs_network.h"
#include "nsICertOverrideService.h"
#include "nsITlsHandshakeListener.h"
@ -224,10 +224,7 @@ CommonSocketControl::IsAcceptableForHost(const nsACString& hostname,
}
void CommonSocketControl::RebuildCertificateInfoFromSSLTokenCache() {
nsAutoCString key;
GetPeerId(key);
mozilla::net::SessionCacheInfo info;
if (!mozilla::net::SSLTokensCache::GetSessionCacheInfo(key, info)) {
if (!mSessionCacheInfo) {
MOZ_LOG(
gPIPNSSLog, LogLevel::Debug,
("CommonSocketControl::RebuildCertificateInfoFromSSLTokenCache cannot "
@ -235,6 +232,7 @@ void CommonSocketControl::RebuildCertificateInfoFromSSLTokenCache() {
return;
}
mozilla::net::SessionCacheInfo& info = *mSessionCacheInfo;
nsCOMPtr<nsIX509Cert> cert(
new nsNSSCertificate(std::move(info.mServerCertBytes)));
SetServerCert(cert, info.mEVStatus);

Просмотреть файл

@ -7,6 +7,8 @@
#ifndef CommonSocketControl_h
#define CommonSocketControl_h
#include "mozilla/Maybe.h"
#include "mozilla/net/SSLTokensCache.h"
#include "nsISSLSocketControl.h"
#include "TransportSecurityInfo.h"
@ -20,10 +22,16 @@ class CommonSocketControl : public mozilla::psm::TransportSecurityInfo,
uint32_t GetProviderFlags() const { return mProviderFlags; }
void SetSSLVersionUsed(int16_t version) { mSSLVersionUsed = version; }
void SetSessionCacheInfo(mozilla::net::SessionCacheInfo&& aInfo) {
mSessionCacheInfo.reset();
mSessionCacheInfo.emplace(std::move(aInfo));
}
void RebuildCertificateInfoFromSSLTokenCache();
protected:
~CommonSocketControl() = default;
mozilla::Maybe<mozilla::net::SessionCacheInfo> mSessionCacheInfo;
bool mHandshakeCompleted;
bool mJoined;
bool mSentClientCert;

Просмотреть файл

@ -926,7 +926,9 @@ nsresult nsNSSSocketInfo::SetResumptionTokenFromExternalCache() {
return rv;
}
rv = mozilla::net::SSLTokensCache::Get(peerId, token);
uint64_t tokenId = 0;
mozilla::net::SessionCacheInfo info;
rv = mozilla::net::SSLTokensCache::Get(peerId, token, info, &tokenId);
if (NS_FAILED(rv)) {
if (rv == NS_ERROR_NOT_AVAILABLE) {
// It's ok if we can't find the token.
@ -939,7 +941,7 @@ nsresult nsNSSSocketInfo::SetResumptionTokenFromExternalCache() {
SECStatus srv = SSL_SetResumptionToken(mFd, token.Elements(), token.Length());
if (srv == SECFailure) {
PRErrorCode error = PR_GetError();
mozilla::net::SSLTokensCache::Remove(peerId);
mozilla::net::SSLTokensCache::Remove(peerId, tokenId);
MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
("Setting token failed with NSS error %d [id=%s]", error,
PromiseFlatCString(peerId).get()));
@ -953,6 +955,8 @@ nsresult nsNSSSocketInfo::SetResumptionTokenFromExternalCache() {
return NS_ERROR_FAILURE;
}
SetSessionCacheInfo(std::move(info));
return NS_OK;
}