tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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