SSLTokensCache.cpp (17171B)
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/. */ 4 5 #include "SSLTokensCache.h" 6 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" 16 17 namespace mozilla { 18 namespace net { 19 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) 26 27 class ExpirationComparator { 28 public: 29 bool Equals(SSLTokensCache::TokenCacheRecord* a, 30 SSLTokensCache::TokenCacheRecord* b) const { 31 return a->mExpirationTime == b->mExpirationTime; 32 } 33 bool LessThan(SSLTokensCache::TokenCacheRecord* a, 34 SSLTokensCache::TokenCacheRecord* b) const { 35 return a->mExpirationTime < b->mExpirationTime; 36 } 37 }; 38 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.mHandshakeCertificatesBytes = 53 mHandshakeCertificatesBytes 54 ? Some(TransformIntoNewArray( 55 *mHandshakeCertificatesBytes, 56 [](const auto& element) { return element.Clone(); })) 57 : Nothing(); 58 return result; 59 } 60 61 StaticRefPtr<SSLTokensCache> SSLTokensCache::gInstance; 62 StaticMutex SSLTokensCache::sLock; 63 uint64_t SSLTokensCache::sRecordId = 0; 64 65 SSLTokensCache::TokenCacheRecord::~TokenCacheRecord() { 66 if (!gInstance) { 67 return; 68 } 69 70 gInstance->OnRecordDestroyed(this); 71 } 72 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(); 82 } 83 } 84 if (mSessionCacheInfo.mHandshakeCertificatesBytes) { 85 for (const auto& cert : 86 mSessionCacheInfo.mHandshakeCertificatesBytes.ref()) { 87 size += cert.Length(); 88 } 89 } 90 return size; 91 } 92 93 void SSLTokensCache::TokenCacheRecord::Reset() { 94 mToken.Clear(); 95 mExpirationTime = 0; 96 mSessionCacheInfo.mEVStatus = psm::EVStatus::NotEV; 97 mSessionCacheInfo.mCertificateTransparencyStatus = 98 nsITransportSecurityInfo::CERTIFICATE_TRANSPARENCY_NOT_APPLICABLE; 99 mSessionCacheInfo.mServerCertBytes.Clear(); 100 mSessionCacheInfo.mSucceededCertChainBytes.reset(); 101 mSessionCacheInfo.mIsBuiltCertChainRootBuiltInRoot.reset(); 102 mSessionCacheInfo.mOverridableErrorCategory = 103 nsITransportSecurityInfo::OverridableErrorCategory::ERROR_UNSET; 104 mSessionCacheInfo.mHandshakeCertificatesBytes.reset(); 105 } 106 107 uint32_t SSLTokensCache::TokenCacheEntry::Size() const { 108 uint32_t size = 0; 109 for (const auto& rec : mRecords) { 110 size += rec->Size(); 111 } 112 return size; 113 } 114 115 void SSLTokensCache::TokenCacheEntry::AddRecord( 116 UniquePtr<SSLTokensCache::TokenCacheRecord>&& aRecord, 117 nsTArray<TokenCacheRecord*>& aExpirationArray) { 118 if (mRecords.Length() == 119 StaticPrefs::network_ssl_tokens_cache_records_per_entry()) { 120 aExpirationArray.RemoveElement(mRecords[0].get()); 121 mRecords.RemoveElementAt(0); 122 } 123 124 aExpirationArray.AppendElement(aRecord.get()); 125 for (int32_t i = mRecords.Length() - 1; i >= 0; --i) { 126 if (aRecord->mExpirationTime > mRecords[i]->mExpirationTime) { 127 mRecords.InsertElementAt(i + 1, std::move(aRecord)); 128 return; 129 } 130 } 131 mRecords.InsertElementAt(0, std::move(aRecord)); 132 } 133 134 UniquePtr<SSLTokensCache::TokenCacheRecord> 135 SSLTokensCache::TokenCacheEntry::RemoveWithId(uint64_t aId) { 136 for (int32_t i = mRecords.Length() - 1; i >= 0; --i) { 137 if (mRecords[i]->mId == aId) { 138 UniquePtr<TokenCacheRecord> record = std::move(mRecords[i]); 139 mRecords.RemoveElementAt(i); 140 return record; 141 } 142 } 143 return nullptr; 144 } 145 146 const UniquePtr<SSLTokensCache::TokenCacheRecord>& 147 SSLTokensCache::TokenCacheEntry::Get() { 148 return mRecords[0]; 149 } 150 151 NS_IMPL_ISUPPORTS(SSLTokensCache, nsIMemoryReporter) 152 153 // static 154 nsresult SSLTokensCache::Init() { 155 StaticMutexAutoLock lock(sLock); 156 157 // SSLTokensCache should be only used in parent process and socket process. 158 // Ideally, parent process should not use this when socket process is enabled. 159 // However, some xpcsehll tests may need to create and use sockets directly, 160 // so we still allow to use this in parent process no matter socket process is 161 // enabled or not. 162 if (!(XRE_IsSocketProcess() || XRE_IsParentProcess())) { 163 return NS_OK; 164 } 165 166 MOZ_ASSERT(!gInstance); 167 168 gInstance = new SSLTokensCache(); 169 170 RegisterWeakMemoryReporter(gInstance); 171 172 return NS_OK; 173 } 174 175 // static 176 nsresult SSLTokensCache::Shutdown() { 177 StaticMutexAutoLock lock(sLock); 178 179 if (!gInstance) { 180 return NS_ERROR_UNEXPECTED; 181 } 182 183 UnregisterWeakMemoryReporter(gInstance); 184 185 gInstance = nullptr; 186 187 return NS_OK; 188 } 189 190 SSLTokensCache::SSLTokensCache() { LOG(("SSLTokensCache::SSLTokensCache")); } 191 192 SSLTokensCache::~SSLTokensCache() { LOG(("SSLTokensCache::~SSLTokensCache")); } 193 194 // static 195 nsresult SSLTokensCache::Put(const nsACString& aKey, const uint8_t* aToken, 196 uint32_t aTokenLen, 197 CommonSocketControl* aSocketControl) { 198 PRUint32 expirationTime; 199 SSLResumptionTokenInfo tokenInfo; 200 if (SSL_GetResumptionTokenInfo(aToken, aTokenLen, &tokenInfo, 201 sizeof(tokenInfo)) != SECSuccess) { 202 LOG((" cannot get expiration time from the token, NSS error %d", 203 PORT_GetError())); 204 return NS_ERROR_FAILURE; 205 } 206 207 expirationTime = tokenInfo.expirationTime; 208 SSL_DestroyResumptionTokenInfo(&tokenInfo); 209 210 return Put(aKey, aToken, aTokenLen, aSocketControl, expirationTime); 211 } 212 213 // static 214 nsresult SSLTokensCache::Put(const nsACString& aKey, const uint8_t* aToken, 215 uint32_t aTokenLen, 216 CommonSocketControl* aSocketControl, 217 PRUint32 aExpirationTime) { 218 StaticMutexAutoLock lock(sLock); 219 220 LOG(("SSLTokensCache::Put [key=%s, tokenLen=%u]", 221 PromiseFlatCString(aKey).get(), aTokenLen)); 222 223 if (!gInstance) { 224 LOG((" service not initialized")); 225 return NS_ERROR_NOT_INITIALIZED; 226 } 227 228 if (!aSocketControl) { 229 return NS_ERROR_FAILURE; 230 } 231 nsCOMPtr<nsITransportSecurityInfo> securityInfo; 232 nsresult rv = aSocketControl->GetSecurityInfo(getter_AddRefs(securityInfo)); 233 if (NS_FAILED(rv)) { 234 return rv; 235 } 236 237 nsCOMPtr<nsIX509Cert> cert; 238 securityInfo->GetServerCert(getter_AddRefs(cert)); 239 if (!cert) { 240 return NS_ERROR_FAILURE; 241 } 242 243 nsTArray<uint8_t> certBytes; 244 rv = cert->GetRawDER(certBytes); 245 if (NS_FAILED(rv)) { 246 return rv; 247 } 248 249 Maybe<nsTArray<nsTArray<uint8_t>>> succeededCertChainBytes; 250 nsTArray<RefPtr<nsIX509Cert>> succeededCertArray; 251 rv = securityInfo->GetSucceededCertChain(succeededCertArray); 252 if (NS_FAILED(rv)) { 253 return rv; 254 } 255 256 Maybe<bool> isBuiltCertChainRootBuiltInRoot; 257 if (!succeededCertArray.IsEmpty()) { 258 succeededCertChainBytes.emplace(); 259 for (const auto& cert : succeededCertArray) { 260 nsTArray<uint8_t> rawCert; 261 nsresult rv = cert->GetRawDER(rawCert); 262 if (NS_FAILED(rv)) { 263 return rv; 264 } 265 succeededCertChainBytes->AppendElement(std::move(rawCert)); 266 } 267 268 bool builtInRoot = false; 269 rv = securityInfo->GetIsBuiltCertChainRootBuiltInRoot(&builtInRoot); 270 if (NS_FAILED(rv)) { 271 return rv; 272 } 273 isBuiltCertChainRootBuiltInRoot.emplace(builtInRoot); 274 } 275 276 bool isEV; 277 rv = securityInfo->GetIsExtendedValidation(&isEV); 278 if (NS_FAILED(rv)) { 279 return rv; 280 } 281 282 uint16_t certificateTransparencyStatus; 283 rv = securityInfo->GetCertificateTransparencyStatus( 284 &certificateTransparencyStatus); 285 if (NS_FAILED(rv)) { 286 return rv; 287 } 288 289 nsITransportSecurityInfo::OverridableErrorCategory overridableErrorCategory; 290 rv = securityInfo->GetOverridableErrorCategory(&overridableErrorCategory); 291 if (NS_FAILED(rv)) { 292 return rv; 293 } 294 295 Maybe<nsTArray<nsTArray<uint8_t>>> handshakeCertificatesBytes; 296 nsTArray<RefPtr<nsIX509Cert>> handshakeCertificates; 297 rv = securityInfo->GetHandshakeCertificates(handshakeCertificates); 298 if (NS_FAILED(rv)) { 299 return rv; 300 } 301 if (!handshakeCertificates.IsEmpty()) { 302 handshakeCertificatesBytes.emplace(); 303 for (const auto& cert : handshakeCertificates) { 304 nsTArray<uint8_t> rawCert; 305 nsresult rv = cert->GetRawDER(rawCert); 306 if (NS_FAILED(rv)) { 307 return rv; 308 } 309 handshakeCertificatesBytes->AppendElement(std::move(rawCert)); 310 } 311 } 312 313 auto makeUniqueRecord = [&]() { 314 auto rec = MakeUnique<TokenCacheRecord>(); 315 rec->mKey = aKey; 316 rec->mExpirationTime = aExpirationTime; 317 MOZ_ASSERT(rec->mToken.IsEmpty()); 318 rec->mToken.AppendElements(aToken, aTokenLen); 319 rec->mId = ++sRecordId; 320 rec->mSessionCacheInfo.mServerCertBytes = std::move(certBytes); 321 rec->mSessionCacheInfo.mSucceededCertChainBytes = 322 std::move(succeededCertChainBytes); 323 if (isEV) { 324 rec->mSessionCacheInfo.mEVStatus = psm::EVStatus::EV; 325 } 326 rec->mSessionCacheInfo.mCertificateTransparencyStatus = 327 certificateTransparencyStatus; 328 rec->mSessionCacheInfo.mIsBuiltCertChainRootBuiltInRoot = 329 std::move(isBuiltCertChainRootBuiltInRoot); 330 rec->mSessionCacheInfo.mOverridableErrorCategory = overridableErrorCategory; 331 rec->mSessionCacheInfo.mHandshakeCertificatesBytes = 332 std::move(handshakeCertificatesBytes); 333 return rec; 334 }; 335 336 TokenCacheEntry* const cacheEntry = 337 gInstance->mTokenCacheRecords.WithEntryHandle(aKey, [&](auto&& entry) { 338 if (!entry) { 339 auto rec = makeUniqueRecord(); 340 auto cacheEntry = MakeUnique<TokenCacheEntry>(); 341 cacheEntry->AddRecord(std::move(rec), gInstance->mExpirationArray); 342 entry.Insert(std::move(cacheEntry)); 343 } else { 344 // To make sure the cache size is synced, we take away the size of 345 // whole entry and add it back later. 346 gInstance->mCacheSize -= entry.Data()->Size(); 347 entry.Data()->AddRecord(makeUniqueRecord(), 348 gInstance->mExpirationArray); 349 } 350 351 return entry->get(); 352 }); 353 354 gInstance->mCacheSize += cacheEntry->Size(); 355 356 gInstance->LogStats(); 357 358 gInstance->EvictIfNecessary(); 359 360 return NS_OK; 361 } 362 363 // static 364 nsresult SSLTokensCache::Get(const nsACString& aKey, nsTArray<uint8_t>& aToken, 365 SessionCacheInfo& aResult, uint64_t* aTokenId) { 366 StaticMutexAutoLock lock(sLock); 367 368 LOG(("SSLTokensCache::Get [key=%s]", PromiseFlatCString(aKey).get())); 369 370 if (!gInstance) { 371 LOG((" service not initialized")); 372 return NS_ERROR_NOT_INITIALIZED; 373 } 374 375 return gInstance->GetLocked(aKey, aToken, aResult, aTokenId); 376 } 377 378 nsresult SSLTokensCache::GetLocked(const nsACString& aKey, 379 nsTArray<uint8_t>& aToken, 380 SessionCacheInfo& aResult, 381 uint64_t* aTokenId) { 382 sLock.AssertCurrentThreadOwns(); 383 384 TokenCacheEntry* cacheEntry = nullptr; 385 386 if (mTokenCacheRecords.Get(aKey, &cacheEntry)) { 387 if (cacheEntry->RecordCount() == 0) { 388 MOZ_ASSERT(false, "Found a cacheEntry with no records"); 389 mTokenCacheRecords.Remove(aKey); 390 return NS_ERROR_NOT_AVAILABLE; 391 } 392 393 const UniquePtr<TokenCacheRecord>& rec = cacheEntry->Get(); 394 aToken = rec->mToken.Clone(); 395 aResult = rec->mSessionCacheInfo.Clone(); 396 if (aTokenId) { 397 *aTokenId = rec->mId; 398 } 399 mCacheSize -= rec->Size(); 400 cacheEntry->RemoveWithId(rec->mId); 401 if (cacheEntry->RecordCount() == 0) { 402 mTokenCacheRecords.Remove(aKey); 403 } 404 return NS_OK; 405 } 406 407 LOG((" token not found")); 408 return NS_ERROR_NOT_AVAILABLE; 409 } 410 411 // static 412 nsresult SSLTokensCache::Remove(const nsACString& aKey, uint64_t aId) { 413 StaticMutexAutoLock lock(sLock); 414 415 LOG(("SSLTokensCache::Remove [key=%s]", PromiseFlatCString(aKey).get())); 416 417 if (!gInstance) { 418 LOG((" service not initialized")); 419 return NS_ERROR_NOT_INITIALIZED; 420 } 421 422 return gInstance->RemoveLocked(aKey, aId); 423 } 424 425 nsresult SSLTokensCache::RemoveLocked(const nsACString& aKey, uint64_t aId) { 426 sLock.AssertCurrentThreadOwns(); 427 428 LOG(("SSLTokensCache::RemoveLocked [key=%s, id=%" PRIu64 "]", 429 PromiseFlatCString(aKey).get(), aId)); 430 431 TokenCacheEntry* cacheEntry; 432 if (!mTokenCacheRecords.Get(aKey, &cacheEntry)) { 433 return NS_ERROR_NOT_AVAILABLE; 434 } 435 436 UniquePtr<TokenCacheRecord> rec = cacheEntry->RemoveWithId(aId); 437 if (!rec) { 438 return NS_ERROR_NOT_AVAILABLE; 439 } 440 441 mCacheSize -= rec->Size(); 442 if (cacheEntry->RecordCount() == 0) { 443 mTokenCacheRecords.Remove(aKey); 444 } 445 446 // Release the record immediately, so mExpirationArray can be also updated. 447 rec = nullptr; 448 449 LogStats(); 450 451 return NS_OK; 452 } 453 454 // static 455 nsresult SSLTokensCache::RemoveAll(const nsACString& aKey) { 456 StaticMutexAutoLock lock(sLock); 457 458 LOG(("SSLTokensCache::RemoveAll [key=%s]", PromiseFlatCString(aKey).get())); 459 460 if (!gInstance) { 461 LOG((" service not initialized")); 462 return NS_ERROR_NOT_INITIALIZED; 463 } 464 465 return gInstance->RemovAllLocked(aKey); 466 } 467 468 nsresult SSLTokensCache::RemovAllLocked(const nsACString& aKey) { 469 sLock.AssertCurrentThreadOwns(); 470 471 LOG(("SSLTokensCache::RemovAllLocked [key=%s]", 472 PromiseFlatCString(aKey).get())); 473 474 UniquePtr<TokenCacheEntry> cacheEntry; 475 if (!mTokenCacheRecords.Remove(aKey, &cacheEntry)) { 476 return NS_ERROR_NOT_AVAILABLE; 477 } 478 479 mCacheSize -= cacheEntry->Size(); 480 cacheEntry = nullptr; 481 482 LogStats(); 483 484 return NS_OK; 485 } 486 487 void SSLTokensCache::OnRecordDestroyed(TokenCacheRecord* aRec) { 488 mExpirationArray.RemoveElement(aRec); 489 } 490 491 void SSLTokensCache::EvictIfNecessary() { 492 // kilobytes to bytes 493 uint32_t capacity = StaticPrefs::network_ssl_tokens_cache_capacity() << 10; 494 if (mCacheSize <= capacity) { 495 return; 496 } 497 498 LOG(("SSLTokensCache::EvictIfNecessary - evicting")); 499 500 mExpirationArray.Sort(ExpirationComparator()); 501 502 while (mCacheSize > capacity && mExpirationArray.Length() > 0) { 503 DebugOnly<nsresult> rv = 504 RemoveLocked(mExpirationArray[0]->mKey, mExpirationArray[0]->mId); 505 MOZ_ASSERT(NS_SUCCEEDED(rv), 506 "mExpirationArray and mTokenCacheRecords are out of sync!"); 507 } 508 } 509 510 void SSLTokensCache::LogStats() { 511 if (!LOG5_ENABLED()) { 512 return; 513 } 514 LOG(("SSLTokensCache::LogStats [count=%zu, cacheSize=%u]", 515 mExpirationArray.Length(), mCacheSize)); 516 for (const auto& ent : mTokenCacheRecords.Values()) { 517 const UniquePtr<TokenCacheRecord>& rec = ent->Get(); 518 LOG(("key=%s count=%d", rec->mKey.get(), ent->RecordCount())); 519 } 520 } 521 522 size_t SSLTokensCache::SizeOfIncludingThis( 523 mozilla::MallocSizeOf mallocSizeOf) const { 524 size_t n = mallocSizeOf(this); 525 526 n += mTokenCacheRecords.ShallowSizeOfExcludingThis(mallocSizeOf); 527 n += mExpirationArray.ShallowSizeOfExcludingThis(mallocSizeOf); 528 529 for (uint32_t i = 0; i < mExpirationArray.Length(); ++i) { 530 n += mallocSizeOf(mExpirationArray[i]); 531 n += mExpirationArray[i]->mKey.SizeOfExcludingThisIfUnshared(mallocSizeOf); 532 n += mExpirationArray[i]->mToken.ShallowSizeOfExcludingThis(mallocSizeOf); 533 } 534 535 return n; 536 } 537 538 MOZ_DEFINE_MALLOC_SIZE_OF(SSLTokensCacheMallocSizeOf) 539 540 NS_IMETHODIMP 541 SSLTokensCache::CollectReports(nsIHandleReportCallback* aHandleReport, 542 nsISupports* aData, bool aAnonymize) { 543 StaticMutexAutoLock lock(sLock); 544 545 MOZ_COLLECT_REPORT("explicit/network/ssl-tokens-cache", KIND_HEAP, 546 UNITS_BYTES, 547 SizeOfIncludingThis(SSLTokensCacheMallocSizeOf), 548 "Memory used for the SSL tokens cache."); 549 550 return NS_OK; 551 } 552 553 // static 554 void SSLTokensCache::Clear() { 555 LOG(("SSLTokensCache::Clear")); 556 557 StaticMutexAutoLock lock(sLock); 558 if (!gInstance) { 559 LOG((" service not initialized")); 560 return; 561 } 562 563 gInstance->mExpirationArray.Clear(); 564 gInstance->mTokenCacheRecords.Clear(); 565 gInstance->mCacheSize = 0; 566 } 567 568 } // namespace net 569 } // namespace mozilla