/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#include "nsNSSComponent.h"

#include "BinaryPath.h"
#include "CryptoTask.h"
#include "EnterpriseRoots.h"
#include "ExtendedValidation.h"
#include "NSSCertDBTrustDomain.h"
#include "SSLTokensCache.h"
#include "ScopedNSSTypes.h"
#include "SharedSSLState.h"
#include "cert.h"
#include "cert_storage/src/cert_storage.h"
#include "certdb.h"
#include "mozilla/AppShutdown.h"
#include "mozilla/ArrayUtils.h"
#include "mozilla/Assertions.h"
#include "mozilla/Casting.h"
#include "mozilla/EndianUtils.h"
#include "mozilla/FilePreferences.h"
#include "mozilla/PodOperations.h"
#include "mozilla/Preferences.h"
#include "mozilla/ProfilerLabels.h"
#include "mozilla/ProfilerMarkers.h"
#include "mozilla/PublicSSL.h"
#include "mozilla/ScopeExit.h"
#include "mozilla/Services.h"
#include "mozilla/StaticMutex.h"
#include "mozilla/StaticPrefs_security.h"
#include "mozilla/StaticPtr.h"
#include "mozilla/SyncRunnable.h"
#include "mozilla/Telemetry.h"
#include "mozilla/TimeStamp.h"
#include "mozilla/Unused.h"
#include "mozilla/Vector.h"
#include "mozilla/dom/Promise.h"
#include "mozilla/net/SocketProcessParent.h"
#include "mozpkix/pkixnss.h"
#include "nsAppDirectoryServiceDefs.h"
#include "nsCRT.h"
#include "nsClientAuthRemember.h"
#include "nsComponentManagerUtils.h"
#include "nsDirectoryServiceDefs.h"
#include "nsICertOverrideService.h"
#include "nsIFile.h"
#include "nsILocalFileWin.h"
#include "nsIOService.h"
#include "nsIObserverService.h"
#include "nsIPrompt.h"
#include "nsIProperties.h"
#include "nsISerialEventTarget.h"
#include "nsISiteSecurityService.h"
#include "nsITimer.h"
#include "nsITokenPasswordDialogs.h"
#include "nsIWindowWatcher.h"
#include "nsIXULRuntime.h"
#include "nsLiteralString.h"
#include "nsNSSCertificateDB.h"
#include "nsNSSHelper.h"
#include "nsNetCID.h"
#include "nsPK11TokenDB.h"
#include "nsPrintfCString.h"
#include "nsServiceManagerUtils.h"
#include "nsThreadUtils.h"
#include "nsXULAppAPI.h"
#include "nss.h"
#include "p12plcy.h"
#include "pk11pub.h"
#include "prmem.h"
#include "secerr.h"
#include "secmod.h"
#include "ssl.h"
#include "sslerr.h"
#include "sslproto.h"

#if defined(XP_LINUX) && !defined(ANDROID)
#  include <linux/magic.h>
#  include <sys/vfs.h>
#endif

#ifdef XP_WIN
#  include "mozilla/WindowsVersion.h"
#  include "nsILocalFileWin.h"

#  include "windows.h"  // this needs to be before the following includes
#  include "lmcons.h"
#  include "sddl.h"
#  include "wincrypt.h"
#  include "nsIWindowsRegKey.h"
#endif

using namespace mozilla;
using namespace mozilla::psm;

LazyLogModule gPIPNSSLog("pipnss");

int nsNSSComponent::mInstanceCount = 0;

// Forward declaration.
nsresult CommonInit();

// Take an nsIFile and get a UTF-8-encoded c-string representation of the
// location of that file (encapsulated in an nsACString).
// This operation is generally to be avoided, except when interacting with
// third-party or legacy libraries that cannot handle `nsIFile`s (such as NSS).
// |result| is encoded in UTF-8.
nsresult FileToCString(const nsCOMPtr<nsIFile>& file, nsACString& result) {
#ifdef XP_WIN
  nsAutoString path;
  nsresult rv = file->GetPath(path);
  if (NS_SUCCEEDED(rv)) {
    CopyUTF16toUTF8(path, result);
  }
  return rv;
#else
  return file->GetNativePath(result);
#endif
}

void TruncateFromLastDirectorySeparator(nsCString& path) {
  static const nsAutoCString kSeparatorString(
      mozilla::FilePreferences::kPathSeparator);
  int32_t index = path.RFind(kSeparatorString);
  if (index == kNotFound) {
    return;
  }
  path.Truncate(index);
}

bool LoadIPCClientCerts() {
  // This returns the path to the binary currently running, which in most
  // cases is "plugin-container".
  UniqueFreePtr<char> pluginContainerPath(BinaryPath::Get());
  if (!pluginContainerPath) {
    MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
            ("failed to get get plugin-container path"));
    return false;
  }
  nsAutoCString ipcClientCertsDirString(pluginContainerPath.get());
  // On most platforms, ipcclientcerts is in the same directory as
  // plugin-container. To obtain the path to that directory, truncate from
  // the last directory separator.
  // On macOS, plugin-container is in
  // Firefox.app/Contents/MacOS/plugin-container.app/Contents/MacOS/,
  // whereas ipcclientcerts is in Firefox.app/Contents/MacOS/. Consequently,
  // this truncation from the last directory separator has to happen 4 times
  // total. Normally this would be done using nsIFile APIs, but due to when
  // this is initialized in the socket process, those aren't available.
  TruncateFromLastDirectorySeparator(ipcClientCertsDirString);
#ifdef XP_MACOSX
  TruncateFromLastDirectorySeparator(ipcClientCertsDirString);
  TruncateFromLastDirectorySeparator(ipcClientCertsDirString);
  TruncateFromLastDirectorySeparator(ipcClientCertsDirString);
#endif
  if (!LoadIPCClientCertsModule(ipcClientCertsDirString)) {
    MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
            ("failed to load ipcclientcerts from '%s'",
             ipcClientCertsDirString.get()));
    return false;
  }
  return true;
}

// This function can be called from chrome or content or socket processes
// to ensure that NSS is initialized.
bool EnsureNSSInitializedChromeOrContent() {
  static Atomic<bool> initialized(false);

  if (initialized) {
    return true;
  }

  // If this is not the main thread (i.e. probably a worker) then forward this
  // call to the main thread.
  if (!NS_IsMainThread()) {
    nsCOMPtr<nsIThread> mainThread;
    nsresult rv = NS_GetMainThread(getter_AddRefs(mainThread));
    if (NS_FAILED(rv)) {
      return false;
    }

    // Forward to the main thread synchronously.
    mozilla::SyncRunnable::DispatchToThread(
        mainThread,
        NS_NewRunnableFunction("EnsureNSSInitializedChromeOrContent", []() {
          EnsureNSSInitializedChromeOrContent();
        }));

    return initialized;
  }

  if (XRE_IsParentProcess()) {
    nsCOMPtr<nsISupports> nss = do_GetService(PSM_COMPONENT_CONTRACTID);
    if (!nss) {
      return false;
    }
    initialized = true;
    return true;
  }

  if (NSS_IsInitialized()) {
    initialized = true;
    return true;
  }

  if (NSS_NoDB_Init(nullptr) != SECSuccess) {
    return false;
  }

  if (XRE_IsSocketProcess()) {
    if (NS_FAILED(CommonInit())) {
      return false;
    }
    // If ipcclientcerts fails to load, client certificate authentication won't
    // work (if networking is done on the socket process). This is preferable
    // to stopping the program entirely, so treat this as best-effort.
    Unused << NS_WARN_IF(!LoadIPCClientCerts());
    initialized = true;
    return true;
  }

  if (NS_FAILED(mozilla::psm::InitializeCipherSuite())) {
    return false;
  }

  mozilla::psm::DisableMD5();
  mozilla::pkix::RegisterErrorTable();
  initialized = true;
  return true;
}

static const uint32_t OCSP_TIMEOUT_MILLISECONDS_SOFT_MAX = 5000;
static const uint32_t OCSP_TIMEOUT_MILLISECONDS_HARD_MAX = 20000;

void nsNSSComponent::GetRevocationBehaviorFromPrefs(
    /*out*/ CertVerifier::OcspDownloadConfig* odc,
    /*out*/ CertVerifier::OcspStrictConfig* osc,
    /*out*/ uint32_t* certShortLifetimeInDays,
    /*out*/ TimeDuration& softTimeout,
    /*out*/ TimeDuration& hardTimeout) {
  MOZ_ASSERT(NS_IsMainThread());
  MOZ_ASSERT(odc);
  MOZ_ASSERT(osc);
  MOZ_ASSERT(certShortLifetimeInDays);

  // 0 = disabled
  // 1 = enabled for everything (default)
  // 2 = enabled for EV certificates only
  uint32_t ocspLevel = StaticPrefs::security_OCSP_enabled();
  switch (ocspLevel) {
    case 0:
      *odc = CertVerifier::ocspOff;
      break;
    case 2:
      *odc = CertVerifier::ocspEVOnly;
      break;
    default:
      *odc = CertVerifier::ocspOn;
      break;
  }

  *osc = StaticPrefs::security_OCSP_require() ? CertVerifier::ocspStrict
                                              : CertVerifier::ocspRelaxed;

  *certShortLifetimeInDays =
      StaticPrefs::security_pki_cert_short_lifetime_in_days();

  uint32_t softTimeoutMillis =
      StaticPrefs::security_OCSP_timeoutMilliseconds_soft();
  softTimeoutMillis =
      std::min(softTimeoutMillis, OCSP_TIMEOUT_MILLISECONDS_SOFT_MAX);
  softTimeout = TimeDuration::FromMilliseconds(softTimeoutMillis);

  uint32_t hardTimeoutMillis =
      StaticPrefs::security_OCSP_timeoutMilliseconds_hard();
  hardTimeoutMillis =
      std::min(hardTimeoutMillis, OCSP_TIMEOUT_MILLISECONDS_HARD_MAX);
  hardTimeout = TimeDuration::FromMilliseconds(hardTimeoutMillis);
}

nsNSSComponent::nsNSSComponent()
    : mLoadableCertsLoadedMonitor("nsNSSComponent.mLoadableCertsLoadedMonitor"),
      mLoadableCertsLoaded(false),
      mLoadableCertsLoadedResult(NS_ERROR_FAILURE),
      mMutex("nsNSSComponent.mMutex"),
      mMitmDetecionEnabled(false) {
  MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsNSSComponent::ctor\n"));
  MOZ_RELEASE_ASSERT(NS_IsMainThread());

  MOZ_ASSERT(mInstanceCount == 0,
             "nsNSSComponent is a singleton, but instantiated multiple times!");
  ++mInstanceCount;
}

nsNSSComponent::~nsNSSComponent() {
  MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsNSSComponent::dtor\n"));
  MOZ_RELEASE_ASSERT(NS_IsMainThread());

  // All cleanup code requiring services needs to happen in xpcom_shutdown

  PrepareForShutdown();
  SharedSSLState::GlobalCleanup();
  --mInstanceCount;

  MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsNSSComponent::dtor finished\n"));
}

#ifdef XP_WIN
static bool GetUserSid(nsAString& sidString) {
  // UNLEN is the maximum user name length (see Lmcons.h). +1 for the null
  // terminator.
  WCHAR lpAccountName[UNLEN + 1];
  DWORD lcAccountName = sizeof(lpAccountName) / sizeof(lpAccountName[0]);
  BOOL success = GetUserName(lpAccountName, &lcAccountName);
  if (!success) {
    MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("GetUserName failed"));
    return false;
  }
  char sid_buffer[SECURITY_MAX_SID_SIZE];
  SID* sid = BitwiseCast<SID*, char*>(sid_buffer);
  DWORD cbSid = ArrayLength(sid_buffer);
  SID_NAME_USE eUse;
  // There doesn't appear to be a defined maximum length for the domain name
  // here. To deal with this, we start with a reasonable buffer length and
  // see if that works. If it fails and the error indicates insufficient length,
  // we use the indicated required length and try again.
  DWORD cchReferencedDomainName = 128;
  auto ReferencedDomainName(MakeUnique<WCHAR[]>(cchReferencedDomainName));
  success = LookupAccountName(nullptr, lpAccountName, sid, &cbSid,
                              ReferencedDomainName.get(),
                              &cchReferencedDomainName, &eUse);
  if (!success && GetLastError() != ERROR_INSUFFICIENT_BUFFER) {
    MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("LookupAccountName failed"));
    return false;
  }
  if (!success) {
    ReferencedDomainName = MakeUnique<WCHAR[]>(cchReferencedDomainName);
    success = LookupAccountName(nullptr, lpAccountName, sid, &cbSid,
                                ReferencedDomainName.get(),
                                &cchReferencedDomainName, &eUse);
  }
  if (!success) {
    MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("LookupAccountName failed"));
    return false;
  }
  LPTSTR StringSid;
  success = ConvertSidToStringSid(sid, &StringSid);
  if (!success) {
    MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("ConvertSidToStringSid failed"));
    return false;
  }
  sidString.Assign(StringSid);
  LocalFree(StringSid);
  return true;
}

