tor-browser

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

nsClientAuthRemember.cpp (14008B)


      1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
      2 *
      3 * This Source Code Form is subject to the terms of the Mozilla Public
      4 * License, v. 2.0. If a copy of the MPL was not distributed with this
      5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      6 
      7 #include "nsClientAuthRemember.h"
      8 
      9 #include "mozilla/BasePrincipal.h"
     10 #include "mozilla/RefPtr.h"
     11 #include "nsCRT.h"
     12 #include "nsINSSComponent.h"
     13 #include "nsPrintfCString.h"
     14 #include "nsNSSComponent.h"
     15 #include "nsIDataStorage.h"
     16 #include "nsIObserverService.h"
     17 #include "nsNetUtil.h"
     18 #include "nsPromiseFlatString.h"
     19 #include "nsThreadUtils.h"
     20 #include "cert.h"
     21 #include "nspr.h"
     22 #include "pk11pub.h"
     23 #include "certdb.h"
     24 #include "sechash.h"
     25 
     26 #include "nsJSUtils.h"
     27 
     28 #ifdef XP_MACOSX
     29 #  include <CoreFoundation/CoreFoundation.h>
     30 #  include <Security/Security.h>
     31 #  include "KeychainSecret.h"  // for ScopedCFType
     32 #endif                         // XP_MACOSX
     33 
     34 using namespace mozilla;
     35 using namespace mozilla::psm;
     36 
     37 NS_IMPL_ISUPPORTS(nsClientAuthRememberService, nsIClientAuthRememberService)
     38 NS_IMPL_ISUPPORTS(nsClientAuthRemember, nsIClientAuthRememberRecord)
     39 
     40 NS_IMETHODIMP
     41 nsClientAuthRemember::GetAsciiHost(/*out*/ nsACString& aAsciiHost) {
     42  aAsciiHost = mAsciiHost;
     43  return NS_OK;
     44 }
     45 
     46 NS_IMETHODIMP
     47 nsClientAuthRemember::GetDbKey(/*out*/ nsACString& aDBKey) {
     48  aDBKey = mDBKey;
     49  return NS_OK;
     50 }
     51 
     52 NS_IMETHODIMP
     53 nsClientAuthRemember::GetEntryKey(/*out*/ nsACString& aEntryKey) {
     54  aEntryKey.Assign(mAsciiHost);
     55  aEntryKey.Append(',');
     56  // This used to include the SHA-256 hash of the server certificate.
     57  aEntryKey.Append(',');
     58  aEntryKey.Append(mOriginAttributesSuffix);
     59  return NS_OK;
     60 }
     61 
     62 nsresult nsClientAuthRememberService::Init() {
     63  if (!NS_IsMainThread()) {
     64    NS_ERROR("nsClientAuthRememberService::Init called off the main thread");
     65    return NS_ERROR_NOT_SAME_THREAD;
     66  }
     67 
     68  nsCOMPtr<nsIDataStorageManager> dataStorageManager(
     69      do_GetService("@mozilla.org/security/datastoragemanager;1"));
     70  if (!dataStorageManager) {
     71    return NS_ERROR_FAILURE;
     72  }
     73  nsresult rv =
     74      dataStorageManager->Get(nsIDataStorageManager::ClientAuthRememberList,
     75                              getter_AddRefs(mClientAuthRememberList));
     76  if (NS_FAILED(rv)) {
     77    return rv;
     78  }
     79  if (!mClientAuthRememberList) {
     80    return NS_ERROR_FAILURE;
     81  }
     82 
     83  return NS_OK;
     84 }
     85 
     86 NS_IMETHODIMP
     87 nsClientAuthRememberService::ForgetRememberedDecision(const nsACString& key) {
     88  nsresult rv = mClientAuthRememberList->Remove(
     89      PromiseFlatCString(key), nsIDataStorage::DataType::Persistent);
     90  if (NS_FAILED(rv)) {
     91    return rv;
     92  }
     93  rv = mClientAuthRememberList->Remove(PromiseFlatCString(key),
     94                                       nsIDataStorage::DataType::Temporary);
     95  if (NS_FAILED(rv)) {
     96    return rv;
     97  }
     98  nsCOMPtr<nsINSSComponent> nssComponent(do_GetService(NS_NSSCOMPONENT_CID));
     99  if (!nssComponent) {
    100    return NS_ERROR_NOT_AVAILABLE;
    101  }
    102  return nssComponent->ClearSSLExternalAndInternalSessionCache();
    103 }
    104 
    105 NS_IMETHODIMP
    106 nsClientAuthRememberService::GetDecisions(
    107    nsTArray<RefPtr<nsIClientAuthRememberRecord>>& results) {
    108  nsTArray<RefPtr<nsIDataStorageItem>> decisions;
    109  nsresult rv = mClientAuthRememberList->GetAll(decisions);
    110  if (NS_FAILED(rv)) {
    111    return rv;
    112  }
    113 
    114  for (const auto& decision : decisions) {
    115    nsIDataStorage::DataType type;
    116    rv = decision->GetType(&type);
    117    if (NS_FAILED(rv)) {
    118      return rv;
    119    }
    120    if (type == nsIDataStorage::DataType::Persistent ||
    121        type == nsIDataStorage::DataType::Temporary) {
    122      nsAutoCString key;
    123      rv = decision->GetKey(key);
    124      if (NS_FAILED(rv)) {
    125        return rv;
    126      }
    127      nsAutoCString value;
    128      rv = decision->GetValue(value);
    129      if (NS_FAILED(rv)) {
    130        return rv;
    131      }
    132      RefPtr<nsIClientAuthRememberRecord> tmp =
    133          new nsClientAuthRemember(key, value);
    134 
    135      results.AppendElement(tmp);
    136    }
    137  }
    138 
    139  return NS_OK;
    140 }
    141 
    142 NS_IMETHODIMP
    143 nsClientAuthRememberService::ClearRememberedDecisions() {
    144  nsresult rv = mClientAuthRememberList->Clear();
    145  if (NS_FAILED(rv)) {
    146    return rv;
    147  }
    148  nsCOMPtr<nsINSSComponent> nssComponent(do_GetService(NS_NSSCOMPONENT_CID));
    149  if (!nssComponent) {
    150    return NS_ERROR_NOT_AVAILABLE;
    151  }
    152  return nssComponent->ClearSSLExternalAndInternalSessionCache();
    153 }
    154 
    155 NS_IMETHODIMP
    156 nsClientAuthRememberService::DeleteDecisionsByHost(
    157    const nsACString& aHostName, JS::Handle<JS::Value> aOriginAttributes,
    158    JSContext* aCx) {
    159  OriginAttributes attrs;
    160  if (!aOriginAttributes.isObject() || !attrs.Init(aCx, aOriginAttributes)) {
    161    return NS_ERROR_INVALID_ARG;
    162  }
    163  bool isPrivateContext = attrs.IsPrivateBrowsing();
    164 
    165  nsTArray<RefPtr<nsIDataStorageItem>> decisions;
    166  nsresult rv = mClientAuthRememberList->GetAll(decisions);
    167  if (NS_FAILED(rv)) {
    168    return rv;
    169  }
    170 
    171  for (const auto& decision : decisions) {
    172    nsIDataStorage::DataType type;
    173    nsresult rv = decision->GetType(&type);
    174    if (NS_FAILED(rv)) {
    175      return rv;
    176    }
    177    bool isPrivateDecision = type == nsIDataStorage::DataType::Private;
    178    if (isPrivateContext == isPrivateDecision) {
    179      nsAutoCString key;
    180      rv = decision->GetKey(key);
    181      if (NS_FAILED(rv)) {
    182        return rv;
    183      }
    184      nsAutoCString value;
    185      rv = decision->GetValue(value);
    186      if (NS_FAILED(rv)) {
    187        return rv;
    188      }
    189      RefPtr<nsIClientAuthRememberRecord> tmp =
    190          new nsClientAuthRemember(key, value);
    191      nsAutoCString asciiHost;
    192      tmp->GetAsciiHost(asciiHost);
    193      if (asciiHost.Equals(aHostName)) {
    194        rv = mClientAuthRememberList->Remove(key, type);
    195        if (NS_FAILED(rv)) {
    196          return rv;
    197        }
    198      }
    199    }
    200  }
    201  nsCOMPtr<nsINSSComponent> nssComponent(do_GetService(NS_NSSCOMPONENT_CID));
    202  if (!nssComponent) {
    203    return NS_ERROR_NOT_AVAILABLE;
    204  }
    205  return nssComponent->ClearSSLExternalAndInternalSessionCache();
    206 }
    207 
    208 NS_IMETHODIMP
    209 nsClientAuthRememberService::RememberDecisionScriptable(
    210    const nsACString& aHostName, JS::Handle<JS::Value> aOriginAttributes,
    211    nsIX509Cert* aClientCert, Duration aDuration, JSContext* aCx) {
    212  OriginAttributes attrs;
    213  if (!aOriginAttributes.isObject() || !attrs.Init(aCx, aOriginAttributes)) {
    214    return NS_ERROR_INVALID_ARG;
    215  }
    216  return RememberDecision(aHostName, attrs, aClientCert, aDuration);
    217 }
    218 
    219 NS_IMETHODIMP
    220 nsClientAuthRememberService::RememberDecision(
    221    const nsACString& aHostName, const OriginAttributes& aOriginAttributes,
    222    nsIX509Cert* aClientCert, Duration aDuration) {
    223  if (aHostName.IsEmpty()) {
    224    return NS_ERROR_INVALID_ARG;
    225  }
    226 
    227  // If a decision is to only be used once, it doesn't need to be remembered in
    228  // any way.
    229  if (aDuration == nsIClientAuthRememberService::Duration::Once) {
    230    return NS_OK;
    231  }
    232 
    233  // aClientCert == nullptr means: remember that user does not want to use a
    234  // cert
    235  if (aClientCert) {
    236    nsAutoCString dbkey;
    237    nsresult rv = aClientCert->GetDbKey(dbkey);
    238    if (NS_FAILED(rv)) {
    239      return rv;
    240    }
    241    return AddEntryToList(aHostName, aOriginAttributes, dbkey, aDuration);
    242  }
    243  return AddEntryToList(aHostName, aOriginAttributes,
    244                        nsClientAuthRemember::SentinelValue, aDuration);
    245 }
    246 
    247 #ifdef XP_MACOSX
    248 // On macOS, users can add "identity preference" items in the keychain. These
    249 // can be added via the Keychain Access tool. These specify mappings from
    250 // URLs/wildcards like "*.mozilla.org" to specific client certificates. This
    251 // function retrieves the preferred client certificate for a hostname by
    252 // querying a system API that checks for these identity preferences.
    253 nsresult CheckForPreferredCertificate(const nsACString& aHostName,
    254                                      nsACString& aCertDBKey) {
    255  aCertDBKey.Truncate();
    256  // SecIdentityCopyPreferred seems to expect a proper URI which it can use
    257  // for prefix and wildcard matches.
    258  // We don't have the full URL but we can turn the hostname into a URI with
    259  // an authority section, so that it matches against macOS identity preferences
    260  // like `*.foo.com`. If we know that this connection is always going to be
    261  // https, then we should put that in the URI as well, so that it matches
    262  // identity preferences like `https://foo.com/` as well. If we can plumb
    263  // the path or the full URL into this function we could also match identity
    264  // preferences like `https://foo.com/bar/` but for now we cannot.
    265  nsPrintfCString fakeUrl("//%s/", PromiseFlatCString(aHostName).get());
    266  ScopedCFType<CFStringRef> host(::CFStringCreateWithCString(
    267      kCFAllocatorDefault, fakeUrl.get(), kCFStringEncodingUTF8));
    268  if (!host) {
    269    return NS_ERROR_UNEXPECTED;
    270  }
    271  ScopedCFType<SecIdentityRef> identity(
    272      ::SecIdentityCopyPreferred(host.get(), NULL, NULL));
    273  if (!identity) {
    274    // No preferred identity for this hostname, leave aCertDBKey empty and
    275    // return
    276    return NS_OK;
    277  }
    278  SecCertificateRef certRefRaw = NULL;
    279  OSStatus copyResult =
    280      ::SecIdentityCopyCertificate(identity.get(), &certRefRaw);
    281  ScopedCFType<SecCertificateRef> certRef(certRefRaw);
    282  if (copyResult != errSecSuccess || certRef.get() == NULL) {
    283    return NS_ERROR_UNEXPECTED;
    284  }
    285  ScopedCFType<CFDataRef> der(::SecCertificateCopyData(certRef.get()));
    286  if (!der) {
    287    return NS_ERROR_UNEXPECTED;
    288  }
    289 
    290  nsTArray<uint8_t> derArray(::CFDataGetBytePtr(der.get()),
    291                             ::CFDataGetLength(der.get()));
    292  nsCOMPtr<nsIX509Cert> cert(new nsNSSCertificate(std::move(derArray)));
    293  return cert->GetDbKey(aCertDBKey);
    294 }
    295 #endif
    296 
    297 void nsClientAuthRememberService::Migrate() {
    298  auto migrated = mMigrated.Lock();
    299  if (*migrated) {
    300    return;
    301  }
    302  *migrated = true;
    303 
    304  nsTArray<RefPtr<nsIDataStorageItem>> decisions;
    305  nsresult rv = mClientAuthRememberList->GetAll(decisions);
    306  if (NS_FAILED(rv)) {
    307    return;
    308  }
    309  for (const auto& decision : decisions) {
    310    nsIDataStorage::DataType type;
    311    if (NS_FAILED(decision->GetType(&type))) {
    312      continue;
    313    }
    314    if (type != nsIDataStorage::DataType::Persistent) {
    315      continue;
    316    }
    317    nsAutoCString key;
    318    if (NS_FAILED(decision->GetKey(key))) {
    319      continue;
    320    }
    321    nsAutoCString value;
    322    if (NS_FAILED(decision->GetValue(value))) {
    323      continue;
    324    }
    325    RefPtr<nsClientAuthRemember> entry(new nsClientAuthRemember(key, value));
    326    nsAutoCString newKey;
    327    if (NS_FAILED(entry->GetEntryKey(newKey))) {
    328      continue;
    329    }
    330    if (newKey != key) {
    331      if (NS_FAILED(mClientAuthRememberList->Remove(
    332              key, nsIDataStorage::DataType::Persistent))) {
    333        continue;
    334      }
    335      if (NS_FAILED(mClientAuthRememberList->Put(
    336              newKey, value, nsIDataStorage::DataType::Persistent))) {
    337        continue;
    338      }
    339    }
    340  }
    341 }
    342 
    343 NS_IMETHODIMP
    344 nsClientAuthRememberService::HasRememberedDecision(
    345    const nsACString& aHostName, const OriginAttributes& aOriginAttributes,
    346    nsACString& aCertDBKey, bool* aRetVal) {
    347  NS_ENSURE_ARG_POINTER(aRetVal);
    348  if (aHostName.IsEmpty()) {
    349    return NS_ERROR_INVALID_ARG;
    350  }
    351 
    352  *aRetVal = false;
    353  aCertDBKey.Truncate();
    354 
    355  Migrate();
    356 
    357  nsAutoCString entryKey;
    358  RefPtr<nsClientAuthRemember> entry(
    359      new nsClientAuthRemember(aHostName, aOriginAttributes));
    360  nsresult rv = entry->GetEntryKey(entryKey);
    361  if (NS_FAILED(rv)) {
    362    return rv;
    363  }
    364 
    365  nsTArray<nsIDataStorage::DataType> typesToTry;
    366  if (aOriginAttributes.IsPrivateBrowsing()) {
    367    typesToTry.AppendElement(nsIDataStorage::DataType::Private);
    368  } else {
    369    typesToTry.AppendElement(nsIDataStorage::DataType::Persistent);
    370    typesToTry.AppendElement(nsIDataStorage::DataType::Temporary);
    371  }
    372 
    373  for (const auto& storageType : typesToTry) {
    374    nsAutoCString listEntry;
    375    rv = mClientAuthRememberList->Get(entryKey, storageType, listEntry);
    376    if (NS_FAILED(rv) && rv != NS_ERROR_NOT_AVAILABLE) {
    377      return rv;
    378    }
    379    if (NS_SUCCEEDED(rv) && !listEntry.IsEmpty()) {
    380      if (!listEntry.Equals(nsClientAuthRemember::SentinelValue)) {
    381        aCertDBKey = listEntry;
    382      }
    383      *aRetVal = true;
    384      return NS_OK;
    385    }
    386  }
    387 
    388 #ifdef XP_MACOSX
    389  rv = CheckForPreferredCertificate(aHostName, aCertDBKey);
    390  if (NS_FAILED(rv)) {
    391    return rv;
    392  }
    393  if (!aCertDBKey.IsEmpty()) {
    394    *aRetVal = true;
    395    return NS_OK;
    396  }
    397 #endif
    398 
    399  return NS_OK;
    400 }
    401 
    402 NS_IMETHODIMP
    403 nsClientAuthRememberService::HasRememberedDecisionScriptable(
    404    const nsACString& aHostName, JS::Handle<JS::Value> aOriginAttributes,
    405    nsACString& aCertDBKey, JSContext* aCx, bool* aRetVal) {
    406  OriginAttributes attrs;
    407  if (!aOriginAttributes.isObject() || !attrs.Init(aCx, aOriginAttributes)) {
    408    return NS_ERROR_INVALID_ARG;
    409  }
    410  return HasRememberedDecision(aHostName, attrs, aCertDBKey, aRetVal);
    411 }
    412 
    413 nsresult nsClientAuthRememberService::AddEntryToList(
    414    const nsACString& aHostName, const OriginAttributes& aOriginAttributes,
    415    const nsACString& aDBKey, Duration aDuration) {
    416  nsAutoCString entryKey;
    417  RefPtr<nsClientAuthRemember> entry(
    418      new nsClientAuthRemember(aHostName, aOriginAttributes));
    419  nsresult rv = entry->GetEntryKey(entryKey);
    420  if (NS_FAILED(rv)) {
    421    return rv;
    422  }
    423 
    424  nsIDataStorage::DataType storageType;
    425  if (aOriginAttributes.IsPrivateBrowsing()) {
    426    storageType = nsIDataStorage::DataType::Private;
    427  } else if (aDuration == nsIClientAuthRememberService::Duration::Permanent) {
    428    storageType = nsIDataStorage::DataType::Persistent;
    429  } else if (aDuration == nsIClientAuthRememberService::Duration::Session) {
    430    storageType = nsIDataStorage::DataType::Temporary;
    431  } else {
    432    return NS_ERROR_INVALID_ARG;
    433  }
    434 
    435  nsCString tmpDbKey(aDBKey);
    436  rv = mClientAuthRememberList->Put(entryKey, tmpDbKey, storageType);
    437  if (NS_FAILED(rv)) {
    438    return rv;
    439  }
    440 
    441  return NS_OK;
    442 }
    443 
    444 bool nsClientAuthRememberService::IsPrivateBrowsingKey(
    445    const nsCString& entryKey) {
    446  const int32_t separator = entryKey.Find(":");
    447  nsCString suffix;
    448  if (separator >= 0) {
    449    entryKey.Left(suffix, separator);
    450  } else {
    451    suffix = entryKey;
    452  }
    453  return OriginAttributes::IsPrivateBrowsing(suffix);
    454 }