Merge mozilla-central to autoland. CLOSED TREE
[gecko.git] / netwerk / base / SSLTokensCache.cpp
blobcf739814f5ca54b81ce63937581c7c80f8401908
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"
14 #include "ssl.h"
15 #include "sslexp.h"
17 namespace mozilla {
18 namespace net {
20 static LazyLogModule gSSLTokensCacheLog("SSLTokensCache");
21 #undef LOG
22 #define LOG(args) MOZ_LOG(gSSLTokensCacheLog, mozilla::LogLevel::Debug, args)
23 #undef LOG5_ENABLED
24 #define LOG5_ENABLED() \
25 MOZ_LOG_TEST(mozilla::net::gSSLTokensCacheLog, mozilla::LogLevel::Verbose)
27 class ExpirationComparator {
28 public:
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(); }))
49 : Nothing();
50 result.mIsBuiltCertChainRootBuiltInRoot = mIsBuiltCertChainRootBuiltInRoot;
51 result.mOverridableErrorCategory = mOverridableErrorCategory;
52 result.mFailedCertChainBytes =
53 mFailedCertChainBytes
54 ? Some(TransformIntoNewArray(
55 *mFailedCertChainBytes,
56 [](const auto& element) { return element.Clone(); }))
57 : Nothing();
58 return result;
61 StaticRefPtr<SSLTokensCache> SSLTokensCache::gInstance;
62 StaticMutex SSLTokensCache::sLock;
63 uint64_t SSLTokensCache::sRecordId = 0;
65 SSLTokensCache::TokenCacheRecord::~TokenCacheRecord() {
66 if (!gInstance) {
67 return;
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();
89 return size;
92 void SSLTokensCache::TokenCacheRecord::Reset() {
93 mToken.Clear();
94 mExpirationTime = 0;
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 {
107 uint32_t size = 0;
108 for (const auto& rec : mRecords) {
109 size += rec->Size();
111 return size;
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));
127 return;
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);
139 return record;
142 return nullptr;
145 const UniquePtr<SSLTokensCache::TokenCacheRecord>&
146 SSLTokensCache::TokenCacheEntry::Get() {
147 return mRecords[0];
150 NS_IMPL_ISUPPORTS(SSLTokensCache, nsIMemoryReporter)
152 // static
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
160 // enabled or not.
161 if (!(XRE_IsSocketProcess() || XRE_IsParentProcess())) {
162 return NS_OK;
165 MOZ_ASSERT(!gInstance);
167 gInstance = new SSLTokensCache();
169 RegisterWeakMemoryReporter(gInstance);
171 return NS_OK;
174 // static
175 nsresult SSLTokensCache::Shutdown() {
176 StaticMutexAutoLock lock(sLock);
178 if (!gInstance) {
179 return NS_ERROR_UNEXPECTED;
182 UnregisterWeakMemoryReporter(gInstance);
184 gInstance = nullptr;
186 return NS_OK;
189 SSLTokensCache::SSLTokensCache() { LOG(("SSLTokensCache::SSLTokensCache")); }
191 SSLTokensCache::~SSLTokensCache() { LOG(("SSLTokensCache::~SSLTokensCache")); }
193 // static
194 nsresult SSLTokensCache::Put(const nsACString& aKey, const uint8_t* aToken,
195 uint32_t aTokenLen,
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",
202 PORT_GetError()));
203 return NS_ERROR_FAILURE;
206 expirationTime = tokenInfo.expirationTime;
207 SSL_DestroyResumptionTokenInfo(&tokenInfo);
209 return Put(aKey, aToken, aTokenLen, aSocketControl, expirationTime);
212 // static
213 nsresult SSLTokensCache::Put(const nsACString& aKey, const uint8_t* aToken,
214 uint32_t aTokenLen,
215 CommonSocketControl* aSocketControl,
216 PRUint32 aExpirationTime) {
217 StaticMutexAutoLock lock(sLock);
219 LOG(("SSLTokensCache::Put [key=%s, tokenLen=%u]",
220 PromiseFlatCString(aKey).get(), aTokenLen));
222 if (!gInstance) {
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));
232 if (NS_FAILED(rv)) {
233 return rv;
236 nsCOMPtr<nsIX509Cert> cert;
237 securityInfo->GetServerCert(getter_AddRefs(cert));
238 if (!cert) {
239 return NS_ERROR_FAILURE;
242 nsTArray<uint8_t> certBytes;
243 rv = cert->GetRawDER(certBytes);
244 if (NS_FAILED(rv)) {
245 return rv;
248 Maybe<nsTArray<nsTArray<uint8_t>>> succeededCertChainBytes;
249 nsTArray<RefPtr<nsIX509Cert>> succeededCertArray;
250 rv = securityInfo->GetSucceededCertChain(succeededCertArray);
251 if (NS_FAILED(rv)) {
252 return rv;
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);
261 if (NS_FAILED(rv)) {
262 return rv;
264 succeededCertChainBytes->AppendElement(std::move(rawCert));
267 bool builtInRoot = false;
268 rv = securityInfo->GetIsBuiltCertChainRootBuiltInRoot(&builtInRoot);
269 if (NS_FAILED(rv)) {
270 return rv;
272 isBuiltCertChainRootBuiltInRoot.emplace(builtInRoot);
275 bool isEV;
276 rv = securityInfo->GetIsExtendedValidation(&isEV);
277 if (NS_FAILED(rv)) {
278 return rv;
281 uint16_t certificateTransparencyStatus;
282 rv = securityInfo->GetCertificateTransparencyStatus(
283 &certificateTransparencyStatus);
284 if (NS_FAILED(rv)) {
285 return rv;
288 nsITransportSecurityInfo::OverridableErrorCategory overridableErrorCategory;
289 rv = securityInfo->GetOverridableErrorCategory(&overridableErrorCategory);
290 if (NS_FAILED(rv)) {
291 return rv;
294 Maybe<nsTArray<nsTArray<uint8_t>>> failedCertChainBytes;
295 nsTArray<RefPtr<nsIX509Cert>> failedCertArray;
296 rv = securityInfo->GetFailedCertChain(failedCertArray);
297 if (NS_FAILED(rv)) {
298 return rv;
300 if (!failedCertArray.IsEmpty()) {
301 failedCertChainBytes.emplace();
302 for (const auto& cert : failedCertArray) {
303 nsTArray<uint8_t> rawCert;
304 nsresult rv = cert->GetRawDER(rawCert);
305 if (NS_FAILED(rv)) {
306 return rv;
308 failedCertChainBytes->AppendElement(std::move(rawCert));
312 auto makeUniqueRecord = [&]() {
313 auto rec = MakeUnique<TokenCacheRecord>();
314 rec->mKey = aKey;
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);
322 if (isEV) {
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);
332 return rec;
335 TokenCacheEntry* const cacheEntry =
336 gInstance->mTokenCacheRecords.WithEntryHandle(aKey, [&](auto&& entry) {
337 if (!entry) {
338 auto rec = makeUniqueRecord();
339 auto cacheEntry = MakeUnique<TokenCacheEntry>();
340 cacheEntry->AddRecord(std::move(rec), gInstance->mExpirationArray);
341 entry.Insert(std::move(cacheEntry));
342 } else {
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);
350 return entry->get();
353 gInstance->mCacheSize += cacheEntry->Size();
355 gInstance->LogStats();
357 gInstance->EvictIfNecessary();
359 return NS_OK;
362 // static
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()));
369 if (!gInstance) {
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();
395 if (aTokenId) {
396 *aTokenId = rec->mId;
398 mCacheSize -= rec->Size();
399 cacheEntry->RemoveWithId(rec->mId);
400 if (cacheEntry->RecordCount() == 0) {
401 mTokenCacheRecords.Remove(aKey);
403 return NS_OK;
406 LOG((" token not found"));
407 return NS_ERROR_NOT_AVAILABLE;
410 // static
411 nsresult SSLTokensCache::Remove(const nsACString& aKey, uint64_t aId) {
412 StaticMutexAutoLock lock(sLock);
414 LOG(("SSLTokensCache::Remove [key=%s]", PromiseFlatCString(aKey).get()));
416 if (!gInstance) {
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);
436 if (!rec) {
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.
446 rec = nullptr;
448 LogStats();
450 return NS_OK;
453 // static
454 nsresult SSLTokensCache::RemoveAll(const nsACString& aKey) {
455 StaticMutexAutoLock lock(sLock);
457 LOG(("SSLTokensCache::RemoveAll [key=%s]", PromiseFlatCString(aKey).get()));
459 if (!gInstance) {
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;
481 LogStats();
483 return NS_OK;
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) {
494 return;
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()) {
511 return;
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);
534 return n;
537 MOZ_DEFINE_MALLOC_SIZE_OF(SSLTokensCacheMallocSizeOf)
539 NS_IMETHODIMP
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,
545 UNITS_BYTES,
546 SizeOfIncludingThis(SSLTokensCacheMallocSizeOf),
547 "Memory used for the SSL tokens cache.");
549 return NS_OK;
552 // static
553 void SSLTokensCache::Clear() {
554 LOG(("SSLTokensCache::Clear"));
556 StaticMutexAutoLock lock(sLock);
557 if (!gInstance) {
558 LOG((" service not initialized"));
559 return;
562 gInstance->mExpirationArray.Clear();
563 gInstance->mTokenCacheRecords.Clear();
564 gInstance->mCacheSize = 0;
567 } // namespace net
568 } // namespace mozilla