// This is a specialized helper function to read the value of a registry key
// that might not be present. If it is present, returns (via the output
// parameter) its value. Otherwise, returns the given default value.
// This function handles one level of nesting. That is, if the desired value
// is actually in a direct child of the given registry key (where the child
// and/or the value being sought may not actually be present), this function
// will handle that. In the normal case, though, optionalChildName will be
// null.
static nsresult ReadRegKeyValueWithDefault(nsCOMPtr<nsIWindowsRegKey> regKey,
                                           uint32_t flags,
                                           const wchar_t* optionalChildName,
                                           const wchar_t* valueName,
                                           uint32_t defaultValue,
                                           uint32_t& valueOut) {
  MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("ReadRegKeyValueWithDefault"));
  MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
          ("attempting to read '%S%s%S' with default '%u'",
           optionalChildName ? optionalChildName : L"",
           optionalChildName ? "\\" : "", valueName, defaultValue));
  if (optionalChildName) {
    nsDependentString childNameString(optionalChildName);
    bool hasChild;
    nsresult rv = regKey->HasChild(childNameString, &hasChild);
    if (NS_FAILED(rv)) {
      MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
              ("failed to determine if child key is present"));
      return rv;
    }
    if (!hasChild) {
      valueOut = defaultValue;
      return NS_OK;
    }
    nsCOMPtr<nsIWindowsRegKey> childRegKey;
    rv = regKey->OpenChild(childNameString, flags, getter_AddRefs(childRegKey));
    if (NS_FAILED(rv)) {
      MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("couldn't open child key"));
      return rv;
    }
    return ReadRegKeyValueWithDefault(childRegKey, flags, nullptr, valueName,
                                      defaultValue, valueOut);
  }
  nsDependentString valueNameString(valueName);
  bool hasValue;
  nsresult rv = regKey->HasValue(valueNameString, &hasValue);
  if (NS_FAILED(rv)) {
    MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
            ("failed to determine if value is present"));
    return rv;
  }
  if (!hasValue) {
    valueOut = defaultValue;
    return NS_OK;
  }
  rv = regKey->ReadIntValue(valueNameString, &valueOut);
  if (NS_FAILED(rv)) {
    MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("failed to read value"));
    return rv;
  }
  return NS_OK;
}

static nsresult AccountHasFamilySafetyEnabled(bool& enabled) {
  enabled = false;
  MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("AccountHasFamilySafetyEnabled?"));
  nsCOMPtr<nsIWindowsRegKey> parentalControlsKey(
      do_CreateInstance("@mozilla.org/windows-registry-key;1"));
  if (!parentalControlsKey) {
    MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("couldn't create nsIWindowsRegKey"));
    return NS_ERROR_FAILURE;
  }
  uint32_t flags = nsIWindowsRegKey::ACCESS_READ | nsIWindowsRegKey::WOW64_64;
  constexpr auto familySafetyPath =
      u"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Parental Controls"_ns;
  nsresult rv = parentalControlsKey->Open(
      nsIWindowsRegKey::ROOT_KEY_LOCAL_MACHINE, familySafetyPath, flags);
  if (NS_FAILED(rv)) {
    MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("couldn't open parentalControlsKey"));
    return rv;
  }
  constexpr auto usersString = u"Users"_ns;
  bool hasUsers;
  rv = parentalControlsKey->HasChild(usersString, &hasUsers);
  if (NS_FAILED(rv)) {
    MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("HasChild(Users) failed"));
    return rv;
  }
  if (!hasUsers) {
    MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
            ("Users subkey not present - Parental Controls not enabled"));
    return NS_OK;
  }
  nsCOMPtr<nsIWindowsRegKey> usersKey;
  rv = parentalControlsKey->OpenChild(usersString, flags,
                                      getter_AddRefs(usersKey));
  if (NS_FAILED(rv)) {
    MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("failed to open Users subkey"));
    return rv;
  }
  nsAutoString sid;
  if (!GetUserSid(sid)) {
    MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("couldn't get sid"));
    return NS_ERROR_FAILURE;
  }
  MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
          ("our sid is '%S'", static_cast<const wchar_t*>(sid.get())));
  bool hasSid;
  rv = usersKey->HasChild(sid, &hasSid);
  if (NS_FAILED(rv)) {
    MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("HasChild(sid) failed"));
    return rv;
  }
  if (!hasSid) {
    MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
            ("sid not present in Family Safety Users"));
    return NS_OK;
  }
  nsCOMPtr<nsIWindowsRegKey> sidKey;
  rv = usersKey->OpenChild(sid, flags, getter_AddRefs(sidKey));
  if (NS_FAILED(rv)) {
    MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("couldn't open sid key"));
    return rv;
  }
  // There are three keys we're interested in: "Parental Controls On",
  // "Logging Required", and "Web\\Filter On". These keys will have value 0
  // or 1, indicating a particular feature is disabled or enabled,
  // respectively. So, if "Parental Controls On" is not 1, Family Safety is
  // disabled and we don't care about anything else. If both "Logging
  // Required" and "Web\\Filter On" are 0, the proxy will not be running,
  // so for our purposes we can consider Family Safety disabled in that
  // case.
  // By default, "Logging Required" is 1 and "Web\\Filter On" is 0,
  // reflecting the initial settings when Family Safety is enabled for an
  // account for the first time, However, these sub-keys are not created
  // unless they are switched away from the default value.
  uint32_t parentalControlsOn;
  rv = sidKey->ReadIntValue(u"Parental Controls On"_ns, &parentalControlsOn);
  if (NS_FAILED(rv)) {
    MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
            ("couldn't read Parental Controls On"));
    return rv;
  }
  MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
          ("Parental Controls On: %u", parentalControlsOn));
  if (parentalControlsOn != 1) {
    return NS_OK;
  }
  uint32_t loggingRequired;
  rv = ReadRegKeyValueWithDefault(sidKey, flags, nullptr, L"Logging Required",
                                  1, loggingRequired);
  if (NS_FAILED(rv)) {
    MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
            ("failed to read value of Logging Required"));
    return rv;
  }
  MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
          ("Logging Required: %u", loggingRequired));
  uint32_t webFilterOn;
  rv = ReadRegKeyValueWithDefault(sidKey, flags, L"Web", L"Filter On", 0,
                                  webFilterOn);
  if (NS_FAILED(rv)) {
    MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
            ("failed to read value of Web\\Filter On"));
    return rv;
  }
  MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("Web\\Filter On: %u", webFilterOn));
  enabled = loggingRequired == 1 || webFilterOn == 1;
  return NS_OK;
}
#endif  // XP_WIN

bool nsNSSComponent::ShouldEnableEnterpriseRootsForFamilySafety(
    uint32_t familySafetyMode) {
#ifdef XP_WIN
  if (!(IsWin8Point1OrLater() && !IsWin10OrLater())) {
    return false;
  }
  if (familySafetyMode != 2) {
    return false;
  }
  bool familySafetyEnabled;
  nsresult rv = AccountHasFamilySafetyEnabled(familySafetyEnabled);
  if (NS_FAILED(rv)) {
    return false;
  }
  return familySafetyEnabled;
#else
  return false;
#endif  // XP_WIN
}

void nsNSSComponent::UnloadEnterpriseRoots() {
  MOZ_ASSERT(NS_IsMainThread());
  if (!NS_IsMainThread()) {
    return;
  }
  MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("UnloadEnterpriseRoots"));
  MutexAutoLock lock(mMutex);
  mEnterpriseCerts.clear();
  setValidationOptions(false, lock);
  ClearSSLExternalAndInternalSessionCache();
}

class BackgroundImportEnterpriseCertsTask final : public CryptoTask {
 public:
  explicit BackgroundImportEnterpriseCertsTask(nsNSSComponent* nssComponent)
      : mNSSComponent(nssComponent) {}

 private:
  virtual nsresult CalculateResult() override {
    mNSSComponent->ImportEnterpriseRoots();
    mNSSComponent->UpdateCertVerifierWithEnterpriseRoots();
    return NS_OK;
  }

  virtual void CallCallback(nsresult rv) override {
    nsCOMPtr<nsIObserverService> observerService =
        mozilla::services::GetObserverService();
    if (observerService) {
      observerService->NotifyObservers(nullptr, "psm:enterprise-certs-imported",
                                       nullptr);
    }
  }

  RefPtr<nsNSSComponent> mNSSComponent;
};

void nsNSSComponent::MaybeImportEnterpriseRoots() {
  MOZ_ASSERT(NS_IsMainThread());
  if (!NS_IsMainThread()) {
    return;
  }
  bool importEnterpriseRoots = StaticPrefs::security_enterprise_roots_enabled();
  uint32_t familySafetyMode = StaticPrefs::security_family_safety_mode();
  // If we've been configured to detect the Family Safety TLS interception
  // feature, see if it's enabled. If so, we want to import enterprise roots.
  if (ShouldEnableEnterpriseRootsForFamilySafety(familySafetyMode)) {
    importEnterpriseRoots = true;
  }
  if (importEnterpriseRoots) {
    RefPtr<BackgroundImportEnterpriseCertsTask> task =
        new BackgroundImportEnterpriseCertsTask(this);
    Unused << task->Dispatch();
  }
}

void nsNSSComponent::ImportEnterpriseRoots() {
  MOZ_ASSERT(!NS_IsMainThread());
  if (NS_IsMainThread()) {
    return;
  }

  Vector<EnterpriseCert> enterpriseCerts;
  nsresult rv = GatherEnterpriseCerts(enterpriseCerts);
  if (NS_SUCCEEDED(rv)) {
    MutexAutoLock lock(mMutex);
    mEnterpriseCerts = std::move(enterpriseCerts);
  } else {
    MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("failed gathering enterprise roots"));
  }
}

nsresult nsNSSComponent::CommonGetEnterpriseCerts(
    nsTArray<nsTArray<uint8_t>>& enterpriseCerts, bool getRoots) {
  nsresult rv = BlockUntilLoadableCertsLoaded();
  if (NS_FAILED(rv)) {
    return rv;
  }

  MutexAutoLock nsNSSComponentLock(mMutex);
  enterpriseCerts.Clear();
  for (const auto& cert : mEnterpriseCerts) {
    nsTArray<uint8_t> certCopy;
    // mEnterpriseCerts includes both roots and intermediates.
    if (cert.GetIsRoot() == getRoots) {
      nsresult rv = cert.CopyBytes(certCopy);
      if (NS_FAILED(rv)) {
        return rv;
      }
      // XXX(Bug 1631371) Check if this should use a fallible operation as it
      // pretended earlier.
      enterpriseCerts.AppendElement(std::move(certCopy));
    }
  }
  return NS_OK;
}

NS_IMETHODIMP
nsNSSComponent::GetEnterpriseRoots(
    nsTArray<nsTArray<uint8_t>>& enterpriseRoots) {
  return CommonGetEnterpriseCerts(enterpriseRoots, true);
}

NS_IMETHODIMP
nsNSSComponent::GetEnterpriseIntermediates(
    nsTArray<nsTArray<uint8_t>>& enterpriseIntermediates) {
  return CommonGetEnterpriseCerts(enterpriseIntermediates, false);
}

NS_IMETHODIMP
nsNSSComponent::AddEnterpriseIntermediate(
    const nsTArray<uint8_t>& intermediateBytes) {
  nsresult rv = BlockUntilLoadableCertsLoaded();
  if (NS_FAILED(rv)) {
    return rv;
  }
  EnterpriseCert intermediate;
  rv = intermediate.Init(intermediateBytes.Elements(),
                         intermediateBytes.Length(), false);
  if (NS_FAILED(rv)) {
    return rv;
  }

  {
    MutexAutoLock nsNSSComponentLock(mMutex);
    if (!mEnterpriseCerts.append(std::move(intermediate))) {
      return NS_ERROR_OUT_OF_MEMORY;
    }
  }

  UpdateCertVerifierWithEnterpriseRoots();
  return NS_OK;
}

class LoadLoadableCertsTask final : public Runnable {
 public:
  LoadLoadableCertsTask(nsNSSComponent* nssComponent,
                        bool importEnterpriseRoots, uint32_t familySafetyMode,
                        Vector<nsCString>&& possibleLoadableRootsLocations,
                        Maybe<nsCString>&& osClientCertsModuleLocation)
      : Runnable("LoadLoadableCertsTask"),
        mNSSComponent(nssComponent),
        mImportEnterpriseRoots(importEnterpriseRoots),
        mFamilySafetyMode(familySafetyMode),
        mPossibleLoadableRootsLocations(
            std::move(possibleLoadableRootsLocations)),
        mOSClientCertsModuleLocation(std::move(osClientCertsModuleLocation)) {
    MOZ_ASSERT(nssComponent);
  }

  ~LoadLoadableCertsTask() = default;

  nsresult Dispatch();

 private:
  NS_IMETHOD Run() override;
  nsresult LoadLoadableRoots();
  RefPtr<nsNSSComponent> mNSSComponent;
  bool mImportEnterpriseRoots;
  uint32_t mFamilySafetyMode;
  Vector<nsCString> mPossibleLoadableRootsLocations;  // encoded in UTF-8
  Maybe<nsCString> mOSClientCertsModuleLocation;      // encoded in UTF-8
};

nsresult LoadLoadableCertsTask::Dispatch() {
  // The stream transport service (note: not the socket transport service) can
  // be used to perform background tasks or I/O that would otherwise block the
  // main thread.
  nsCOMPtr<nsIEventTarget> target(
      do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID));
  if (!target) {
    return NS_ERROR_FAILURE;
  }
  return target->Dispatch(this, NS_DISPATCH_NORMAL);
}

NS_IMETHODIMP
LoadLoadableCertsTask::Run() {
  Telemetry::AutoScalarTimer<Telemetry::ScalarID::NETWORKING_LOADING_CERTS_TASK>
      timer;

  nsresult loadLoadableRootsResult = LoadLoadableRoots();
  if (NS_WARN_IF(NS_FAILED(loadLoadableRootsResult))) {
    MOZ_LOG(gPIPNSSLog, LogLevel::Error, ("LoadLoadableRoots failed"));
    // We don't return loadLoadableRootsResult here because then
    // BlockUntilLoadableCertsLoaded will just wait forever. Instead we'll save
    // its value (below) so we can inform code that relies on the roots module
    // being present that loading it failed.
  }

  // Loading EV information will only succeed if we've successfully loaded the
  // loadable roots module.
  if (NS_SUCCEEDED(loadLoadableRootsResult)) {
    if (NS_FAILED(LoadExtendedValidationInfo())) {
      // This isn't a show-stopper in the same way that failing to load the
      // roots module is.
      MOZ_LOG(gPIPNSSLog, LogLevel::Error, ("failed to load EV info"));
    }
  }

  // If we've been configured to detect the Family Safety TLS interception
  // feature, see if it's enabled. If so, we want to import enterprise roots.
  if (mNSSComponent->ShouldEnableEnterpriseRootsForFamilySafety(
          mFamilySafetyMode)) {
    mImportEnterpriseRoots = true;
  }
  if (mImportEnterpriseRoots) {
    mNSSComponent->ImportEnterpriseRoots();
    mNSSComponent->UpdateCertVerifierWithEnterpriseRoots();
  }
  if (mOSClientCertsModuleLocation.isSome()) {
    bool success = LoadOSClientCertsModule(*mOSClientCertsModuleLocation);
    MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
            ("loading OS client certs module %s",
             success ? "succeeded" : "failed"));
  }
  {
    MonitorAutoLock rootsLoadedLock(mNSSComponent->mLoadableCertsLoadedMonitor);
    mNSSComponent->mLoadableCertsLoaded = true;
    // Cache the result of LoadLoadableRoots so BlockUntilLoadableCertsLoaded
    // can return it to all callers later (we use that particular result because
    // if that operation fails, it's unlikely that any TLS connection will
    // succeed whereas the browser may still be able to operate if the other
    // tasks fail).
    mNSSComponent->mLoadableCertsLoadedResult = loadLoadableRootsResult;
    mNSSComponent->mLoadableCertsLoadedMonitor.NotifyAll();
  }
  return NS_OK;
}

