summaryrefslogtreecommitdiffstats
path: root/security/manager/ssl/nsNSSComponent.cpp
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--security/manager/ssl/nsNSSComponent.cpp2809
1 files changed, 2809 insertions, 0 deletions
diff --git a/security/manager/ssl/nsNSSComponent.cpp b/security/manager/ssl/nsNSSComponent.cpp
new file mode 100644
index 0000000000..5844ffecfd
--- /dev/null
+++ b/security/manager/ssl/nsNSSComponent.cpp
@@ -0,0 +1,2809 @@
+/* -*- 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());
+ }
+ }
+}
+
+NS_IMETHODIMP
+nsNSSComponent::HasActiveSmartCards(bool* result) {
+ NS_ENSURE_ARG_POINTER(result);
+
+ BlockUntilLoadableCertsLoaded();
+
+#ifndef MOZ_NO_SMART_CARDS
+ AutoSECMODListReadLock secmodLock;
+ SECMODModuleList* list = SECMOD_GetDefaultModuleList();
+ while (list) {
+ SECMODModule* module = list->module;
+ if (SECMOD_LockedModuleHasRemovableSlots(module)) {
+ *result = true;
+ return NS_OK;
+ }
+ for (int i = 0; i < module->slotCount; i++) {
+ if (!PK11_IsFriendly(module->slots[i])) {
+ *result = true;
+ return NS_OK;
+ }
+ }
+ list = list->next;
+ }
+#endif
+ *result = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNSSComponent::HasUserCertsInstalled(bool* result) {
+ NS_ENSURE_ARG_POINTER(result);
+
+ // FindClientCertificatesWithPrivateKeys won't ever return an empty list, so
+ // all we need to do is check if this is null or not.
+ UniqueCERTCertList certList(FindClientCertificatesWithPrivateKeys());
+ *result = !!certList;
+
+ return NS_OK;
+}
+
+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