/* -*- 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 # include #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& 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 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 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 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 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_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(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(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 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 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 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 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(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 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 observerService = mozilla::services::GetObserverService(); if (observerService) { observerService->NotifyObservers(nullptr, "psm:enterprise-certs-imported", nullptr); } } RefPtr 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 task = new BackgroundImportEnterpriseCertsTask(this); Unused << task->Dispatch(); } } void nsNSSComponent::ImportEnterpriseRoots() { MOZ_ASSERT(!NS_IsMainThread()); if (NS_IsMainThread()) { return; } Vector 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>& enterpriseCerts, bool getRoots) { nsresult rv = BlockUntilLoadableCertsLoaded(); if (NS_FAILED(rv)) { return rv; } MutexAutoLock nsNSSComponentLock(mMutex); enterpriseCerts.Clear(); for (const auto& cert : mEnterpriseCerts) { nsTArray 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>& enterpriseRoots) { return CommonGetEnterpriseCerts(enterpriseRoots, true); } NS_IMETHODIMP nsNSSComponent::GetEnterpriseIntermediates( nsTArray>& enterpriseIntermediates) { return CommonGetEnterpriseCerts(enterpriseIntermediates, false); } NS_IMETHODIMP nsNSSComponent::AddEnterpriseIntermediate( const nsTArray& 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&& possibleLoadableRootsLocations, Maybe&& 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 mNSSComponent; bool mImportEnterpriseRoots; uint32_t mFamilySafetyMode; Vector mPossibleLoadableRootsLocations; // encoded in UTF-8 Maybe 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 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 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 directoryService( do_GetService(NS_DIRECTORY_SERVICE_CONTRACTID)); if (!directoryService) { MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("could not get directory service")); return NS_ERROR_FAILURE; } nsCOMPtr 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 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 task = new BackgroundLoadOSClientCertsModuleTask(std::move(libraryDir)); Unused << task->Dispatch(); } else { UniqueSECMODModule osClientCertsModule( SECMOD_FindModule(kOSClientCertsModuleName)); if (osClientCertsModule) { SECMOD_UnloadUserModule(osClientCertsModule.get()); } } } nsresult nsNSSComponent::BlockUntilLoadableCertsLoaded() { MonitorAutoLock rootsLoadedLock(mLoadableCertsLoadedMonitor); while (!mLoadableCertsLoaded) { rootsLoadedLock.Wait(); } MOZ_ASSERT(mLoadableCertsLoaded); return mLoadableCertsLoadedResult; } #ifndef MOZ_NO_SMART_CARDS static StaticMutex sCheckForSmartCardChangesMutex MOZ_UNANNOTATED; static TimeStamp sLastCheckedForSmartCardChanges = TimeStamp::Now(); #endif nsresult nsNSSComponent::CheckForSmartCardChanges() { #ifndef MOZ_NO_SMART_CARDS { StaticMutexAutoLock lock(sCheckForSmartCardChangesMutex); // Do this at most once every 3 seconds. TimeStamp now = TimeStamp::Now(); if (now - sLastCheckedForSmartCardChanges < TimeDuration::FromSeconds(3.0)) { return NS_OK; } sLastCheckedForSmartCardChanges = now; } // SECMOD_UpdateSlotList attempts to acquire the list lock as well, so we // have to do this in three steps. Vector 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(NSS_Initialize))); if (!nss3Path) { MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nss not loaded?")); return NS_ERROR_FAILURE; } nsCOMPtr 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 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& 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( 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 sObserver; CipherSuiteChangeObserver() = default; }; NS_IMPL_ISUPPORTS(CipherSuiteChangeObserver, nsIObserver) // static StaticRefPtr CipherSuiteChangeObserver::sObserver; // static nsresult CipherSuiteChangeObserver::StartObserve() { MOZ_ASSERT(NS_IsMainThread(), "CipherSuiteChangeObserver::StartObserve() can only be accessed " "on the main thread"); if (!sObserver) { RefPtr observer = new CipherSuiteChangeObserver(); nsresult rv = Preferences::AddStrongObserver(observer.get(), "security."); if (NS_FAILED(rv)) { sObserver = nullptr; return rv; } nsCOMPtr 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 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( 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( 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(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 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 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 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 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 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 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 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. 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& profileDirectory, const nsACString& dbType) { nsCOMPtr 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 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 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 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 certOverrideService( do_GetService(NS_CERTOVERRIDE_CONTRACTID)); nsCOMPtr clientAuthRememberService( do_GetService(NS_CLIENTAUTHREMEMBERSERVICE_CONTRACTID)); nsCOMPtr siteSecurityService( do_GetService(NS_SSSERVICE_CONTRACTID)); nsCOMPtr 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 possibleLoadableRootsLocations; rv = ListPossibleLoadableRootsLocations(possibleLoadableRootsLocations); MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv)); if (NS_FAILED(rv)) { return rv; } bool loadOSClientCertsModule = StaticPrefs::security_osclientcerts_autoload(); Maybe maybeOSClientCertsModuleLocation; if (loadOSClientCertsModule) { nsAutoCString libraryDir; if (NS_SUCCEEDED(GetDirectoryPath(NS_GRE_BIN_DIR, libraryDir))) { maybeOSClientCertsModuleLocation.emplace(libraryDir); } } RefPtr 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 certStorage(do_GetService(NS_CERT_STORAGE_CID)); if (!certStorage) { MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("couldn't get cert_storage")); return; } Vector 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 subject; subject.AppendElements(n->cert->derSubject.data, n->cert->derSubject.len); nsTArray> 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 runnable(NS_NewRunnableFunction( "IntermediatePreloadingHealerCallbackDone", []() -> void { nsCOMPtr 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 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 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 icos = do_GetService("@mozilla.org/security/certoverride;1"); if (icos) { icos->ClearValidityOverride("all:temporary-certificates"_ns, 0, OriginAttributes()); } ClearSSLExternalAndInternalSessionCache(); nsCOMPtr os = mozilla::services::GetObserverService(); if (os) { os->NotifyObservers(nullptr, "net:cancel-all-connections", nullptr); } return NS_OK; } nsresult nsNSSComponent::RegisterObservers() { nsCOMPtr 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& cert, const nsCString& fingerprint, bool& result) { result = false; if (cert.Length() > std::numeric_limits::max()) { return NS_ERROR_INVALID_ARG; } nsTArray 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(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& 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 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 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 GetDefaultCertVerifier() { static NS_DEFINE_CID(kNSSComponentCID, NS_NSSCOMPONENT_CID); nsCOMPtr nssComponent(do_GetService(kNSSComponentCID)); if (!nssComponent) { return nullptr; } nsresult rv = nssComponent->BlockUntilLoadableCertsLoaded(); if (NS_FAILED(rv)) { return nullptr; } RefPtr 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 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 dialogs; nsresult rv = getNSSDialogs(getter_AddRefs(dialogs), NS_GET_IID(nsITokenPasswordDialogs), NS_TOKENPASSWORDSDIALOG_CONTRACTID); if (NS_FAILED(rv)) { return rv; } bool canceled; nsCOMPtr 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 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(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