// Returns by reference the path to the desired directory, based on the current
// settings in the directory service.
// |result| is encoded in UTF-8.
static nsresult GetDirectoryPath(const char* directoryKey, nsCString& result) {
  MOZ_ASSERT(NS_IsMainThread());

  nsCOMPtr<nsIProperties> directoryService(
      do_GetService(NS_DIRECTORY_SERVICE_CONTRACTID));
  if (!directoryService) {
    MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("could not get directory service"));
    return NS_ERROR_FAILURE;
  }
  nsCOMPtr<nsIFile> directory;
  nsresult rv = directoryService->Get(directoryKey, NS_GET_IID(nsIFile),
                                      getter_AddRefs(directory));
  if (NS_FAILED(rv)) {
    MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
            ("could not get '%s' from directory service", directoryKey));
    return rv;
  }
  return FileToCString(directory, result);
}

class BackgroundLoadOSClientCertsModuleTask final : public CryptoTask {
 public:
  explicit BackgroundLoadOSClientCertsModuleTask(const nsCString&& libraryDir)
      : mLibraryDir(std::move(libraryDir)) {}

 private:
  virtual nsresult CalculateResult() override {
    bool success = LoadOSClientCertsModule(mLibraryDir);
    return success ? NS_OK : NS_ERROR_FAILURE;
  }

  virtual void CallCallback(nsresult rv) override {
    MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
            ("loading OS client certs module %s",
             NS_SUCCEEDED(rv) ? "succeeded" : "failed"));
    nsCOMPtr<nsIObserverService> observerService =
        mozilla::services::GetObserverService();
    if (observerService) {
      observerService->NotifyObservers(
          nullptr, "psm:load-os-client-certs-module-task-ran", nullptr);
    }
  }

  nsCString mLibraryDir;
};

void AsyncLoadOrUnloadOSClientCertsModule(bool load) {
  if (load) {
    nsCString libraryDir;
    nsresult rv = GetDirectoryPath(NS_GRE_BIN_DIR, libraryDir);
    if (NS_FAILED(rv)) {
      return;
    }
    RefPtr<BackgroundLoadOSClientCertsModuleTask> task =
        new BackgroundLoadOSClientCertsModuleTask(std::move(libraryDir));
    Unused << task->Dispatch();
  } else {
    UniqueSECMODModule osClientCertsModule(
        SECMOD_FindModule(kOSClientCertsModuleName));
    if (osClientCertsModule) {
      SECMOD_UnloadUserModule(osClientCertsModule.get());
    }
  }
}

nsresult nsNSSComponent::BlockUntilLoadableCertsLoaded() {
  MonitorAutoLock rootsLoadedLock(mLoadableCertsLoadedMonitor);
  while (!mLoadableCertsLoaded) {
    rootsLoadedLock.Wait();
  }
  MOZ_ASSERT(mLoadableCertsLoaded);

  return mLoadableCertsLoadedResult;
}

#ifndef MOZ_NO_SMART_CARDS
static StaticMutex sCheckForSmartCardChangesMutex MOZ_UNANNOTATED;
static TimeStamp sLastCheckedForSmartCardChanges = TimeStamp::Now();
#endif

nsresult nsNSSComponent::CheckForSmartCardChanges() {
#ifndef MOZ_NO_SMART_CARDS
  {
    StaticMutexAutoLock lock(sCheckForSmartCardChangesMutex);
    // Do this at most once every 3 seconds.
    TimeStamp now = TimeStamp::Now();
    if (now - sLastCheckedForSmartCardChanges <
        TimeDuration::FromSeconds(3.0)) {
      return NS_OK;
    }
    sLastCheckedForSmartCardChanges = now;
  }

  // SECMOD_UpdateSlotList attempts to acquire the list lock as well, so we
  // have to do this in three steps.
  Vector<UniqueSECMODModule> modulesWithRemovableSlots;
  {
    AutoSECMODListReadLock secmodLock;
    SECMODModuleList* list = SECMOD_GetDefaultModuleList();
    while (list) {
      if (SECMOD_LockedModuleHasRemovableSlots(list->module)) {
        UniqueSECMODModule module(SECMOD_ReferenceModule(list->module));
        if (!modulesWithRemovableSlots.append(std::move(module))) {
          return NS_ERROR_OUT_OF_MEMORY;
        }
      }
      list = list->next;
    }
  }
  for (auto& module : modulesWithRemovableSlots) {
    // Best-effort.
    Unused << SECMOD_UpdateSlotList(module.get());
  }
  AutoSECMODListReadLock secmodLock;
  for (auto& module : modulesWithRemovableSlots) {
    for (int i = 0; i < module->slotCount; i++) {
      // We actually don't care about the return value here - we just need to
      // call this to get NSS to update its view of this slot.
      Unused << PK11_IsPresent(module->slots[i]);
    }
  }
#endif

  return NS_OK;
}

// Returns by reference the path to the directory containing the file that has
// been loaded as MOZ_DLL_PREFIX nss3 MOZ_DLL_SUFFIX.
// |result| is encoded in UTF-8.
static nsresult GetNSS3Directory(nsCString& result) {
  MOZ_ASSERT(NS_IsMainThread());

  UniquePRString nss3Path(
      PR_GetLibraryFilePathname(MOZ_DLL_PREFIX "nss3" MOZ_DLL_SUFFIX,
                                reinterpret_cast<PRFuncPtr>(NSS_Initialize)));
  if (!nss3Path) {
    MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nss not loaded?"));
    return NS_ERROR_FAILURE;
  }
  nsCOMPtr<nsIFile> nss3File(do_CreateInstance(NS_LOCAL_FILE_CONTRACTID));
  if (!nss3File) {
    MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("couldn't create a file?"));
    return NS_ERROR_FAILURE;
  }
  nsAutoCString nss3PathAsString(nss3Path.get());
  nsresult rv = nss3File->InitWithNativePath(nss3PathAsString);
  if (NS_FAILED(rv)) {
    MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
            ("couldn't initialize file with path '%s'", nss3Path.get()));
    return rv;
  }
  nsCOMPtr<nsIFile> nss3Directory;
  rv = nss3File->GetParent(getter_AddRefs(nss3Directory));
  if (NS_FAILED(rv)) {
    MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("couldn't get parent directory?"));
    return rv;
  }
  return FileToCString(nss3Directory, result);
}

// The loadable roots library is probably in the same directory we loaded the
// NSS shared library from, but in some cases it may be elsewhere. This function
// enumerates and returns the possible locations as nsCStrings.
// |possibleLoadableRootsLocations| is encoded in UTF-8.
static nsresult ListPossibleLoadableRootsLocations(
    Vector<nsCString>& possibleLoadableRootsLocations) {
  MOZ_ASSERT(NS_IsMainThread());
  if (!NS_IsMainThread()) {
    return NS_ERROR_NOT_SAME_THREAD;
  }

  // First try in the directory where we've already loaded
  // MOZ_DLL_PREFIX nss3 MOZ_DLL_SUFFIX, since that's likely to be correct.
  nsAutoCString nss3Dir;
  nsresult rv = GetNSS3Directory(nss3Dir);
  if (NS_SUCCEEDED(rv)) {
    if (!possibleLoadableRootsLocations.append(std::move(nss3Dir))) {
      return NS_ERROR_OUT_OF_MEMORY;
    }
  } else {
    // For some reason this fails on android. In any case, we should try with
    // the other potential locations we have.
    MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
            ("could not determine where nss was loaded from"));
  }
  nsAutoCString currentProcessDir;
  rv = GetDirectoryPath(NS_XPCOM_CURRENT_PROCESS_DIR, currentProcessDir);
  if (NS_SUCCEEDED(rv)) {
    if (!possibleLoadableRootsLocations.append(std::move(currentProcessDir))) {
      return NS_ERROR_OUT_OF_MEMORY;
    }
  } else {
    MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
            ("could not get current process directory"));
  }
  nsAutoCString greDir;
  rv = GetDirectoryPath(NS_GRE_DIR, greDir);
  if (NS_SUCCEEDED(rv)) {
    if (!possibleLoadableRootsLocations.append(std::move(greDir))) {
      return NS_ERROR_OUT_OF_MEMORY;
    }
  } else {
    MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("could not get gre directory"));
  }
  // As a last resort, this will cause the library loading code to use the OS'
  // default library search path.
  nsAutoCString emptyString;
  if (!possibleLoadableRootsLocations.append(std::move(emptyString))) {
    return NS_ERROR_OUT_OF_MEMORY;
  }

  return NS_OK;
}

nsresult LoadLoadableCertsTask::LoadLoadableRoots() {
  for (const auto& possibleLocation : mPossibleLoadableRootsLocations) {
    if (mozilla::psm::LoadLoadableRoots(possibleLocation)) {
      MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
              ("loaded CKBI from %s", possibleLocation.get()));
      return NS_OK;
    }
  }
  MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("could not load loadable roots"));
  return NS_ERROR_FAILURE;
}

// Table of pref names and SSL cipher ID
typedef struct {
  const char* pref;
  int32_t id;
  bool (*prefGetter)();
} CipherPref;

// Update the switch statement in AccumulateCipherSuite in nsNSSCallbacks.cpp
// when you add/remove cipher suites here.
static const CipherPref sCipherPrefs[] = {
    {"security.ssl3.ecdhe_rsa_aes_128_gcm_sha256",
     TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
     StaticPrefs::security_ssl3_ecdhe_rsa_aes_128_gcm_sha256},
    {"security.ssl3.ecdhe_ecdsa_aes_128_gcm_sha256",
     TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
     StaticPrefs::security_ssl3_ecdhe_ecdsa_aes_128_gcm_sha256},
    {"security.ssl3.ecdhe_ecdsa_chacha20_poly1305_sha256",
     TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256,
     StaticPrefs::security_ssl3_ecdhe_ecdsa_chacha20_poly1305_sha256},
    {"security.ssl3.ecdhe_rsa_chacha20_poly1305_sha256",
     TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256,
     StaticPrefs::security_ssl3_ecdhe_rsa_chacha20_poly1305_sha256},
    {"security.ssl3.ecdhe_ecdsa_aes_256_gcm_sha384",
     TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
     StaticPrefs::security_ssl3_ecdhe_ecdsa_aes_256_gcm_sha384},
    {"security.ssl3.ecdhe_rsa_aes_256_gcm_sha384",
     TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
     StaticPrefs::security_ssl3_ecdhe_rsa_aes_256_gcm_sha384},
    {"security.ssl3.ecdhe_rsa_aes_128_sha", TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
     StaticPrefs::security_ssl3_ecdhe_rsa_aes_128_sha},
    {"security.ssl3.ecdhe_ecdsa_aes_128_sha",
     TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,
     StaticPrefs::security_ssl3_ecdhe_ecdsa_aes_128_sha},
    {"security.ssl3.ecdhe_rsa_aes_256_sha", TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
     StaticPrefs::security_ssl3_ecdhe_rsa_aes_256_sha},
    {"security.ssl3.ecdhe_ecdsa_aes_256_sha",
     TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,
     StaticPrefs::security_ssl3_ecdhe_ecdsa_aes_256_sha},
    {"security.ssl3.dhe_rsa_aes_128_sha", TLS_DHE_RSA_WITH_AES_128_CBC_SHA,
     StaticPrefs::security_ssl3_dhe_rsa_aes_128_sha},
    {"security.ssl3.dhe_rsa_aes_256_sha", TLS_DHE_RSA_WITH_AES_256_CBC_SHA,
     StaticPrefs::security_ssl3_dhe_rsa_aes_256_sha},
    {"security.tls13.aes_128_gcm_sha256", TLS_AES_128_GCM_SHA256,
     StaticPrefs::security_tls13_aes_128_gcm_sha256},
    {"security.tls13.chacha20_poly1305_sha256", TLS_CHACHA20_POLY1305_SHA256,
     StaticPrefs::security_tls13_chacha20_poly1305_sha256},
    {"security.tls13.aes_256_gcm_sha384", TLS_AES_256_GCM_SHA384,
     StaticPrefs::security_tls13_aes_256_gcm_sha384},
    {"security.ssl3.rsa_aes_128_gcm_sha256", TLS_RSA_WITH_AES_128_GCM_SHA256,
     StaticPrefs::security_ssl3_rsa_aes_128_gcm_sha256},
    {"security.ssl3.rsa_aes_256_gcm_sha384", TLS_RSA_WITH_AES_256_GCM_SHA384,
     StaticPrefs::security_ssl3_rsa_aes_256_gcm_sha384},
    {"security.ssl3.rsa_aes_128_sha", TLS_RSA_WITH_AES_128_CBC_SHA,
     StaticPrefs::security_ssl3_rsa_aes_128_sha},
    {"security.ssl3.rsa_aes_256_sha", TLS_RSA_WITH_AES_256_CBC_SHA,
     StaticPrefs::security_ssl3_rsa_aes_256_sha},
};

