1 /* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 #include "SSLTokensCache.h"
7 #include "CertVerifier.h"
8 #include "CommonSocketControl.h"
9 #include "TransportSecurityInfo.h"
10 #include "mozilla/ArrayAlgorithm.h"
11 #include "mozilla/Logging.h"
12 #include "mozilla/Preferences.h"
13 #include "nsIOService.h"
20 static LazyLogModule
gSSLTokensCacheLog("SSLTokensCache");
22 #define LOG(args) MOZ_LOG(gSSLTokensCacheLog, mozilla::LogLevel::Debug, args)
24 #define LOG5_ENABLED() \
25 MOZ_LOG_TEST(mozilla::net::gSSLTokensCacheLog, mozilla::LogLevel::Verbose)
27 class ExpirationComparator
{
29 bool Equals(SSLTokensCache::TokenCacheRecord
* a
,
30 SSLTokensCache::TokenCacheRecord
* b
) const {
31 return a
->mExpirationTime
== b
->mExpirationTime
;
33 bool LessThan(SSLTokensCache::TokenCacheRecord
* a
,
34 SSLTokensCache::TokenCacheRecord
* b
) const {
35 return a
->mExpirationTime
< b
->mExpirationTime
;
39 SessionCacheInfo
SessionCacheInfo::Clone() const {
40 SessionCacheInfo result
;
41 result
.mEVStatus
= mEVStatus
;
42 result
.mCertificateTransparencyStatus
= mCertificateTransparencyStatus
;
43 result
.mServerCertBytes
= mServerCertBytes
.Clone();
44 result
.mSucceededCertChainBytes
=
45 mSucceededCertChainBytes
46 ? Some(TransformIntoNewArray(
47 *mSucceededCertChainBytes
,
48 [](const auto& element
) { return element
.Clone(); }))
50 result
.mIsBuiltCertChainRootBuiltInRoot
= mIsBuiltCertChainRootBuiltInRoot
;
51 result
.mOverridableErrorCategory
= mOverridableErrorCategory
;
52 result
.mFailedCertChainBytes
=
54 ? Some(TransformIntoNewArray(
55 *mFailedCertChainBytes
,
56 [](const auto& element
) { return element
.Clone(); }))
61 StaticRefPtr
<SSLTokensCache
> SSLTokensCache::gInstance
;
62 StaticMutex
SSLTokensCache::sLock
;
63 uint64_t SSLTokensCache::sRecordId
= 0;
65 SSLTokensCache::TokenCacheRecord::~TokenCacheRecord() {
70 gInstance
->OnRecordDestroyed(this);
73 uint32_t SSLTokensCache::TokenCacheRecord::Size() const {
74 uint32_t size
= mToken
.Length() + sizeof(mSessionCacheInfo
.mEVStatus
) +
75 sizeof(mSessionCacheInfo
.mCertificateTransparencyStatus
) +
76 mSessionCacheInfo
.mServerCertBytes
.Length() +
77 sizeof(mSessionCacheInfo
.mIsBuiltCertChainRootBuiltInRoot
) +
78 sizeof(mSessionCacheInfo
.mOverridableErrorCategory
);
79 if (mSessionCacheInfo
.mSucceededCertChainBytes
) {
80 for (const auto& cert
: mSessionCacheInfo
.mSucceededCertChainBytes
.ref()) {
81 size
+= cert
.Length();
84 if (mSessionCacheInfo
.mFailedCertChainBytes
) {
85 for (const auto& cert
: mSessionCacheInfo
.mFailedCertChainBytes
.ref()) {
86 size
+= cert
.Length();
92 void SSLTokensCache::TokenCacheRecord::Reset() {
95 mSessionCacheInfo
.mEVStatus
= psm::EVStatus::NotEV
;
96 mSessionCacheInfo
.mCertificateTransparencyStatus
=
97 nsITransportSecurityInfo::CERTIFICATE_TRANSPARENCY_NOT_APPLICABLE
;
98 mSessionCacheInfo
.mServerCertBytes
.Clear();
99 mSessionCacheInfo
.mSucceededCertChainBytes
.reset();
100 mSessionCacheInfo
.mIsBuiltCertChainRootBuiltInRoot
.reset();
101 mSessionCacheInfo
.mOverridableErrorCategory
=
102 nsITransportSecurityInfo::OverridableErrorCategory::ERROR_UNSET
;
103 mSessionCacheInfo
.mFailedCertChainBytes
.reset();
106 uint32_t SSLTokensCache::TokenCacheEntry::Size() const {
108 for (const auto& rec
: mRecords
) {
114 void SSLTokensCache::TokenCacheEntry::AddRecord(
115 UniquePtr
<SSLTokensCache::TokenCacheRecord
>&& aRecord
,
116 nsTArray
<TokenCacheRecord
*>& aExpirationArray
) {
117 if (mRecords
.Length() ==
118 StaticPrefs::network_ssl_tokens_cache_records_per_entry()) {
119 aExpirationArray
.RemoveElement(mRecords
[0].get());
120 mRecords
.RemoveElementAt(0);
123 aExpirationArray
.AppendElement(aRecord
.get());
124 for (int32_t i
= mRecords
.Length() - 1; i
>= 0; --i
) {
125 if (aRecord
->mExpirationTime
> mRecords
[i
]->mExpirationTime
) {
126 mRecords
.InsertElementAt(i
+ 1, std::move(aRecord
));
130 mRecords
.InsertElementAt(0, std::move(aRecord
));
133 UniquePtr
<SSLTokensCache::TokenCacheRecord
>
134 SSLTokensCache::TokenCacheEntry::RemoveWithId(uint64_t aId
) {
135 for (int32_t i
= mRecords
.Length() - 1; i
>= 0; --i
) {
136 if (mRecords
[i
]->mId
== aId
) {
137 UniquePtr
<TokenCacheRecord
> record
= std::move(mRecords
[i
]);
138 mRecords
.RemoveElementAt(i
);
145 const UniquePtr
<SSLTokensCache::TokenCacheRecord
>&
146 SSLTokensCache::TokenCacheEntry::Get() {
150 NS_IMPL_ISUPPORTS(SSLTokensCache
, nsIMemoryReporter
)
153 nsresult
SSLTokensCache::Init() {
154 StaticMutexAutoLock
lock(sLock
);
156 // SSLTokensCache should be only used in parent process and socket process.
157 // Ideally, parent process should not use this when socket process is enabled.
158 // However, some xpcsehll tests may need to create and use sockets directly,
159 // so we still allow to use this in parent process no matter socket process is
161 if (!(XRE_IsSocketProcess() || XRE_IsParentProcess())) {
165 MOZ_ASSERT(!gInstance
);
167 gInstance
= new SSLTokensCache();
169 RegisterWeakMemoryReporter(gInstance
);
175 nsresult
SSLTokensCache::Shutdown() {
176 StaticMutexAutoLock
lock(sLock
);
179 return NS_ERROR_UNEXPECTED
;
182 UnregisterWeakMemoryReporter(gInstance
);
189 SSLTokensCache::SSLTokensCache() { LOG(("SSLTokensCache::SSLTokensCache")); }
191 SSLTokensCache::~SSLTokensCache() { LOG(("SSLTokensCache::~SSLTokensCache")); }
194 nsresult
SSLTokensCache::Put(const nsACString
& aKey
, const uint8_t* aToken
,
196 CommonSocketControl
* aSocketControl
) {
197 PRUint32 expirationTime
;
198 SSLResumptionTokenInfo tokenInfo
;
199 if (SSL_GetResumptionTokenInfo(aToken
, aTokenLen
, &tokenInfo
,
200 sizeof(tokenInfo
)) != SECSuccess
) {
201 LOG((" cannot get expiration time from the token, NSS error %d",
203 return NS_ERROR_FAILURE
;
206 expirationTime
= tokenInfo
.expirationTime
;
207 SSL_DestroyResumptionTokenInfo(&tokenInfo
);
209 return Put(aKey
, aToken
, aTokenLen
, aSocketControl
, expirationTime
);
213 nsresult
SSLTokensCache::Put(const nsACString
& aKey
, const uint8_t* aToken
,
215 CommonSocketControl
* aSocketControl
,
216 PRUint32 aExpirationTime
) {
217 StaticMutexAutoLock
lock(sLock
);
219 LOG(("SSLTokensCache::Put [key=%s, tokenLen=%u]",
220 PromiseFlatCString(aKey
).get(), aTokenLen
));
223 LOG((" service not initialized"));
224 return NS_ERROR_NOT_INITIALIZED
;
227 if (!aSocketControl
) {
228 return NS_ERROR_FAILURE
;
230 nsCOMPtr
<nsITransportSecurityInfo
> securityInfo
;
231 nsresult rv
= aSocketControl
->GetSecurityInfo(getter_AddRefs(securityInfo
));
236 nsCOMPtr
<nsIX509Cert
> cert
;
237 securityInfo
->GetServerCert(getter_AddRefs(cert
));
239 return NS_ERROR_FAILURE
;
242 nsTArray
<uint8_t> certBytes
;
243 rv
= cert
->GetRawDER(certBytes
);
248 Maybe
<nsTArray
<nsTArray
<uint8_t>>> succeededCertChainBytes
;
249 nsTArray
<RefPtr
<nsIX509Cert
>> succeededCertArray
;
250 rv
= securityInfo
->GetSucceededCertChain(succeededCertArray
);
255 Maybe
<bool> isBuiltCertChainRootBuiltInRoot
;
256 if (!succeededCertArray
.IsEmpty()) {
257 succeededCertChainBytes
.emplace();
258 for (const auto& cert
: succeededCertArray
) {
259 nsTArray
<uint8_t> rawCert
;
260 nsresult rv
= cert
->GetRawDER(rawCert
);
264 succeededCertChainBytes
->AppendElement(std::move(rawCert
));
267 bool builtInRoot
= false;
268 rv
= securityInfo
->GetIsBuiltCertChainRootBuiltInRoot(&builtInRoot
);
272 isBuiltCertChainRootBuiltInRoot
.emplace(builtInRoot
);
276 rv
= securityInfo
->GetIsExtendedValidation(&isEV
);
281 uint16_t certificateTransparencyStatus
;
282 rv
= securityInfo
->GetCertificateTransparencyStatus(
283 &certificateTransparencyStatus
);
288 nsITransportSecurityInfo::OverridableErrorCategory overridableErrorCategory
;
289 rv
= securityInfo
->GetOverridableErrorCategory(&overridableErrorCategory
);
294 Maybe
<nsTArray
<nsTArray
<uint8_t>>> failedCertChainBytes
;
295 nsTArray
<RefPtr
<nsIX509Cert
>> failedCertArray
;
296 rv
= securityInfo
->GetFailedCertChain(failedCertArray
);
300 if (!failedCertArray
.IsEmpty()) {
301 failedCertChainBytes
.emplace();
302 for (const auto& cert
: failedCertArray
) {
303 nsTArray
<uint8_t> rawCert
;
304 nsresult rv
= cert
->GetRawDER(rawCert
);
308 failedCertChainBytes
->AppendElement(std::move(rawCert
));
312 auto makeUniqueRecord
= [&]() {
313 auto rec
= MakeUnique
<TokenCacheRecord
>();
315 rec
->mExpirationTime
= aExpirationTime
;
316 MOZ_ASSERT(rec
->mToken
.IsEmpty());
317 rec
->mToken
.AppendElements(aToken
, aTokenLen
);
318 rec
->mId
= ++sRecordId
;
319 rec
->mSessionCacheInfo
.mServerCertBytes
= std::move(certBytes
);
320 rec
->mSessionCacheInfo
.mSucceededCertChainBytes
=
321 std::move(succeededCertChainBytes
);
323 rec
->mSessionCacheInfo
.mEVStatus
= psm::EVStatus::EV
;
325 rec
->mSessionCacheInfo
.mCertificateTransparencyStatus
=
326 certificateTransparencyStatus
;
327 rec
->mSessionCacheInfo
.mIsBuiltCertChainRootBuiltInRoot
=
328 std::move(isBuiltCertChainRootBuiltInRoot
);
329 rec
->mSessionCacheInfo
.mOverridableErrorCategory
= overridableErrorCategory
;
330 rec
->mSessionCacheInfo
.mFailedCertChainBytes
=
331 std::move(failedCertChainBytes
);
335 TokenCacheEntry
* const cacheEntry
=
336 gInstance
->mTokenCacheRecords
.WithEntryHandle(aKey
, [&](auto&& entry
) {
338 auto rec
= makeUniqueRecord();
339 auto cacheEntry
= MakeUnique
<TokenCacheEntry
>();
340 cacheEntry
->AddRecord(std::move(rec
), gInstance
->mExpirationArray
);
341 entry
.Insert(std::move(cacheEntry
));
343 // To make sure the cache size is synced, we take away the size of
344 // whole entry and add it back later.
345 gInstance
->mCacheSize
-= entry
.Data()->Size();
346 entry
.Data()->AddRecord(makeUniqueRecord(),
347 gInstance
->mExpirationArray
);
353 gInstance
->mCacheSize
+= cacheEntry
->Size();
355 gInstance
->LogStats();
357 gInstance
->EvictIfNecessary();
363 nsresult
SSLTokensCache::Get(const nsACString
& aKey
, nsTArray
<uint8_t>& aToken
,
364 SessionCacheInfo
& aResult
, uint64_t* aTokenId
) {
365 StaticMutexAutoLock
lock(sLock
);
367 LOG(("SSLTokensCache::Get [key=%s]", PromiseFlatCString(aKey
).get()));
370 LOG((" service not initialized"));
371 return NS_ERROR_NOT_INITIALIZED
;
374 return gInstance
->GetLocked(aKey
, aToken
, aResult
, aTokenId
);
377 nsresult
SSLTokensCache::GetLocked(const nsACString
& aKey
,
378 nsTArray
<uint8_t>& aToken
,
379 SessionCacheInfo
& aResult
,
380 uint64_t* aTokenId
) {
381 sLock
.AssertCurrentThreadOwns();
383 TokenCacheEntry
* cacheEntry
= nullptr;
385 if (mTokenCacheRecords
.Get(aKey
, &cacheEntry
)) {
386 if (cacheEntry
->RecordCount() == 0) {
387 MOZ_ASSERT(false, "Found a cacheEntry with no records");
388 mTokenCacheRecords
.Remove(aKey
);
389 return NS_ERROR_NOT_AVAILABLE
;
392 const UniquePtr
<TokenCacheRecord
>& rec
= cacheEntry
->Get();
393 aToken
= rec
->mToken
.Clone();
394 aResult
= rec
->mSessionCacheInfo
.Clone();
396 *aTokenId
= rec
->mId
;
398 mCacheSize
-= rec
->Size();
399 cacheEntry
->RemoveWithId(rec
->mId
);
400 if (cacheEntry
->RecordCount() == 0) {
401 mTokenCacheRecords
.Remove(aKey
);
406 LOG((" token not found"));
407 return NS_ERROR_NOT_AVAILABLE
;
411 nsresult
SSLTokensCache::Remove(const nsACString
& aKey
, uint64_t aId
) {
412 StaticMutexAutoLock
lock(sLock
);
414 LOG(("SSLTokensCache::Remove [key=%s]", PromiseFlatCString(aKey
).get()));
417 LOG((" service not initialized"));
418 return NS_ERROR_NOT_INITIALIZED
;
421 return gInstance
->RemoveLocked(aKey
, aId
);
424 nsresult
SSLTokensCache::RemoveLocked(const nsACString
& aKey
, uint64_t aId
) {
425 sLock
.AssertCurrentThreadOwns();
427 LOG(("SSLTokensCache::RemoveLocked [key=%s, id=%" PRIu64
"]",
428 PromiseFlatCString(aKey
).get(), aId
));
430 TokenCacheEntry
* cacheEntry
;
431 if (!mTokenCacheRecords
.Get(aKey
, &cacheEntry
)) {
432 return NS_ERROR_NOT_AVAILABLE
;
435 UniquePtr
<TokenCacheRecord
> rec
= cacheEntry
->RemoveWithId(aId
);
437 return NS_ERROR_NOT_AVAILABLE
;
440 mCacheSize
-= rec
->Size();
441 if (cacheEntry
->RecordCount() == 0) {
442 mTokenCacheRecords
.Remove(aKey
);
445 // Release the record immediately, so mExpirationArray can be also updated.
454 nsresult
SSLTokensCache::RemoveAll(const nsACString
& aKey
) {
455 StaticMutexAutoLock
lock(sLock
);
457 LOG(("SSLTokensCache::RemoveAll [key=%s]", PromiseFlatCString(aKey
).get()));
460 LOG((" service not initialized"));
461 return NS_ERROR_NOT_INITIALIZED
;
464 return gInstance
->RemovAllLocked(aKey
);
467 nsresult
SSLTokensCache::RemovAllLocked(const nsACString
& aKey
) {
468 sLock
.AssertCurrentThreadOwns();
470 LOG(("SSLTokensCache::RemovAllLocked [key=%s]",
471 PromiseFlatCString(aKey
).get()));
473 UniquePtr
<TokenCacheEntry
> cacheEntry
;
474 if (!mTokenCacheRecords
.Remove(aKey
, &cacheEntry
)) {
475 return NS_ERROR_NOT_AVAILABLE
;
478 mCacheSize
-= cacheEntry
->Size();
479 cacheEntry
= nullptr;
486 void SSLTokensCache::OnRecordDestroyed(TokenCacheRecord
* aRec
) {
487 mExpirationArray
.RemoveElement(aRec
);
490 void SSLTokensCache::EvictIfNecessary() {
491 // kilobytes to bytes
492 uint32_t capacity
= StaticPrefs::network_ssl_tokens_cache_capacity() << 10;
493 if (mCacheSize
<= capacity
) {
497 LOG(("SSLTokensCache::EvictIfNecessary - evicting"));
499 mExpirationArray
.Sort(ExpirationComparator());
501 while (mCacheSize
> capacity
&& mExpirationArray
.Length() > 0) {
502 DebugOnly
<nsresult
> rv
=
503 RemoveLocked(mExpirationArray
[0]->mKey
, mExpirationArray
[0]->mId
);
504 MOZ_ASSERT(NS_SUCCEEDED(rv
),
505 "mExpirationArray and mTokenCacheRecords are out of sync!");
509 void SSLTokensCache::LogStats() {
510 if (!LOG5_ENABLED()) {
513 LOG(("SSLTokensCache::LogStats [count=%zu, cacheSize=%u]",
514 mExpirationArray
.Length(), mCacheSize
));
515 for (const auto& ent
: mTokenCacheRecords
.Values()) {
516 const UniquePtr
<TokenCacheRecord
>& rec
= ent
->Get();
517 LOG(("key=%s count=%d", rec
->mKey
.get(), ent
->RecordCount()));
521 size_t SSLTokensCache::SizeOfIncludingThis(
522 mozilla::MallocSizeOf mallocSizeOf
) const {
523 size_t n
= mallocSizeOf(this);
525 n
+= mTokenCacheRecords
.ShallowSizeOfExcludingThis(mallocSizeOf
);
526 n
+= mExpirationArray
.ShallowSizeOfExcludingThis(mallocSizeOf
);
528 for (uint32_t i
= 0; i
< mExpirationArray
.Length(); ++i
) {
529 n
+= mallocSizeOf(mExpirationArray
[i
]);
530 n
+= mExpirationArray
[i
]->mKey
.SizeOfExcludingThisIfUnshared(mallocSizeOf
);
531 n
+= mExpirationArray
[i
]->mToken
.ShallowSizeOfExcludingThis(mallocSizeOf
);
537 MOZ_DEFINE_MALLOC_SIZE_OF(SSLTokensCacheMallocSizeOf
)
540 SSLTokensCache::CollectReports(nsIHandleReportCallback
* aHandleReport
,
541 nsISupports
* aData
, bool aAnonymize
) {
542 StaticMutexAutoLock
lock(sLock
);
544 MOZ_COLLECT_REPORT("explicit/network/ssl-tokens-cache", KIND_HEAP
,
546 SizeOfIncludingThis(SSLTokensCacheMallocSizeOf
),
547 "Memory used for the SSL tokens cache.");
553 void SSLTokensCache::Clear() {
554 LOG(("SSLTokensCache::Clear"));
556 StaticMutexAutoLock
lock(sLock
);
558 LOG((" service not initialized"));
562 gInstance
->mExpirationArray
.Clear();
563 gInstance
->mTokenCacheRecords
.Clear();
564 gInstance
->mCacheSize
= 0;
568 } // namespace mozilla