// These ciphersuites can only be enabled if deprecated versions of TLS are
// also enabled (via the preference "security.tls.version.enable-deprecated").
static const CipherPref sDeprecatedTLS1CipherPrefs[] = {
    {"security.ssl3.deprecated.rsa_des_ede3_sha", TLS_RSA_WITH_3DES_EDE_CBC_SHA,
     StaticPrefs::security_ssl3_deprecated_rsa_des_ede3_sha},
};

// This function will convert from pref values like 1, 2, ...
// to the internal values of SSL_LIBRARY_VERSION_TLS_1_0,
// SSL_LIBRARY_VERSION_TLS_1_1, ...
/*static*/
void nsNSSComponent::FillTLSVersionRange(SSLVersionRange& rangeOut,
                                         uint32_t minFromPrefs,
                                         uint32_t maxFromPrefs,
                                         SSLVersionRange defaults) {
  rangeOut = defaults;
  // determine what versions are supported
  SSLVersionRange supported;
  if (SSL_VersionRangeGetSupported(ssl_variant_stream, &supported) !=
      SECSuccess) {
    return;
  }

  // Clip the defaults by what NSS actually supports to enable
  // working with a system NSS with different ranges.
  rangeOut.min = std::max(rangeOut.min, supported.min);
  rangeOut.max = std::min(rangeOut.max, supported.max);

  // convert min/maxFromPrefs to the internal representation
  minFromPrefs += SSL_LIBRARY_VERSION_3_0;
  maxFromPrefs += SSL_LIBRARY_VERSION_3_0;
  // if min/maxFromPrefs are invalid, use defaults
  if (minFromPrefs > maxFromPrefs || minFromPrefs < supported.min ||
      maxFromPrefs > supported.max ||
      minFromPrefs < SSL_LIBRARY_VERSION_TLS_1_0) {
    return;
  }

  // fill out rangeOut
  rangeOut.min = (uint16_t)minFromPrefs;
  rangeOut.max = (uint16_t)maxFromPrefs;
}

static void ConfigureTLSSessionIdentifiers() {
  bool disableSessionIdentifiers =
      StaticPrefs::security_ssl_disable_session_identifiers();
  SSL_OptionSetDefault(SSL_ENABLE_SESSION_TICKETS, !disableSessionIdentifiers);
  SSL_OptionSetDefault(SSL_NO_CACHE, disableSessionIdentifiers);
}

nsresult CommonInit() {
  SSL_OptionSetDefault(SSL_ENABLE_SSL2, false);
  SSL_OptionSetDefault(SSL_V2_COMPATIBLE_HELLO, false);

  nsresult rv = nsNSSComponent::SetEnabledTLSVersions();
  if (NS_FAILED(rv)) {
    return rv;
  }

  ConfigureTLSSessionIdentifiers();

  SSL_OptionSetDefault(SSL_REQUIRE_SAFE_NEGOTIATION,
                       StaticPrefs::security_ssl_require_safe_negotiation());
  SSL_OptionSetDefault(SSL_ENABLE_RENEGOTIATION, SSL_RENEGOTIATE_REQUIRES_XTN);
  SSL_OptionSetDefault(SSL_ENABLE_EXTENDED_MASTER_SECRET, true);
  SSL_OptionSetDefault(SSL_ENABLE_HELLO_DOWNGRADE_CHECK,
                       StaticPrefs::security_tls_hello_downgrade_check());
  SSL_OptionSetDefault(SSL_ENABLE_FALSE_START,
                       StaticPrefs::security_ssl_enable_false_start());
  // SSL_ENABLE_ALPN also requires calling SSL_SetNextProtoNego in order for
  // the extensions to be negotiated.
  // WebRTC does not do that so it will not use ALPN even when this preference
  // is true.
  SSL_OptionSetDefault(SSL_ENABLE_ALPN,
                       StaticPrefs::security_ssl_enable_alpn());
  SSL_OptionSetDefault(SSL_ENABLE_0RTT_DATA,
                       StaticPrefs::security_tls_enable_0rtt_data());
  SSL_OptionSetDefault(SSL_ENABLE_POST_HANDSHAKE_AUTH,
                       StaticPrefs::security_tls_enable_post_handshake_auth());
  SSL_OptionSetDefault(
      SSL_ENABLE_DELEGATED_CREDENTIALS,
      StaticPrefs::security_tls_enable_delegated_credentials());

  rv = InitializeCipherSuite();
  if (NS_FAILED(rv)) {
    MOZ_LOG(gPIPNSSLog, LogLevel::Error,
            ("Unable to initialize cipher suite settings\n"));
    return rv;
  }

  DisableMD5();

  mozilla::pkix::RegisterErrorTable();
  SharedSSLState::GlobalInit();
  SetValidationOptionsCommon();

  return NS_OK;
}

void PrepareForShutdownInSocketProcess() {
  MOZ_ASSERT(XRE_IsSocketProcess());
  SharedSSLState::GlobalCleanup();
}

bool HandleTLSPrefChange(const nsCString& prefName) {
  // Note that the code in this function should be kept in sync with
  // gCallbackSecurityPrefs in nsIOService.cpp.
  bool prefFound = true;
  if (prefName.EqualsLiteral("security.tls.version.min") ||
      prefName.EqualsLiteral("security.tls.version.max") ||
      prefName.EqualsLiteral("security.tls.version.enable-deprecated")) {
    Unused << nsNSSComponent::SetEnabledTLSVersions();
  } else if (prefName.EqualsLiteral("security.tls.hello_downgrade_check")) {
    SSL_OptionSetDefault(SSL_ENABLE_HELLO_DOWNGRADE_CHECK,
                         StaticPrefs::security_tls_hello_downgrade_check());
  } else if (prefName.EqualsLiteral("security.ssl.require_safe_negotiation")) {
    SSL_OptionSetDefault(SSL_REQUIRE_SAFE_NEGOTIATION,
                         StaticPrefs::security_ssl_require_safe_negotiation());
  } else if (prefName.EqualsLiteral("security.ssl.enable_false_start")) {
    SSL_OptionSetDefault(SSL_ENABLE_FALSE_START,
                         StaticPrefs::security_ssl_enable_false_start());
  } else if (prefName.EqualsLiteral("security.ssl.enable_alpn")) {
    SSL_OptionSetDefault(SSL_ENABLE_ALPN,
                         StaticPrefs::security_ssl_enable_alpn());
  } else if (prefName.EqualsLiteral("security.tls.enable_0rtt_data")) {
    SSL_OptionSetDefault(SSL_ENABLE_0RTT_DATA,
                         StaticPrefs::security_tls_enable_0rtt_data());
  } else if (prefName.EqualsLiteral(
                 "security.tls.enable_post_handshake_auth")) {
    SSL_OptionSetDefault(
        SSL_ENABLE_POST_HANDSHAKE_AUTH,
        StaticPrefs::security_tls_enable_post_handshake_auth());
  } else if (prefName.EqualsLiteral(
                 "security.tls.enable_delegated_credentials")) {
    SSL_OptionSetDefault(
        SSL_ENABLE_DELEGATED_CREDENTIALS,
        StaticPrefs::security_tls_enable_delegated_credentials());
  } else if (prefName.EqualsLiteral(
                 "security.ssl.disable_session_identifiers")) {
    ConfigureTLSSessionIdentifiers();
  } else {
    prefFound = false;
  }
  return prefFound;
}

void SetValidationOptionsCommon() {
  // Note that the code in this function should be kept in sync with
  // gCallbackSecurityPrefs in nsIOService.cpp.
  bool ocspStaplingEnabled = StaticPrefs::security_ssl_enable_ocsp_stapling();
  PublicSSLState()->SetOCSPStaplingEnabled(ocspStaplingEnabled);
  PrivateSSLState()->SetOCSPStaplingEnabled(ocspStaplingEnabled);

  bool ocspMustStapleEnabled =
      StaticPrefs::security_ssl_enable_ocsp_must_staple();
  PublicSSLState()->SetOCSPMustStapleEnabled(ocspMustStapleEnabled);
  PrivateSSLState()->SetOCSPMustStapleEnabled(ocspMustStapleEnabled);

  const CertVerifier::CertificateTransparencyMode defaultCTMode =
      CertVerifier::CertificateTransparencyMode::TelemetryOnly;
  CertVerifier::CertificateTransparencyMode ctMode =
      static_cast<CertVerifier::CertificateTransparencyMode>(
          StaticPrefs::security_pki_certificate_transparency_mode());
  switch (ctMode) {
    case CertVerifier::CertificateTransparencyMode::Disabled:
    case CertVerifier::CertificateTransparencyMode::TelemetryOnly:
      break;
    default:
      ctMode = defaultCTMode;
      break;
  }
  bool sctsEnabled =
      ctMode != CertVerifier::CertificateTransparencyMode::Disabled;
  PublicSSLState()->SetSignedCertTimestampsEnabled(sctsEnabled);
  PrivateSSLState()->SetSignedCertTimestampsEnabled(sctsEnabled);
}

namespace {

class CipherSuiteChangeObserver : public nsIObserver {
 public:
  NS_DECL_ISUPPORTS
  NS_DECL_NSIOBSERVER

  static nsresult StartObserve();

 protected:
  virtual ~CipherSuiteChangeObserver() = default;

 private:
  static StaticRefPtr<CipherSuiteChangeObserver> sObserver;
  CipherSuiteChangeObserver() = default;
};

NS_IMPL_ISUPPORTS(CipherSuiteChangeObserver, nsIObserver)

// static
StaticRefPtr<CipherSuiteChangeObserver> CipherSuiteChangeObserver::sObserver;

// static
nsresult CipherSuiteChangeObserver::StartObserve() {
  MOZ_ASSERT(NS_IsMainThread(),
             "CipherSuiteChangeObserver::StartObserve() can only be accessed "
             "on the main thread");
  if (!sObserver) {
    RefPtr<CipherSuiteChangeObserver> observer =
        new CipherSuiteChangeObserver();
    nsresult rv = Preferences::AddStrongObserver(observer.get(), "security.");
    if (NS_FAILED(rv)) {
      sObserver = nullptr;
      return rv;
    }

    nsCOMPtr<nsIObserverService> observerService =
        mozilla::services::GetObserverService();
    observerService->AddObserver(observer, NS_XPCOM_SHUTDOWN_OBSERVER_ID,
                                 false);

    sObserver = observer;
  }
  return NS_OK;
}

// Enables or disabled ciphersuites from deprecated versions of TLS as
// appropriate. If security.tls.version.enable-deprecated is true, these
// ciphersuites may be enabled, if the corresponding preference is true.
// Otherwise, these ciphersuites will be disabled.
void SetDeprecatedTLS1CipherPrefs() {
  if (StaticPrefs::security_tls_version_enable_deprecated()) {
    for (const auto& deprecatedTLS1CipherPref : sDeprecatedTLS1CipherPrefs) {
      SSL_CipherPrefSetDefault(deprecatedTLS1CipherPref.id,
                               deprecatedTLS1CipherPref.prefGetter());
    }
  } else {
    for (const auto& deprecatedTLS1CipherPref : sDeprecatedTLS1CipherPrefs) {
      SSL_CipherPrefSetDefault(deprecatedTLS1CipherPref.id, false);
    }
  }
}

nsresult CipherSuiteChangeObserver::Observe(nsISupports* /*aSubject*/,
                                            const char* aTopic,
                                            const char16_t* someData) {
  MOZ_ASSERT(NS_IsMainThread(),
             "CipherSuiteChangeObserver::Observe can only be accessed on main "
             "thread");
  if (nsCRT::strcmp(aTopic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID) == 0) {
    NS_ConvertUTF16toUTF8 prefName(someData);
    // Look through the cipher table and set according to pref setting
    for (const auto& cipherPref : sCipherPrefs) {
      if (prefName.Equals(cipherPref.pref)) {
        SSL_CipherPrefSetDefault(cipherPref.id, cipherPref.prefGetter());
        break;
      }
    }
    SetDeprecatedTLS1CipherPrefs();
    nsNSSComponent::DoClearSSLExternalAndInternalSessionCache();
  } else if (nsCRT::strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID) == 0) {
    Preferences::RemoveObserver(this, "security.");
    MOZ_ASSERT(sObserver.get() == this);
    sObserver = nullptr;
    nsCOMPtr<nsIObserverService> observerService =
        mozilla::services::GetObserverService();
    observerService->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID);
  }
  return NS_OK;
}

}  // namespace

void nsNSSComponent::setValidationOptions(
    bool isInitialSetting, const mozilla::MutexAutoLock& proofOfLock) {
  // We access prefs so this must be done on the main thread.
  mMutex.AssertCurrentThreadOwns();
  MOZ_ASSERT(NS_IsMainThread());
  if (NS_WARN_IF(!NS_IsMainThread())) {
    return;
  }

  SetValidationOptionsCommon();

  const CertVerifier::CertificateTransparencyMode defaultCTMode =
      CertVerifier::CertificateTransparencyMode::TelemetryOnly;
  CertVerifier::CertificateTransparencyMode ctMode =
      static_cast<CertVerifier::CertificateTransparencyMode>(
          StaticPrefs::security_pki_certificate_transparency_mode());
  switch (ctMode) {
    case CertVerifier::CertificateTransparencyMode::Disabled:
    case CertVerifier::CertificateTransparencyMode::TelemetryOnly:
      break;
    default:
      ctMode = defaultCTMode;
      break;
  }

  // This preference controls whether we do OCSP fetching and does not affect
  // OCSP stapling.
  // 0 = disabled, 1 = enabled, 2 = only enabled for EV
  uint32_t ocspEnabled = StaticPrefs::security_OCSP_enabled();

  bool ocspRequired = ocspEnabled > 0 && StaticPrefs::security_OCSP_require();

  // We measure the setting of the pref at startup only to minimize noise by
  // addons that may muck with the settings, though it probably doesn't matter.
  if (isInitialSetting) {
    Telemetry::Accumulate(Telemetry::CERT_OCSP_ENABLED, ocspEnabled);
    Telemetry::Accumulate(Telemetry::CERT_OCSP_REQUIRED, ocspRequired);
  }

  NetscapeStepUpPolicy netscapeStepUpPolicy = static_cast<NetscapeStepUpPolicy>(
      StaticPrefs::security_pki_netscape_step_up_policy());
  switch (netscapeStepUpPolicy) {
    case NetscapeStepUpPolicy::AlwaysMatch:
    case NetscapeStepUpPolicy::MatchBefore23August2016:
    case NetscapeStepUpPolicy::MatchBefore23August2015:
    case NetscapeStepUpPolicy::NeverMatch:
      break;
    default:
      netscapeStepUpPolicy = NetscapeStepUpPolicy::AlwaysMatch;
      break;
  }

  CRLiteMode defaultCRLiteMode = CRLiteMode::Disabled;
  CRLiteMode crliteMode =
      static_cast<CRLiteMode>(StaticPrefs::security_pki_crlite_mode());
  switch (crliteMode) {
    case CRLiteMode::Disabled:
    case CRLiteMode::TelemetryOnly:
    case CRLiteMode::Enforce:
    case CRLiteMode::ConfirmRevocations:
      break;
    default:
      crliteMode = defaultCRLiteMode;
      break;
  }

  CertVerifier::OcspDownloadConfig odc;
  CertVerifier::OcspStrictConfig osc;
  uint32_t certShortLifetimeInDays;
  TimeDuration softTimeout;
  TimeDuration hardTimeout;

  GetRevocationBehaviorFromPrefs(&odc, &osc, &certShortLifetimeInDays,
                                 softTimeout, hardTimeout);

  mDefaultCertVerifier = new SharedCertVerifier(
      odc, osc, softTimeout, hardTimeout, certShortLifetimeInDays,
      netscapeStepUpPolicy, ctMode, crliteMode, mEnterpriseCerts);
}

void nsNSSComponent::UpdateCertVerifierWithEnterpriseRoots() {
  MutexAutoLock lock(mMutex);
  MOZ_ASSERT(mDefaultCertVerifier);
  if (NS_WARN_IF(!mDefaultCertVerifier)) {
    return;
  }

  RefPtr<SharedCertVerifier> oldCertVerifier = mDefaultCertVerifier;
  mDefaultCertVerifier = new SharedCertVerifier(
      oldCertVerifier->mOCSPDownloadConfig,
      oldCertVerifier->mOCSPStrict ? CertVerifier::ocspStrict
                                   : CertVerifier::ocspRelaxed,
      oldCertVerifier->mOCSPTimeoutSoft, oldCertVerifier->mOCSPTimeoutHard,
      oldCertVerifier->mCertShortLifetimeInDays,
      oldCertVerifier->mNetscapeStepUpPolicy, oldCertVerifier->mCTMode,
      oldCertVerifier->mCRLiteMode, mEnterpriseCerts);
}

// Enable the TLS versions given in the prefs, defaulting to TLS 1.0 (min) and
// TLS 1.2 (max) when the prefs aren't set or set to invalid values.
nsresult nsNSSComponent::SetEnabledTLSVersions() {
  // Keep these values in sync with all.js.
  // 1 means TLS 1.0, 2 means TLS 1.1, etc.
  static const uint32_t PSM_DEFAULT_MIN_TLS_VERSION = 3;
  static const uint32_t PSM_DEFAULT_MAX_TLS_VERSION = 4;
  static const uint32_t PSM_DEPRECATED_TLS_VERSION = 1;

  uint32_t minFromPrefs = StaticPrefs::security_tls_version_min();
  uint32_t maxFromPrefs = StaticPrefs::security_tls_version_max();

  // This override should be removed some time after
  // PSM_DEFAULT_MIN_TLS_VERSION is increased to 3.
  bool enableDeprecated = StaticPrefs::security_tls_version_enable_deprecated();
  if (enableDeprecated) {
    minFromPrefs = std::min(minFromPrefs, PSM_DEPRECATED_TLS_VERSION);
  }

  SSLVersionRange defaults = {
      SSL_LIBRARY_VERSION_3_0 + PSM_DEFAULT_MIN_TLS_VERSION,
      SSL_LIBRARY_VERSION_3_0 + PSM_DEFAULT_MAX_TLS_VERSION};
  SSLVersionRange filledInRange;
  FillTLSVersionRange(filledInRange, minFromPrefs, maxFromPrefs, defaults);

  SECStatus srv =
      SSL_VersionRangeSetDefault(ssl_variant_stream, &filledInRange);
  if (srv != SECSuccess) {
    return NS_ERROR_FAILURE;
  }

  return NS_OK;
}

#if defined(XP_WIN) || (defined(XP_LINUX) && !defined(ANDROID))
// If the profile directory is on a networked drive, we want to set the
// environment variable NSS_SDB_USE_CACHE to yes (as long as it hasn't been set
// before).
static void SetNSSDatabaseCacheModeAsAppropriate() {
  MOZ_ASSERT(NS_IsMainThread());

  nsCOMPtr<nsIFile> profileFile;
  nsresult rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
                                       getter_AddRefs(profileFile));
  if (NS_FAILED(rv)) {
    // We're probably running without a profile directory, so this is
    // irrelevant.
    return;
  }

  static const char sNSS_SDB_USE_CACHE[] = "NSS_SDB_USE_CACHE";
  static const char sNSS_SDB_USE_CACHE_WITH_VALUE[] = "NSS_SDB_USE_CACHE=yes";
  auto profilePath = profileFile->NativePath();

#  if defined(XP_LINUX) && !defined(ANDROID)
  struct statfs statfs_s;
  if (statfs(profilePath.get(), &statfs_s) == 0 &&
      statfs_s.f_type == NFS_SUPER_MAGIC && !PR_GetEnv(sNSS_SDB_USE_CACHE)) {
    MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
            ("profile is remote (and NSS_SDB_USE_CACHE wasn't set): "
             "setting NSS_SDB_USE_CACHE"));
    PR_SetEnv(sNSS_SDB_USE_CACHE_WITH_VALUE);
  } else {
    MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("not setting NSS_SDB_USE_CACHE"));
  }
#  endif  // defined(XP_LINUX) && !defined(ANDROID)

#  ifdef XP_WIN
  wchar_t volPath[MAX_PATH];
  if (::GetVolumePathNameW(profilePath.get(), volPath, MAX_PATH) &&
      ::GetDriveTypeW(volPath) == DRIVE_REMOTE &&
      !PR_GetEnv(sNSS_SDB_USE_CACHE)) {
    MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
            ("profile is remote (and NSS_SDB_USE_CACHE wasn't set): "
             "setting NSS_SDB_USE_CACHE"));
    PR_SetEnv(sNSS_SDB_USE_CACHE_WITH_VALUE);
  } else {
    MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("not setting NSS_SDB_USE_CACHE"));
  }
#  endif  // XP_WIN
}
#endif  // defined(XP_WIN) || (defined(XP_LINUX) && !defined(ANDROID))

static nsresult GetNSSProfilePath(nsAutoCString& aProfilePath) {
  aProfilePath.Truncate();
  nsCOMPtr<nsIFile> profileFile;
  nsresult rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
                                       getter_AddRefs(profileFile));
  if (NS_FAILED(rv)) {
    NS_WARNING(
        "NSS will be initialized without a profile directory. "
        "Some things may not work as expected.");
    return NS_OK;
  }

#if defined(XP_WIN)
  // SQLite always takes UTF-8 file paths regardless of the current system
  // code page.
  nsCOMPtr<nsILocalFileWin> profileFileWin(do_QueryInterface(profileFile));
  if (!profileFileWin) {
    MOZ_LOG(gPIPNSSLog, LogLevel::Error,
            ("Could not get nsILocalFileWin for profile directory.\n"));
    return NS_ERROR_FAILURE;
  }
  nsAutoString u16ProfilePath;
  rv = profileFileWin->GetPath(u16ProfilePath);
  CopyUTF16toUTF8(u16ProfilePath, aProfilePath);
#else
  rv = profileFile->GetNativePath(aProfilePath);
#endif
  if (NS_FAILED(rv)) {
    MOZ_LOG(gPIPNSSLog, LogLevel::Error,
            ("Could not get native path for profile directory.\n"));
    return rv;
  }

  MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
          ("NSS profile at '%s'\n", aProfilePath.get()));
  return NS_OK;
}

#ifndef ANDROID
// Given a profile path, attempt to rename the PKCS#11 module DB to
// "pkcs11.txt.fips". In the case of a catastrophic failure (e.g. out of
// memory), returns a failing nsresult. If execution could conceivably proceed,
// returns NS_OK even if renaming the file didn't work. This simplifies the
// logic of the calling code.
// |profilePath| is encoded in UTF-8.
static nsresult AttemptToRenamePKCS11ModuleDB(const nsACString& profilePath) {
  nsCOMPtr<nsIFile> profileDir = do_CreateInstance("@mozilla.org/file/local;1");
  if (!profileDir) {
    return NS_ERROR_FAILURE;
  }
#  ifdef XP_WIN
  // |profilePath| is encoded in UTF-8 because SQLite always takes UTF-8 file
  // paths regardless of the current system code page.
  nsresult rv = profileDir->InitWithPath(NS_ConvertUTF8toUTF16(profilePath));
#  else
  nsresult rv = profileDir->InitWithNativePath(profilePath);
#  endif
  if (NS_FAILED(rv)) {
    return rv;
  }
  const char* moduleDBFilename = "pkcs11.txt";
  nsAutoCString destModuleDBFilename(moduleDBFilename);
  destModuleDBFilename.Append(".fips");
  nsCOMPtr<nsIFile> dbFile;
  rv = profileDir->Clone(getter_AddRefs(dbFile));
  if (NS_FAILED(rv) || !dbFile) {
    return NS_ERROR_FAILURE;
  }
  rv = dbFile->AppendNative(nsAutoCString(moduleDBFilename));
  if (NS_FAILED(rv)) {
    return rv;
  }
  // If the PKCS#11 module DB doesn't exist, renaming it won't help.
  bool exists;
  rv = dbFile->Exists(&exists);
  if (NS_FAILED(rv)) {
    return rv;
  }
  // This is strange, but not a catastrophic failure.
  if (!exists) {
    MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
            ("%s doesn't exist?", moduleDBFilename));
    return NS_OK;
  }
  nsCOMPtr<nsIFile> destDBFile;
  rv = profileDir->Clone(getter_AddRefs(destDBFile));
  if (NS_FAILED(rv) || !destDBFile) {
    return NS_ERROR_FAILURE;
  }
  rv = destDBFile->AppendNative(destModuleDBFilename);
  if (NS_FAILED(rv)) {
    return rv;
  }
  // If the destination exists, presumably we've already tried this. Doing it
  // again won't help.
  rv = destDBFile->Exists(&exists);
  if (NS_FAILED(rv)) {
    return rv;
  }
  // Unfortunate, but not a catastrophic failure.
  if (exists) {
    MOZ_LOG(
        gPIPNSSLog, LogLevel::Debug,
        ("%s already exists - not overwriting", destModuleDBFilename.get()));
    return NS_OK;
  }
  // Now do the actual move.
  // This may fail on, e.g., a read-only file system. This would be unfortunate,
  // but again it isn't catastropic and we would want to fall back to
  // initializing NSS in no-DB mode.
  Unused << dbFile->MoveToNative(profileDir, destModuleDBFilename);
  return NS_OK;
}
#endif  // ifndef ANDROID

// Given a profile directory, attempt to initialize NSS. If nocertdb is true,
// (or if we don't have a profile directory) simply initialize NSS in no DB mode
// and return. Otherwise, first attempt to initialize in read/write mode, and
// then read-only mode if that fails. If both attempts fail, we may be failing
// to initialize an NSS DB collection that has FIPS mode enabled. Attempt to
// ascertain if this is the case, and if so, rename the offending PKCS#11 module
// DB so we can (hopefully) initialize NSS in read-write mode. Again attempt
// read-only mode if that fails. Finally, fall back to no DB mode. On Android
// we can skip the FIPS workaround since it was never possible to enable FIPS
// there anyway.
// |profilePath| is encoded in UTF-8.
static nsresult InitializeNSSWithFallbacks(const nsACString& profilePath,
                                           bool nocertdb, bool safeMode) {
  if (nocertdb || profilePath.IsEmpty()) {
    MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
            ("nocertdb mode or empty profile path -> NSS_NoDB_Init"));
    SECStatus srv = NSS_NoDB_Init(nullptr);
#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
    if (srv != SECSuccess) {
      MOZ_CRASH_UNSAFE_PRINTF("InitializeNSSWithFallbacks failed: %d",
                              PR_GetError());
    }
#endif
    return srv == SECSuccess ? NS_OK : NS_ERROR_FAILURE;
  }

  // Try read/write mode. If we're in safeMode, we won't load PKCS#11 modules.
#ifndef ANDROID
  PRErrorCode savedPRErrorCode1;
#endif  // ifndef ANDROID
  PKCS11DBConfig safeModeDBConfig =
      safeMode ? PKCS11DBConfig::DoNotLoadModules : PKCS11DBConfig::LoadModules;
  SECStatus srv = ::mozilla::psm::InitializeNSS(
      profilePath, NSSDBConfig::ReadWrite, safeModeDBConfig);
  if (srv == SECSuccess) {
    MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("initialized NSS in r/w mode"));
    return NS_OK;
  }
#ifndef ANDROID
  savedPRErrorCode1 = PR_GetError();
  PRErrorCode savedPRErrorCode2;
#endif  // ifndef ANDROID
  // That failed. Try read-only mode.
  srv = ::mozilla::psm::InitializeNSS(profilePath, NSSDBConfig::ReadOnly,
                                      safeModeDBConfig);
  if (srv == SECSuccess) {
    MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("initialized NSS in r-o mode"));
    return NS_OK;
  }
#ifndef ANDROID
  savedPRErrorCode2 = PR_GetError();

  MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
          ("failed to initialize NSS with codes %d %d", savedPRErrorCode1,
           savedPRErrorCode2));
#endif  // ifndef ANDROID

#ifndef ANDROID
  // That failed as well. Maybe we're trying to load a PKCS#11 module DB that is
  // in FIPS mode, but we don't support FIPS? Test load NSS without PKCS#11
  // modules. If that succeeds, that's probably what's going on.
  if (!safeMode && (savedPRErrorCode1 == SEC_ERROR_LEGACY_DATABASE ||
                    savedPRErrorCode2 == SEC_ERROR_LEGACY_DATABASE ||
                    savedPRErrorCode1 == SEC_ERROR_PKCS11_DEVICE_ERROR ||
                    savedPRErrorCode2 == SEC_ERROR_PKCS11_DEVICE_ERROR)) {
    MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("attempting no-module db init"));
    // It would make sense to initialize NSS in read-only mode here since this
    // is just a test to see if the PKCS#11 module DB being in FIPS mode is the
    // problem, but for some reason the combination of read-only and no-moddb
    // flags causes NSS initialization to fail, so unfortunately we have to use
    // read-write mode.
    srv = ::mozilla::psm::InitializeNSS(profilePath, NSSDBConfig::ReadWrite,
                                        PKCS11DBConfig::DoNotLoadModules);
    if (srv == SECSuccess) {
      MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("FIPS may be the problem"));
      // Unload NSS so we can attempt to fix this situation for the user.
      srv = NSS_Shutdown();
      if (srv != SECSuccess) {
#  ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
        MOZ_CRASH_UNSAFE_PRINTF("InitializeNSSWithFallbacks failed: %d",
                                PR_GetError());
#  endif
        return NS_ERROR_FAILURE;
      }
      MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("trying to rename module db"));
      // If this fails non-catastrophically, we'll attempt to initialize NSS
      // again in r/w then r-o mode (both of which will fail), and then we'll
      // fall back to NSS_NoDB_Init, which is the behavior we want.
      nsresult rv = AttemptToRenamePKCS11ModuleDB(profilePath);
      if (NS_FAILED(rv)) {
#  ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
        // An nsresult is a uint32_t, but at least one of our compilers doesn't
        // like this format string unless we include the cast. <shruggie emoji>
        MOZ_CRASH_UNSAFE_PRINTF("InitializeNSSWithFallbacks failed: %u",
                                (uint32_t)rv);
#  endif
        return rv;
      }
      srv = ::mozilla::psm::InitializeNSS(profilePath, NSSDBConfig::ReadWrite,
                                          PKCS11DBConfig::LoadModules);
      if (srv == SECSuccess) {
        MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("initialized in r/w mode"));
        return NS_OK;
      }
      srv = ::mozilla::psm::InitializeNSS(profilePath, NSSDBConfig::ReadOnly,
                                          PKCS11DBConfig::LoadModules);
      if (srv == SECSuccess) {
        MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("initialized in r-o mode"));
        return NS_OK;
      }
    }
  }
#endif

  MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("last-resort NSS_NoDB_Init"));
  srv = NSS_NoDB_Init(nullptr);
#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
  if (srv != SECSuccess) {
    MOZ_CRASH_UNSAFE_PRINTF("InitializeNSSWithFallbacks failed: %d",
                            PR_GetError());
  }
#endif
  return srv == SECSuccess ? NS_OK : NS_ERROR_FAILURE;
}

#if defined(NIGHTLY_BUILD) && !defined(ANDROID)
// dbType is either "cert9.db" or "key4.db"
void UnmigrateOneCertDB(const nsCOMPtr<nsIFile>& profileDirectory,
                        const nsACString& dbType) {
  nsCOMPtr<nsIFile> dbFile;
  nsresult rv = profileDirectory->Clone(getter_AddRefs(dbFile));
  if (NS_FAILED(rv)) {
    return;
  }
  rv = dbFile->AppendNative(dbType);
  if (NS_FAILED(rv)) {
    return;
  }
  bool exists;
  rv = dbFile->Exists(&exists);
  if (NS_FAILED(rv)) {
    return;
  }
  // If the unprefixed DB already exists, don't overwrite it.
  if (exists) {
    return;
  }
  nsCOMPtr<nsIFile> prefixedDBFile;
  rv = profileDirectory->Clone(getter_AddRefs(prefixedDBFile));
  if (NS_FAILED(rv)) {
    return;
  }
  nsAutoCString prefixedDBName("gecko-no-share-");
  prefixedDBName.Append(dbType);
  rv = prefixedDBFile->AppendNative(prefixedDBName);
  if (NS_FAILED(rv)) {
    return;
  }
  Unused << prefixedDBFile->MoveToNative(nullptr, dbType);
}

void UnmigrateFromPrefixedCertDBs() {
  nsCOMPtr<nsIFile> profileDirectory;
  nsresult rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
                                       getter_AddRefs(profileDirectory));
  if (NS_FAILED(rv)) {
    return;
  }
  UnmigrateOneCertDB(profileDirectory, "cert9.db"_ns);
  UnmigrateOneCertDB(profileDirectory, "key4.db"_ns);
}
#endif  // defined(NIGHTLY_BUILD) && !defined(ANDROID)

nsresult nsNSSComponent::InitializeNSS() {
  MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsNSSComponent::InitializeNSS\n"));
  AUTO_PROFILER_LABEL("nsNSSComponent::InitializeNSS", OTHER);
  AUTO_PROFILER_TRACING_MARKER("NSS", "nsNSSComponent::InitializeNSS", OTHER);

  static_assert(
      nsINSSErrorsService::NSS_SEC_ERROR_BASE == SEC_ERROR_BASE &&
          nsINSSErrorsService::NSS_SEC_ERROR_LIMIT == SEC_ERROR_LIMIT &&
          nsINSSErrorsService::NSS_SSL_ERROR_BASE == SSL_ERROR_BASE &&
          nsINSSErrorsService::NSS_SSL_ERROR_LIMIT == SSL_ERROR_LIMIT,
      "You must update the values in nsINSSErrorsService.idl");

  MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("NSS Initialization beginning\n"));

  nsAutoCString profileStr;
  nsresult rv = GetNSSProfilePath(profileStr);
  MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv));
  if (NS_FAILED(rv)) {
    return NS_ERROR_NOT_AVAILABLE;
  }

#if defined(NIGHTLY_BUILD) && !defined(ANDROID)
  if (!profileStr.IsEmpty()) {
    UnmigrateFromPrefixedCertDBs();
  }
#endif

#if defined(XP_WIN) || (defined(XP_LINUX) && !defined(ANDROID))
  SetNSSDatabaseCacheModeAsAppropriate();
#endif

  bool nocertdb = StaticPrefs::security_nocertdb_AtStartup();
  bool inSafeMode = true;
  nsCOMPtr<nsIXULRuntime> runtime(do_GetService("@mozilla.org/xre/runtime;1"));
  // There might not be an nsIXULRuntime in embedded situations. This will
  // default to assuming we are in safe mode (as a result, no external PKCS11
  // modules will be loaded).
  if (runtime) {
    rv = runtime->GetInSafeMode(&inSafeMode);
    MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv));
    if (NS_FAILED(rv)) {
      return rv;
    }
  }
  MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("inSafeMode: %u\n", inSafeMode));

  rv = InitializeNSSWithFallbacks(profileStr, nocertdb, inSafeMode);
  MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv));
  if (NS_FAILED(rv)) {
    MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("failed to initialize NSS"));
    return rv;
  }

  PK11_SetPasswordFunc(PK11PasswordPrompt);

  // Register an observer so we can inform NSS when these prefs change
  Preferences::AddStrongObserver(this, "security.");

  rv = CommonInit();

  MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv));
  if (NS_FAILED(rv)) {
    return NS_ERROR_UNEXPECTED;
  }

  nsCOMPtr<nsICertOverrideService> certOverrideService(
      do_GetService(NS_CERTOVERRIDE_CONTRACTID));
  nsCOMPtr<nsIClientAuthRememberService> clientAuthRememberService(
      do_GetService(NS_CLIENTAUTHREMEMBERSERVICE_CONTRACTID));
  nsCOMPtr<nsISiteSecurityService> siteSecurityService(
      do_GetService(NS_SSSERVICE_CONTRACTID));
  nsCOMPtr<nsICertStorage> certStorage(do_GetService(NS_CERT_STORAGE_CID));

  MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("NSS Initialization done\n"));

  {
    MutexAutoLock lock(mMutex);

    // ensure we have initial values for various root hashes
#ifdef DEBUG
    mTestBuiltInRootHash.Truncate();
    Preferences::GetCString("security.test.built_in_root_hash",
                            mTestBuiltInRootHash);
#endif
    mMitmCanaryIssuer.Truncate();
    Preferences::GetString("security.pki.mitm_canary_issuer",
                           mMitmCanaryIssuer);
    mMitmDetecionEnabled =
        Preferences::GetBool("security.pki.mitm_canary_issuer.enabled", true);

    // Set dynamic options from prefs.
    setValidationOptions(true, lock);

    bool importEnterpriseRoots =
        StaticPrefs::security_enterprise_roots_enabled();
    uint32_t familySafetyMode = StaticPrefs::security_family_safety_mode();
    Vector<nsCString> possibleLoadableRootsLocations;
    rv = ListPossibleLoadableRootsLocations(possibleLoadableRootsLocations);
    MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv));
    if (NS_FAILED(rv)) {
      return rv;
    }

    bool loadOSClientCertsModule =
        StaticPrefs::security_osclientcerts_autoload();
    Maybe<nsCString> maybeOSClientCertsModuleLocation;
    if (loadOSClientCertsModule) {
      nsAutoCString libraryDir;
      if (NS_SUCCEEDED(GetDirectoryPath(NS_GRE_BIN_DIR, libraryDir))) {
        maybeOSClientCertsModuleLocation.emplace(libraryDir);
      }
    }
    RefPtr<LoadLoadableCertsTask> loadLoadableCertsTask(
        new LoadLoadableCertsTask(this, importEnterpriseRoots, familySafetyMode,
                                  std::move(possibleLoadableRootsLocations),
                                  std::move(maybeOSClientCertsModuleLocation)));
    rv = loadLoadableCertsTask->Dispatch();
    MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv));
    if (NS_FAILED(rv)) {
      return rv;
    }

    return NS_OK;
  }
}

void nsNSSComponent::PrepareForShutdown() {
  MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsNSSComponent::PrepareForShutdown"));
  MOZ_RELEASE_ASSERT(NS_IsMainThread());

  PK11_SetPasswordFunc((PK11PasswordFunc) nullptr);

  Preferences::RemoveObserver(this, "security.");

  if (mIntermediatePreloadingHealerTimer) {
    mIntermediatePreloadingHealerTimer->Cancel();
    mIntermediatePreloadingHealerTimer = nullptr;
  }

  // Release the default CertVerifier. This will cause any held NSS resources
  // to be released.
  MutexAutoLock lock(mMutex);
  mDefaultCertVerifier = nullptr;
  // We don't actually shut down NSS - XPCOM does, after all threads have been
  // joined and the component manager has been shut down (and so there shouldn't
  // be any XPCOM objects holding NSS resources).
}

// The aim of the intermediate preloading healer is to remove intermediates
// that were previously cached by PSM in the NSS certdb that are now preloaded
// in cert_storage. When cached by PSM, these certificates will have no
// particular trust set - they are intended to inherit their trust. If, upon
// examination, these certificates do have trust bits set that affect
// certificate validation, they must have been modified by the user, so we want
// to leave them alone.
bool CertHasDefaultTrust(CERTCertificate* cert) {
  CERTCertTrust trust;
  if (CERT_GetCertTrust(cert, &trust) != SECSuccess) {
    MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("CERT_GetCertTrust failed"));
    return false;
  }
  // This is the active distrust test for CA certificates (this is expected to
  // be an intermediate).
  if ((trust.sslFlags & (CERTDB_TRUSTED_CA | CERTDB_TERMINAL_RECORD)) ==
      CERTDB_TERMINAL_RECORD) {
    return false;
  }
  // This is the trust anchor test.
  if (trust.sslFlags & CERTDB_TRUSTED_CA) {
    return false;
  }
  // This is the active distrust test for CA certificates (this is expected to
  // be an intermediate).
  if ((trust.emailFlags & (CERTDB_TRUSTED_CA | CERTDB_TERMINAL_RECORD)) ==
      CERTDB_TERMINAL_RECORD) {
    return false;
  }
  // This is the trust anchor test.
  if (trust.emailFlags & CERTDB_TRUSTED_CA) {
    return false;
  }
  return true;
}

void IntermediatePreloadingHealerCallback(nsITimer*, void*) {
  MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
          ("IntermediatePreloadingHealerCallback"));

  if (AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdownConfirmed)) {
    MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
            ("Exiting healer due to app shutdown"));
    return;
  }

  // Get the slot corresponding to the NSS certdb.
  UniquePK11SlotInfo softokenSlot(PK11_GetInternalKeySlot());
  if (!softokenSlot) {
    MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("PK11_GetInternalKeySlot failed"));
    return;
  }
  // List the certificates in the NSS certdb.
  UniqueCERTCertList softokenCertificates(
      PK11_ListCertsInSlot(softokenSlot.get()));
  if (!softokenCertificates) {
    MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("PK11_ListCertsInSlot failed"));
    return;
  }
  nsCOMPtr<nsICertStorage> certStorage(do_GetService(NS_CERT_STORAGE_CID));
  if (!certStorage) {
    MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("couldn't get cert_storage"));
    return;
  }
  Vector<UniqueCERTCertificate> certsToDelete;
  // For each certificate, look it up in cert_storage. If there's a match, this
  // is a preloaded intermediate.
  for (CERTCertListNode* n = CERT_LIST_HEAD(softokenCertificates);
       !CERT_LIST_END(n, softokenCertificates); n = CERT_LIST_NEXT(n)) {
    if (AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdownConfirmed)) {
      MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
              ("Exiting healer due to app shutdown"));
      return;
    }

    nsTArray<uint8_t> subject;
    subject.AppendElements(n->cert->derSubject.data, n->cert->derSubject.len);
    nsTArray<nsTArray<uint8_t>> certs;
    nsresult rv = certStorage->FindCertsBySubject(subject, certs);
    if (NS_FAILED(rv)) {
      MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("FindCertsBySubject failed"));
      break;
    }
    for (const auto& encodedCert : certs) {
      if (encodedCert.Length() != n->cert->derCert.len) {
        continue;
      }
      if (memcmp(encodedCert.Elements(), n->cert->derCert.data,
                 encodedCert.Length()) != 0) {
        continue;
      }
      MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
              ("found preloaded intermediate in certdb"));
      if (!CertHasDefaultTrust(n->cert)) {
        MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
                ("certificate doesn't have default trust - skipping"));
        continue;
      }
      UniqueCERTCertificate certCopy(CERT_DupCertificate(n->cert));
      if (!certCopy) {
        MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("CERT_DupCertificate failed"));
        continue;
      }
      // Note that we want to remove this certificate from the NSS certdb
      // because it also exists in preloaded intermediate storage and is thus
      // superfluous.
      if (!certsToDelete.append(std::move(certCopy))) {
        MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
                ("append failed - out of memory?"));
        return;
      }
      break;
    }
    // Only delete 20 at a time.
    if (certsToDelete.length() >= 20) {
      MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
              ("found limit of 20 preloaded intermediates in certdb"));
      break;
    }
  }
  for (const auto& certToDelete : certsToDelete) {
    if (AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdownConfirmed)) {
      MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
              ("Exiting healer due to app shutdown"));
      return;
    }
    MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
            ("attempting to delete preloaded intermediate '%s'",
             certToDelete->subjectName));
    if (SEC_DeletePermCertificate(certToDelete.get()) != SECSuccess) {
      MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
              ("SEC_DeletePermCertificate failed"));
    }
  }

  // This is for tests - notify that this ran.
  nsCOMPtr<nsIRunnable> runnable(NS_NewRunnableFunction(
      "IntermediatePreloadingHealerCallbackDone", []() -> void {
        nsCOMPtr<nsIObserverService> observerService =
            mozilla::services::GetObserverService();
        if (observerService) {
          observerService->NotifyObservers(
              nullptr, "psm:intermediate-preloading-healer-ran", nullptr);
        }
      }));
  Unused << NS_DispatchToMainThread(runnable.forget());
}

nsresult nsNSSComponent::Init() {
  MOZ_RELEASE_ASSERT(NS_IsMainThread());
  if (!NS_IsMainThread()) {
    return NS_ERROR_NOT_SAME_THREAD;
  }

  MOZ_ASSERT(XRE_IsParentProcess());
  if (!XRE_IsParentProcess()) {
    return NS_ERROR_NOT_AVAILABLE;
  }

  Telemetry::AutoScalarTimer<Telemetry::ScalarID::NETWORKING_NSS_INITIALIZATION>
      timer;
  uint32_t zero = 0;  // Directly using 0 makes the call to ScalarSet ambiguous.
  Telemetry::ScalarSet(Telemetry::ScalarID::SECURITY_CLIENT_AUTH_CERT_USAGE,
                       u"requested"_ns, zero);
  Telemetry::ScalarSet(Telemetry::ScalarID::SECURITY_CLIENT_AUTH_CERT_USAGE,
                       u"sent"_ns, zero);

  MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("Beginning NSS initialization\n"));

  nsresult rv = InitializeNSS();
  if (NS_FAILED(rv)) {
    MOZ_LOG(gPIPNSSLog, LogLevel::Error,
            ("nsNSSComponent::InitializeNSS() failed\n"));
    return rv;
  }

  rv = RegisterObservers();
  if (NS_FAILED(rv)) {
    return rv;
  }

  rv = MaybeEnableIntermediatePreloadingHealer();
  if (NS_FAILED(rv)) {
    return rv;
  }

  return NS_OK;
}

nsresult nsNSSComponent::MaybeEnableIntermediatePreloadingHealer() {
  MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
          ("nsNSSComponent::MaybeEnableIntermediatePreloadingHealer"));
  MOZ_ASSERT(NS_IsMainThread());
  if (!NS_IsMainThread()) {
    return NS_ERROR_NOT_SAME_THREAD;
  }

  if (mIntermediatePreloadingHealerTimer) {
    mIntermediatePreloadingHealerTimer->Cancel();
    mIntermediatePreloadingHealerTimer = nullptr;
  }

  if (!StaticPrefs::security_intermediate_preloading_healer_enabled()) {
    return NS_OK;
  }

  if (!mIntermediatePreloadingHealerTaskQueue) {
    nsresult rv = NS_CreateBackgroundTaskQueue(
        "IntermediatePreloadingHealer",
        getter_AddRefs(mIntermediatePreloadingHealerTaskQueue));
    if (NS_FAILED(rv)) {
      MOZ_LOG(gPIPNSSLog, LogLevel::Error,
              ("NS_CreateBackgroundTaskQueue failed"));
      return rv;
    }
  }
  uint32_t timerDelayMS =
      StaticPrefs::security_intermediate_preloading_healer_timer_interval_ms();
  nsresult rv = NS_NewTimerWithFuncCallback(
      getter_AddRefs(mIntermediatePreloadingHealerTimer),
      IntermediatePreloadingHealerCallback, nullptr, timerDelayMS,
      nsITimer::TYPE_REPEATING_SLACK_LOW_PRIORITY,
      "IntermediatePreloadingHealer", mIntermediatePreloadingHealerTaskQueue);
  if (NS_FAILED(rv)) {
    MOZ_LOG(gPIPNSSLog, LogLevel::Error,
            ("NS_NewTimerWithFuncCallback failed"));
    return rv;
  }
  return NS_OK;
}

// nsISupports Implementation for the class
NS_IMPL_ISUPPORTS(nsNSSComponent, nsINSSComponent, nsIObserver)

static const char* const PROFILE_BEFORE_CHANGE_TOPIC = "profile-before-change";

NS_IMETHODIMP
nsNSSComponent::Observe(nsISupports* aSubject, const char* aTopic,
                        const char16_t* someData) {
  // In some tests, we don't receive a "profile-before-change" topic. However,
  // we still have to shut down before the storage service shuts down, because
  // closing the sql-backed softoken requires sqlite still be available. Thus,
  // we observe "xpcom-shutdown" just in case.
  if (nsCRT::strcmp(aTopic, PROFILE_BEFORE_CHANGE_TOPIC) == 0 ||
      nsCRT::strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID) == 0) {
    MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
            ("receiving profile change or XPCOM shutdown notification"));
    PrepareForShutdown();
  } else if (nsCRT::strcmp(aTopic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID) == 0) {
    bool clearSessionCache = true;
    NS_ConvertUTF16toUTF8 prefName(someData);

    if (HandleTLSPrefChange(prefName)) {
      MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("HandleTLSPrefChange done"));
    } else if (prefName.EqualsLiteral("security.OCSP.enabled") ||
               prefName.EqualsLiteral("security.OCSP.require") ||
               prefName.EqualsLiteral(
                   "security.pki.cert_short_lifetime_in_days") ||
               prefName.EqualsLiteral("security.ssl.enable_ocsp_stapling") ||
               prefName.EqualsLiteral("security.ssl.enable_ocsp_must_staple") ||
               prefName.EqualsLiteral(
                   "security.pki.certificate_transparency.mode") ||
               prefName.EqualsLiteral("security.pki.netscape_step_up_policy") ||
               prefName.EqualsLiteral(
                   "security.OCSP.timeoutMilliseconds.soft") ||
               prefName.EqualsLiteral(
                   "security.OCSP.timeoutMilliseconds.hard") ||
               prefName.EqualsLiteral("security.pki.crlite_mode")) {
      MutexAutoLock lock(mMutex);
      setValidationOptions(false, lock);
#ifdef DEBUG
    } else if (prefName.EqualsLiteral("security.test.built_in_root_hash")) {
      MutexAutoLock lock(mMutex);
      mTestBuiltInRootHash.Truncate();
      Preferences::GetCString("security.test.built_in_root_hash",
                              mTestBuiltInRootHash);
#endif  // DEBUG
    } else if (prefName.Equals("security.enterprise_roots.enabled") ||
               prefName.Equals("security.family_safety.mode")) {
      UnloadEnterpriseRoots();
      MaybeImportEnterpriseRoots();
    } else if (prefName.Equals("security.osclientcerts.autoload")) {
      bool loadOSClientCertsModule =
          StaticPrefs::security_osclientcerts_autoload();
      AsyncLoadOrUnloadOSClientCertsModule(loadOSClientCertsModule);
    } else if (prefName.EqualsLiteral("security.pki.mitm_canary_issuer")) {
      MutexAutoLock lock(mMutex);
      mMitmCanaryIssuer.Truncate();
      Preferences::GetString("security.pki.mitm_canary_issuer",
                             mMitmCanaryIssuer);
    } else if (prefName.EqualsLiteral(
                   "security.pki.mitm_canary_issuer.enabled")) {
      MutexAutoLock lock(mMutex);
      mMitmDetecionEnabled =
          Preferences::GetBool("security.pki.mitm_canary_issuer.enabled", true);
    } else {
      clearSessionCache = false;
    }
    if (clearSessionCache) {
      ClearSSLExternalAndInternalSessionCache();
    }

    // Preferences that don't affect certificate verification.
    if (prefName.Equals("security.intermediate_preloading_healer.enabled") ||
        prefName.Equals(
            "security.intermediate_preloading_healer.timer_interval_ms")) {
      MaybeEnableIntermediatePreloadingHealer();
    }
  }

  return NS_OK;
}

/*static*/
nsresult nsNSSComponent::GetNewPrompter(nsIPrompt** result) {
  NS_ENSURE_ARG_POINTER(result);
  *result = nullptr;

  if (!NS_IsMainThread()) {
    NS_ERROR("nsSDRContext::GetNewPrompter called off the main thread");
    return NS_ERROR_NOT_SAME_THREAD;
  }

  nsresult rv;
  nsCOMPtr<nsIWindowWatcher> wwatch(
      do_GetService(NS_WINDOWWATCHER_CONTRACTID, &rv));
  NS_ENSURE_SUCCESS(rv, rv);

  rv = wwatch->GetNewPrompter(0, result);
  NS_ENSURE_SUCCESS(rv, rv);

  return rv;
}

nsresult nsNSSComponent::LogoutAuthenticatedPK11() {
  nsCOMPtr<nsICertOverrideService> icos =
      do_GetService("@mozilla.org/security/certoverride;1");
  if (icos) {
    icos->ClearValidityOverride("all:temporary-certificates"_ns, 0,
                                OriginAttributes());
  }

  ClearSSLExternalAndInternalSessionCache();

  nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
  if (os) {
    os->NotifyObservers(nullptr, "net:cancel-all-connections", nullptr);
  }

  return NS_OK;
}

nsresult nsNSSComponent::RegisterObservers() {
  nsCOMPtr<nsIObserverService> observerService(
      do_GetService("@mozilla.org/observer-service;1"));
  if (!observerService) {
    MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
            ("nsNSSComponent: couldn't get observer service\n"));
    return NS_ERROR_FAILURE;
  }

  MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsNSSComponent: adding observers\n"));
  // Using false for the ownsweak parameter means the observer service will
  // keep a strong reference to this component. As a result, this will live at
  // least as long as the observer service.
  observerService->AddObserver(this, PROFILE_BEFORE_CHANGE_TOPIC, false);
  observerService->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false);

  return NS_OK;
}

nsresult DoesCertMatchFingerprint(const nsTArray<uint8_t>& cert,
                                  const nsCString& fingerprint, bool& result) {
  result = false;

  if (cert.Length() > std::numeric_limits<uint32_t>::max()) {
    return NS_ERROR_INVALID_ARG;
  }
  nsTArray<uint8_t> digestArray;
  nsresult rv = Digest::DigestBuf(SEC_OID_SHA256, cert.Elements(),
                                  cert.Length(), digestArray);
  if (NS_FAILED(rv)) {
    return rv;
  }
  SECItem digestItem = {siBuffer, digestArray.Elements(),
                        static_cast<unsigned int>(digestArray.Length())};
  UniquePORTString certFingerprint(
      CERT_Hexify(&digestItem, true /* use colon delimiters */));
  if (!certFingerprint) {
    return NS_ERROR_FAILURE;
  }

  result = fingerprint.Equals(certFingerprint.get());
  return NS_OK;
}

NS_IMETHODIMP
nsNSSComponent::IsCertTestBuiltInRoot(const nsTArray<uint8_t>& cert,
                                      bool* result) {
  NS_ENSURE_ARG_POINTER(result);
  *result = false;

#ifdef DEBUG
  MutexAutoLock lock(mMutex);
  nsresult rv = DoesCertMatchFingerprint(cert, mTestBuiltInRootHash, *result);
  if (NS_FAILED(rv)) {
    return rv;
  }
#endif  // DEBUG

  return NS_OK;
}

NS_IMETHODIMP
nsNSSComponent::IssuerMatchesMitmCanary(const char* aCertIssuer) {
  MutexAutoLock lock(mMutex);
  if (mMitmDetecionEnabled && !mMitmCanaryIssuer.IsEmpty()) {
    nsString certIssuer = NS_ConvertUTF8toUTF16(aCertIssuer);
    if (mMitmCanaryIssuer.Equals(certIssuer)) {
      return NS_OK;
    }
  }

  return NS_ERROR_FAILURE;
}

SharedCertVerifier::~SharedCertVerifier() = default;

NS_IMETHODIMP
nsNSSComponent::GetDefaultCertVerifier(SharedCertVerifier** result) {
  MutexAutoLock lock(mMutex);
  NS_ENSURE_ARG_POINTER(result);
  RefPtr<SharedCertVerifier> certVerifier(mDefaultCertVerifier);
  certVerifier.forget(result);
  return NS_OK;
}

// static
void nsNSSComponent::DoClearSSLExternalAndInternalSessionCache() {
  SSL_ClearSessionCache();
  mozilla::net::SSLTokensCache::Clear();
}

NS_IMETHODIMP
nsNSSComponent::ClearSSLExternalAndInternalSessionCache() {
  MOZ_ASSERT(XRE_IsParentProcess());
  if (!XRE_IsParentProcess()) {
    return NS_ERROR_NOT_AVAILABLE;
  }

  if (mozilla::net::nsIOService::UseSocketProcess()) {
    if (mozilla::net::gIOService) {
      mozilla::net::gIOService->CallOrWaitForSocketProcess([]() {
        Unused << mozilla::net::SocketProcessParent::GetSingleton()
                      ->SendClearSessionCache();
      });
    }
  }
  DoClearSSLExternalAndInternalSessionCache();
  return NS_OK;
}

NS_IMETHODIMP
nsNSSComponent::AsyncClearSSLExternalAndInternalSessionCache(
    JSContext* aCx, ::mozilla::dom::Promise** aPromise) {
  MOZ_ASSERT(XRE_IsParentProcess());
  if (!XRE_IsParentProcess()) {
    return NS_ERROR_NOT_AVAILABLE;
  }

  nsIGlobalObject* globalObject = xpc::CurrentNativeGlobal(aCx);
  if (NS_WARN_IF(!globalObject)) {
    return NS_ERROR_FAILURE;
  }

  ErrorResult result;
  RefPtr<mozilla::dom::Promise> promise =
      mozilla::dom::Promise::Create(globalObject, result);
  if (NS_WARN_IF(result.Failed())) {
    return result.StealNSResult();
  }

  if (mozilla::net::nsIOService::UseSocketProcess() &&
      mozilla::net::gIOService) {
    mozilla::net::gIOService->CallOrWaitForSocketProcess(
        [p = RefPtr{promise}]() {
          Unused << mozilla::net::SocketProcessParent::GetSingleton()
                        ->SendClearSessionCache()
                        ->Then(
                            GetCurrentSerialEventTarget(), __func__,
                            [promise = RefPtr{p}] {
                              promise->MaybeResolveWithUndefined();
                            },
                            [promise = RefPtr{p}] {
                              promise->MaybeReject(NS_ERROR_UNEXPECTED);
                            });
        });
  } else {
    promise->MaybeResolveWithUndefined();
  }
  DoClearSSLExternalAndInternalSessionCache();
  promise.forget(aPromise);
  return NS_OK;
}

namespace mozilla {
namespace psm {

already_AddRefed<SharedCertVerifier> GetDefaultCertVerifier() {
  static NS_DEFINE_CID(kNSSComponentCID, NS_NSSCOMPONENT_CID);

  nsCOMPtr<nsINSSComponent> nssComponent(do_GetService(kNSSComponentCID));
  if (!nssComponent) {
    return nullptr;
  }
  nsresult rv = nssComponent->BlockUntilLoadableCertsLoaded();
  if (NS_FAILED(rv)) {
    return nullptr;
  }
  RefPtr<SharedCertVerifier> result;
  rv = nssComponent->GetDefaultCertVerifier(getter_AddRefs(result));
  if (NS_FAILED(rv)) {
    return nullptr;
  }
  return result.forget();
}

// Helper for FindClientCertificatesWithPrivateKeys. Copies all
// CERTCertificates from `from` to `to`.
static inline void CopyCertificatesTo(UniqueCERTCertList& from,
                                      UniqueCERTCertList& to) {
  MOZ_ASSERT(from);
  MOZ_ASSERT(to);
  for (CERTCertListNode* n = CERT_LIST_HEAD(from.get());
       !CERT_LIST_END(n, from.get()); n = CERT_LIST_NEXT(n)) {
    UniqueCERTCertificate cert(CERT_DupCertificate(n->cert));
    MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
            ("      provisionally adding '%s'", n->cert->subjectName));
    if (CERT_AddCertToListTail(to.get(), cert.get()) == SECSuccess) {
      Unused << cert.release();
    }
  }
}

// Lists all private keys on all modules and returns a list of any corresponding
// client certificates. Returns null if no such certificates can be found. Also
// returns null if an error is encountered, because this is called as part of
// the client auth data callback, and NSS ignores any errors returned by the
// callback.
UniqueCERTCertList FindClientCertificatesWithPrivateKeys() {
  MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
          ("FindClientCertificatesWithPrivateKeys"));

  BlockUntilLoadableCertsLoaded();

  UniqueCERTCertList certsWithPrivateKeys(CERT_NewCertList());
  if (!certsWithPrivateKeys) {
    return nullptr;
  }

  UniquePK11SlotInfo internalSlot(PK11_GetInternalKeySlot());

  AutoSECMODListReadLock secmodLock;
  SECMODModuleList* list = SECMOD_GetDefaultModuleList();
  while (list) {
    MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
            ("  module '%s'", list->module->commonName));
    for (int i = 0; i < list->module->slotCount; i++) {
      PK11SlotInfo* slot = list->module->slots[i];
      MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
              ("    slot '%s'", PK11_GetSlotName(slot)));
      // If this is the internal certificate/key slot or the slot on the
      // builtin roots module, there may be many more certificates than private
      // keys, so search by private keys (PK11_HasRootCerts will be true if the
      // slot contains an object with the vendor-specific CK_CLASS
      // CKO_NSS_BUILTIN_ROOT_LIST, which should only be the case for the NSS
      // builtin roots module).
      if (internalSlot.get() == slot || PK11_HasRootCerts(slot)) {
        MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
                ("    (looking at internal/builtin slot)"));
        if (PK11_Authenticate(slot, true, nullptr) != SECSuccess) {
          MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("    (couldn't authenticate)"));
          continue;
        }
        UniqueSECKEYPrivateKeyList privateKeys(
            PK11_ListPrivKeysInSlot(slot, nullptr, nullptr));
        if (!privateKeys) {
          MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("      (no private keys)"));
          continue;
        }
        for (SECKEYPrivateKeyListNode* node = PRIVKEY_LIST_HEAD(privateKeys);
             !PRIVKEY_LIST_END(node, privateKeys);
             node = PRIVKEY_LIST_NEXT(node)) {
          UniqueCERTCertList certs(PK11_GetCertsMatchingPrivateKey(node->key));
          if (!certs) {
            MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
                    ("      PK11_GetCertsMatchingPrivateKey encountered an "
                     "error "));
            continue;
          }
          if (CERT_LIST_EMPTY(certs)) {
            MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("      (no certs for key)"));
            continue;
          }
          CopyCertificatesTo(certs, certsWithPrivateKeys);
        }
      } else {
        // ... otherwise, optimistically assume that searching by certificate
        // won't take too much time. Since "friendly" slots expose certificates
        // without needing to be authenticated to, this results in fewer PIN
        // dialogs shown to the user.
        MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
                ("    (looking at non-internal slot)"));

        if (!PK11_IsPresent(slot)) {
          MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("    (not present)"));
          continue;
        }
        // If this isn't a "friendly" slot, authenticate to expose certificates.
        if (!PK11_IsFriendly(slot) &&
            PK11_Authenticate(slot, true, nullptr) != SECSuccess) {
          MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("    (couldn't authenticate)"));
          continue;
        }
        UniqueCERTCertList certsInSlot(PK11_ListCertsInSlot(slot));
        if (!certsInSlot) {
          MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
                  ("      (couldn't list certs in slot)"));
          continue;
        }
        // When NSS decodes a certificate, if that certificate has a
        // corresponding private key (or public key, if the slot it's on hasn't
        // been logged into), it notes it as a "user cert".
        if (CERT_FilterCertListForUserCerts(certsInSlot.get()) != SECSuccess) {
          MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
                  ("      (couldn't filter certs)"));
          continue;
        }
        CopyCertificatesTo(certsInSlot, certsWithPrivateKeys);
      }
    }
    list = list->next;
  }

  if (CERT_FilterCertListByUsage(certsWithPrivateKeys.get(), certUsageSSLClient,
                                 false) != SECSuccess) {
    MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
            ("  CERT_FilterCertListByUsage encountered an error - returning"));
    return nullptr;
  }

  if (MOZ_UNLIKELY(MOZ_LOG_TEST(gPIPNSSLog, LogLevel::Debug))) {
    MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("  returning:"));
    for (CERTCertListNode* n = CERT_LIST_HEAD(certsWithPrivateKeys);
         !CERT_LIST_END(n, certsWithPrivateKeys); n = CERT_LIST_NEXT(n)) {
      MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("    %s", n->cert->subjectName));
    }
  }

  if (CERT_LIST_EMPTY(certsWithPrivateKeys)) {
    return nullptr;
  }

  return certsWithPrivateKeys;
}

}  // namespace psm
}  // namespace mozilla

NS_IMPL_ISUPPORTS(PipUIContext, nsIInterfaceRequestor)

PipUIContext::PipUIContext() = default;

PipUIContext::~PipUIContext() = default;

NS_IMETHODIMP
PipUIContext::GetInterface(const nsIID& uuid, void** result) {
  NS_ENSURE_ARG_POINTER(result);
  *result = nullptr;

  if (!NS_IsMainThread()) {
    NS_ERROR("PipUIContext::GetInterface called off the main thread");
    return NS_ERROR_NOT_SAME_THREAD;
  }

  if (!uuid.Equals(NS_GET_IID(nsIPrompt))) return NS_ERROR_NO_INTERFACE;

  nsIPrompt* prompt = nullptr;
  nsresult rv = nsNSSComponent::GetNewPrompter(&prompt);
  *result = prompt;
  return rv;
}

nsresult getNSSDialogs(void** _result, REFNSIID aIID, const char* contract) {
  if (!NS_IsMainThread()) {
    NS_ERROR("getNSSDialogs called off the main thread");
    return NS_ERROR_NOT_SAME_THREAD;
  }

  nsresult rv;

  nsCOMPtr<nsISupports> svc = do_GetService(contract, &rv);
  if (NS_FAILED(rv)) {
    return rv;
  }

  rv = svc->QueryInterface(aIID, _result);

  return rv;
}

nsresult setPassword(PK11SlotInfo* slot, nsIInterfaceRequestor* ctx) {
  MOZ_ASSERT(slot);
  MOZ_ASSERT(ctx);
  NS_ENSURE_ARG_POINTER(slot);
  NS_ENSURE_ARG_POINTER(ctx);

  if (PK11_NeedUserInit(slot)) {
    nsCOMPtr<nsITokenPasswordDialogs> dialogs;
    nsresult rv = getNSSDialogs(getter_AddRefs(dialogs),
                                NS_GET_IID(nsITokenPasswordDialogs),
                                NS_TOKENPASSWORDSDIALOG_CONTRACTID);
    if (NS_FAILED(rv)) {
      return rv;
    }

    bool canceled;
    nsCOMPtr<nsIPK11Token> token = new nsPK11Token(slot);
    rv = dialogs->SetPassword(ctx, token, &canceled);
    if (NS_FAILED(rv)) {
      return rv;
    }

    if (canceled) {
      return NS_ERROR_NOT_AVAILABLE;
    }
  }

  return NS_OK;
}

static PRBool ConvertBetweenUCS2andASCII(PRBool toUnicode, unsigned char* inBuf,
                                         unsigned int inBufLen,
                                         unsigned char* outBuf,
                                         unsigned int maxOutBufLen,
                                         unsigned int* outBufLen,
                                         PRBool swapBytes) {
  std::unique_ptr<unsigned char[]> inBufDup(new unsigned char[inBufLen]);
  if (!inBufDup) {
    return PR_FALSE;
  }
  std::memcpy(inBufDup.get(), inBuf, inBufLen * sizeof(unsigned char));

  // If converting Unicode to ASCII, swap bytes before conversion as neccessary.
  if (!toUnicode && swapBytes) {
    if (inBufLen % 2 != 0) {
      return PR_FALSE;
    }
    mozilla::NativeEndian::swapFromLittleEndianInPlace(
        reinterpret_cast<char16_t*>(inBufDup.get()), inBufLen / 2);
  }
  return PORT_UCS2_UTF8Conversion(toUnicode, inBufDup.get(), inBufLen, outBuf,
                                  maxOutBufLen, outBufLen);
}

namespace mozilla {
namespace psm {

nsresult InitializeCipherSuite() {
  MOZ_ASSERT(NS_IsMainThread(),
             "InitializeCipherSuite() can only be accessed on the main thread");

  if (NSS_SetDomesticPolicy() != SECSuccess) {
    return NS_ERROR_FAILURE;
  }

  // Disable any ciphers that NSS might have enabled by default
  for (uint16_t i = 0; i < SSL_NumImplementedCiphers; ++i) {
    uint16_t cipher_id = SSL_ImplementedCiphers[i];
    SSL_CipherPrefSetDefault(cipher_id, false);
  }

  // Now only set SSL/TLS ciphers we knew about at compile time
  for (const auto& cipherPref : sCipherPrefs) {
    SSL_CipherPrefSetDefault(cipherPref.id, cipherPref.prefGetter());
  }

  SetDeprecatedTLS1CipherPrefs();

  // Enable ciphers for PKCS#12
  SEC_PKCS12EnableCipher(PKCS12_RC4_40, 1);
  SEC_PKCS12EnableCipher(PKCS12_RC4_128, 1);
  SEC_PKCS12EnableCipher(PKCS12_RC2_CBC_40, 1);
  SEC_PKCS12EnableCipher(PKCS12_RC2_CBC_128, 1);
  SEC_PKCS12EnableCipher(PKCS12_DES_56, 1);
  SEC_PKCS12EnableCipher(PKCS12_DES_EDE3_168, 1);
  SEC_PKCS12EnableCipher(PKCS12_AES_CBC_128, 1);
  SEC_PKCS12EnableCipher(PKCS12_AES_CBC_192, 1);
  SEC_PKCS12EnableCipher(PKCS12_AES_CBC_256, 1);
  SEC_PKCS12SetPreferredCipher(PKCS12_DES_EDE3_168, 1);
  PORT_SetUCS2_ASCIIConversionFunction(ConvertBetweenUCS2andASCII);

  // PSM enforces a minimum RSA key size of 1024 bits, which is overridable.
  // NSS has its own minimum, which is not overridable (the default is 1023
  // bits). This sets the NSS minimum to 512 bits so users can still connect to
  // devices like wifi routers with woefully small keys (they would have to add
  // an override to do so, but they already do for such devices).
  NSS_OptionSet(NSS_RSA_MIN_KEY_SIZE, 512);

  // Observe preference change around cipher suite setting.
  return CipherSuiteChangeObserver::StartObserve();
}

}  // namespace psm
}  // namespace mozilla
