bug 1372656 - load loadable roots on a background thread r?Cykesiopka,jcj
In a profile, loading the loadable roots PKCS#11 module (i.e. the built-in root
CA module) accounted for about 60% of the time to initialize PSM/NSS. Since we
only need the roots module loaded when we're actually looking for an issuing
certificate or querying a certificate's trust, we can do the load
asynchronously (where it hopefully finishes before we actually need it, because
otherwise we'll have to wait anyway).
MozReview-Commit-ID: JyY6NtpQAUj
--- a/security/certverifier/CertVerifier.cpp
+++ b/security/certverifier/CertVerifier.cpp
@@ -132,16 +132,20 @@ IsCertChainRootBuiltInRoot(const UniqueC
// The term "builtin root" traditionally refers to a root CA certificate that
// has been added to the NSS trust store, because it has been approved
// for inclusion according to the Mozilla CA policy, and might be accepted
// by Mozilla applications as an issuer for certificates seen on the public web.
Result
IsCertBuiltInRoot(CERTCertificate* cert, bool& result)
{
+ if (NS_FAILED(BlockUntilLoadableRootsLoaded())) {
+ return Result::FATAL_ERROR_LIBRARY_FAILURE;
+ }
+
result = false;
#ifdef DEBUG
nsCOMPtr<nsINSSComponent> component(do_GetService(PSM_COMPONENT_CONTRACTID));
if (!component) {
return Result::FATAL_ERROR_LIBRARY_FAILURE;
}
nsresult rv = component->IsCertTestBuiltInRoot(cert, result);
if (NS_FAILED(rv)) {
@@ -459,16 +463,20 @@ CertVerifier::VerifyCert(CERTCertificate
{
MOZ_LOG(gCertVerifierLog, LogLevel::Debug, ("Top of VerifyCert\n"));
MOZ_ASSERT(cert);
MOZ_ASSERT(usage == certificateUsageSSLServer || !(flags & FLAG_MUST_BE_EV));
MOZ_ASSERT(usage == certificateUsageSSLServer || !keySizeStatus);
MOZ_ASSERT(usage == certificateUsageSSLServer || !sha1ModeResult);
+ if (NS_FAILED(BlockUntilLoadableRootsLoaded())) {
+ return Result::FATAL_ERROR_LIBRARY_FAILURE;
+ }
+
if (evOidPolicy) {
*evOidPolicy = SEC_OID_UNKNOWN;
}
if (ocspStaplingStatus) {
if (usage != certificateUsageSSLServer) {
return Result::FATAL_ERROR_INVALID_ARGS;
}
*ocspStaplingStatus = OCSP_STAPLING_NEVER_CHECKED;
--- a/security/certverifier/ExtendedValidation.cpp
+++ b/security/certverifier/ExtendedValidation.cpp
@@ -1171,17 +1171,17 @@ CertIsAuthoritativeForEVPolicy(const Uni
return true;
}
}
return false;
}
nsresult
-LoadExtendedValidationInfo()
+LoadExtendedValidationInfo(const nsNSSShutDownPreventionLock& /*proofOfLock*/)
{
static const char* sCABForumOIDString = "2.23.140.1.1";
static const char* sCABForumOIDDescription = "CA/Browser Forum EV OID";
ScopedAutoSECItem cabforumOIDItem;
if (SEC_StringToOID(nullptr, &cabforumOIDItem, sCABForumOIDString, 0)
!= SECSuccess) {
return NS_ERROR_FAILURE;
--- a/security/certverifier/ExtendedValidation.h
+++ b/security/certverifier/ExtendedValidation.h
@@ -3,22 +3,25 @@
* 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/. */
#ifndef ExtendedValidation_h
#define ExtendedValidation_h
#include "ScopedNSSTypes.h"
#include "certt.h"
+#include "nsNSSShutDown.h"
namespace mozilla { namespace pkix { struct CertPolicyId; } }
namespace mozilla { namespace psm {
-nsresult LoadExtendedValidationInfo();
+nsresult LoadExtendedValidationInfo(
+ const nsNSSShutDownPreventionLock& proofOfLock);
+
/**
* Finds the first policy OID in the given cert that is known to be an EV policy
* OID.
*
* @param cert
* The cert to find the first EV policy of.
* @param policy
* The found policy.
--- a/security/certverifier/NSSCertDBTrustDomain.cpp
+++ b/security/certverifier/NSSCertDBTrustDomain.cpp
@@ -16,16 +16,17 @@
#include "cert.h"
#include "certdb.h"
#include "mozilla/Assertions.h"
#include "mozilla/Casting.h"
#include "mozilla/Move.h"
#include "mozilla/PodOperations.h"
#include "mozilla/TimeStamp.h"
#include "mozilla/Unused.h"
+#include "nsCRTGlue.h"
#include "nsNSSCertificate.h"
#include "nsServiceManagerUtils.h"
#include "nss.h"
#include "pk11pub.h"
#include "pkix/Result.h"
#include "pkix/pkix.h"
#include "pkix/pkixnss.h"
#include "prerror.h"
@@ -1255,42 +1256,37 @@ DisableMD5()
0, NSS_USE_ALG_IN_CERT_SIGNATURE | NSS_USE_ALG_IN_CMS_SIGNATURE);
NSS_SetAlgorithmPolicy(SEC_OID_PKCS5_PBE_WITH_MD5_AND_DES_CBC,
0, NSS_USE_ALG_IN_CERT_SIGNATURE | NSS_USE_ALG_IN_CMS_SIGNATURE);
}
bool
LoadLoadableRoots(const nsCString& dir, const nsCString& modNameUTF8)
{
- UniquePRLibraryName fullLibraryPath(
- PR_GetLibraryName(dir.IsEmpty() ? nullptr : dir.get(), "nssckbi"));
- if (!fullLibraryPath) {
- return false;
- }
-
- // Escape the \ and " characters.
- nsAutoCString escapedFullLibraryPath(fullLibraryPath.get());
- escapedFullLibraryPath.ReplaceSubstring("\\", "\\\\");
- escapedFullLibraryPath.ReplaceSubstring("\"", "\\\"");
- if (escapedFullLibraryPath.IsEmpty()) {
- return false;
- }
-
// If a module exists with the same name, make a best effort attempt to delete
// it. Note that it isn't possible to delete the internal module, so checking
// the return value would be detrimental in that case.
int unusedModType;
Unused << SECMOD_DeleteModule(modNameUTF8.get(), &unusedModType);
- nsAutoCString pkcs11ModuleSpec;
- pkcs11ModuleSpec.AppendPrintf("name=\"%s\" library=\"%s\"", modNameUTF8.get(),
- escapedFullLibraryPath.get());
- if (pkcs11ModuleSpec.IsEmpty()) {
- return false;
+ nsAutoCString fullLibraryPath;
+ if (!dir.IsEmpty()) {
+ fullLibraryPath.Assign(dir);
+ fullLibraryPath.AppendLiteral(FILE_PATH_SEPARATOR);
}
+ fullLibraryPath.Append(DLL_PREFIX "nssckbi" DLL_SUFFIX);
+ // Escape the \ and " characters.
+ fullLibraryPath.ReplaceSubstring("\\", "\\\\");
+ fullLibraryPath.ReplaceSubstring("\"", "\\\"");
+
+ nsAutoCString pkcs11ModuleSpec("name=\"");
+ pkcs11ModuleSpec.Append(modNameUTF8);
+ pkcs11ModuleSpec.AppendLiteral("\" library=\"");
+ pkcs11ModuleSpec.Append(fullLibraryPath);
+ pkcs11ModuleSpec.AppendLiteral("\"");
UniqueSECMODModule rootsModule(
SECMOD_LoadUserModule(const_cast<char*>(pkcs11ModuleSpec.get()), nullptr,
false));
if (!rootsModule) {
return false;
}
--- a/security/certverifier/moz.build
+++ b/security/certverifier/moz.build
@@ -35,16 +35,19 @@ UNIFIED_SOURCES += [
'SignedCertificateTimestamp.cpp',
]
if not CONFIG['NSS_NO_EV_CERTS']:
UNIFIED_SOURCES += [
'ExtendedValidation.cpp',
]
+for var in ('DLL_PREFIX', 'DLL_SUFFIX'):
+ DEFINES[var] = '"%s"' % CONFIG[var]
+
LOCAL_INCLUDES += [
'/security/manager/ssl',
'/security/pkix/include',
'/security/pkix/lib',
]
DIRS += [
'../pkix',
--- a/security/manager/ssl/ScopedNSSTypes.h
+++ b/security/manager/ssl/ScopedNSSTypes.h
@@ -21,16 +21,17 @@
#include "mozilla/UniquePtr.h"
#include "nsDebug.h"
#include "nsError.h"
#include "NSSErrorsService.h"
#include "pk11pub.h"
#include "pkcs12.h"
#include "prerror.h"
#include "prio.h"
+#include "prmem.h"
#include "sechash.h"
#include "secmod.h"
#include "secpkcs7.h"
#include "secport.h"
#ifndef MOZ_NO_MOZALLOC
#include "mozilla/mozalloc_oom.h"
#endif
@@ -322,19 +323,19 @@ MOZ_TYPE_SPECIFIC_UNIQUE_PTR_TEMPLATE(Un
PLArenaPool,
internal::PORT_FreeArena_false)
MOZ_TYPE_SPECIFIC_UNIQUE_PTR_TEMPLATE(UniquePORTString,
char,
PORT_Free)
MOZ_TYPE_SPECIFIC_UNIQUE_PTR_TEMPLATE(UniquePRFileDesc,
PRFileDesc,
PR_Close)
-MOZ_TYPE_SPECIFIC_UNIQUE_PTR_TEMPLATE(UniquePRLibraryName,
+MOZ_TYPE_SPECIFIC_UNIQUE_PTR_TEMPLATE(UniquePRString,
char,
- PR_FreeLibraryName)
+ PR_Free)
MOZ_TYPE_SPECIFIC_UNIQUE_PTR_TEMPLATE(UniqueSECAlgorithmID,
SECAlgorithmID,
internal::SECOID_DestroyAlgorithmID_true)
MOZ_TYPE_SPECIFIC_UNIQUE_PTR_TEMPLATE(UniqueSECItem,
SECItem,
internal::SECITEM_FreeItem_true)
MOZ_TYPE_SPECIFIC_UNIQUE_PTR_TEMPLATE(UniqueSECKEYPrivateKey,
--- a/security/manager/ssl/nsNSSCertificate.cpp
+++ b/security/manager/ssl/nsNSSCertificate.cpp
@@ -891,16 +891,21 @@ nsNSSCertificate::ExportAsCMS(uint32_t c
{
NS_ENSURE_ARG(aLength);
NS_ENSURE_ARG(aArray);
nsNSSShutDownPreventionLock locker;
if (isAlreadyShutDown())
return NS_ERROR_NOT_AVAILABLE;
+ nsresult rv = BlockUntilLoadableRootsLoaded();
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
if (!mCert)
return NS_ERROR_FAILURE;
switch (chainMode) {
case nsIX509Cert::CMS_CHAIN_MODE_CertOnly:
case nsIX509Cert::CMS_CHAIN_MODE_CertChain:
case nsIX509Cert::CMS_CHAIN_MODE_CertChainWithRoot:
break;
--- a/security/manager/ssl/nsNSSCertificateDB.cpp
+++ b/security/manager/ssl/nsNSSCertificateDB.cpp
@@ -103,18 +103,23 @@ nsNSSCertificateDB::FindCertByDBKey(cons
return NS_ERROR_INVALID_ARG;
}
nsNSSShutDownPreventionLock locker;
if (isAlreadyShutDown()) {
return NS_ERROR_NOT_AVAILABLE;
}
+ nsresult rv = BlockUntilLoadableRootsLoaded();
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
UniqueCERTCertificate cert;
- nsresult rv = FindCertByDBKey(aDBKey, cert);
+ rv = FindCertByDBKey(aDBKey, cert);
if (NS_FAILED(rv)) {
return rv;
}
// If we can't find the certificate, that's not an error. Just return null.
if (!cert) {
return NS_OK;
}
nsCOMPtr<nsIX509Cert> nssCert = nsNSSCertificate::Create(cert.get());
@@ -887,16 +892,22 @@ nsNSSCertificateDB::IsCertTrusted(nsIX50
{
NS_ENSURE_ARG_POINTER(_isTrusted);
*_isTrusted = false;
nsNSSShutDownPreventionLock locker;
if (isAlreadyShutDown()) {
return NS_ERROR_NOT_AVAILABLE;
}
+
+ nsresult rv = BlockUntilLoadableRootsLoaded();
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
SECStatus srv;
UniqueCERTCertificate nsscert(cert->GetCert());
CERTCertTrust nsstrust;
srv = CERT_GetCertTrust(nsscert.get(), &nsstrust);
if (srv != SECSuccess)
return NS_ERROR_FAILURE;
nsNSSCertTrust trust(&nsstrust);
@@ -997,16 +1008,20 @@ nsNSSCertificateDB::ImportPKCS12File(nsI
{
if (!NS_IsMainThread()) {
return NS_ERROR_NOT_SAME_THREAD;
}
nsNSSShutDownPreventionLock locker;
if (isAlreadyShutDown()) {
return NS_ERROR_NOT_AVAILABLE;
}
+ nsresult rv = BlockUntilLoadableRootsLoaded();
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
NS_ENSURE_ARG(aFile);
nsPKCS12Blob blob;
return blob.ImportFromFile(aFile);
}
NS_IMETHODIMP
nsNSSCertificateDB::ExportPKCS12File(nsIFile* aFile, uint32_t count,
@@ -1014,16 +1029,20 @@ nsNSSCertificateDB::ExportPKCS12File(nsI
{
if (!NS_IsMainThread()) {
return NS_ERROR_NOT_SAME_THREAD;
}
nsNSSShutDownPreventionLock locker;
if (isAlreadyShutDown()) {
return NS_ERROR_NOT_AVAILABLE;
}
+ nsresult rv = BlockUntilLoadableRootsLoaded();
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
NS_ENSURE_ARG(aFile);
if (count == 0) {
return NS_OK;
}
nsPKCS12Blob blob;
return blob.ExportToFile(aFile, certs, count);
}
@@ -1032,16 +1051,21 @@ NS_IMETHODIMP
nsNSSCertificateDB::FindCertByEmailAddress(const nsACString& aEmailAddress,
nsIX509Cert** _retval)
{
nsNSSShutDownPreventionLock locker;
if (isAlreadyShutDown()) {
return NS_ERROR_NOT_AVAILABLE;
}
+ nsresult rv = BlockUntilLoadableRootsLoaded();
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
RefPtr<SharedCertVerifier> certVerifier(GetDefaultCertVerifier());
NS_ENSURE_TRUE(certVerifier, NS_ERROR_UNEXPECTED);
const nsCString& flatEmailAddress = PromiseFlatCString(aEmailAddress);
UniqueCERTCertList certlist(
PK11_FindCertsFromEmailAddress(flatEmailAddress.get(), nullptr));
if (!certlist)
return NS_ERROR_FAILURE;
@@ -1152,16 +1176,20 @@ nsNSSCertificateDB::get_default_nickname
{
static NS_DEFINE_CID(kNSSComponentCID, NS_NSSCOMPONENT_CID);
nickname.Truncate();
nsresult rv;
CK_OBJECT_HANDLE keyHandle;
+ if (NS_FAILED(BlockUntilLoadableRootsLoaded())) {
+ return;
+ }
+
CERTCertDBHandle *defaultcertdb = CERT_GetDefaultCertDB();
nsCOMPtr<nsINSSComponent> nssComponent(do_GetService(kNSSComponentCID, &rv));
if (NS_FAILED(rv))
return;
nsAutoCString username;
UniquePORTString tempCN(CERT_GetCommonName(&cert->subject));
if (tempCN) {
--- a/security/manager/ssl/nsNSSComponent.cpp
+++ b/security/manager/ssl/nsNSSComponent.cpp
@@ -194,17 +194,20 @@ GetRevocationBehaviorFromPrefs(/*out*/ C
hardTimeoutMillis = std::min(hardTimeoutMillis,
OCSP_TIMEOUT_MILLISECONDS_HARD_MAX);
hardTimeout = TimeDuration::FromMilliseconds(hardTimeoutMillis);
SSL_ClearSessionCache();
}
nsNSSComponent::nsNSSComponent()
- : mMutex("nsNSSComponent.mMutex")
+ : mLoadableRootsLoadedMonitor("nsNSSComponent.mLoadableRootsLoadedMonitor")
+ , mLoadableRootsLoaded(false)
+ , mLoadableRootsLoadedResult(NS_ERROR_FAILURE)
+ , mMutex("nsNSSComponent.mMutex")
, mNSSInitialized(false)
#ifndef MOZ_NO_SMART_CARDS
, mThreadList(nullptr)
#endif
{
MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsNSSComponent::ctor\n"));
MOZ_RELEASE_ASSERT(NS_IsMainThread());
@@ -1044,95 +1047,245 @@ nsNSSComponent::ImportEnterpriseRootsFor
numImported++;
// now owned by mEnterpriseRoots
Unused << nssCertificate.release();
}
MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("imported %u roots", numImported));
}
#endif // XP_WIN
-void
-nsNSSComponent::LoadLoadableRoots()
+class LoadLoadableRootsTask final : public Runnable
+ , public nsNSSShutDownObject
+{
+public:
+ explicit LoadLoadableRootsTask(nsNSSComponent* nssComponent)
+ : Runnable("LoadLoadableRootsTask")
+ , mNSSComponent(nssComponent)
+ {
+ MOZ_ASSERT(nssComponent);
+ }
+
+ ~LoadLoadableRootsTask();
+
+ nsresult Dispatch();
+
+ void virtualDestroyNSSReference() override {} // nothing to release
+
+private:
+ NS_IMETHOD Run() override;
+ nsresult LoadLoadableRoots(const nsNSSShutDownPreventionLock& proofOfLock);
+ RefPtr<nsNSSComponent> mNSSComponent;
+ nsCOMPtr<nsIThread> mThread;
+};
+
+LoadLoadableRootsTask::~LoadLoadableRootsTask()
+{
+ nsNSSShutDownPreventionLock lock;
+ if (isAlreadyShutDown()) {
+ return;
+ }
+ shutdown(ShutdownCalledFrom::Object);
+}
+
+nsresult
+LoadLoadableRootsTask::Dispatch()
+{
+ // Can't add 'this' as the event to run, since mThread may not be set yet
+ nsresult rv = NS_NewNamedThread("LoadRoots", getter_AddRefs(mThread),
+ nullptr,
+ nsIThreadManager::DEFAULT_STACK_SIZE);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ // Note: event must not null out mThread!
+ return mThread->Dispatch(this, NS_DISPATCH_NORMAL);
+}
+
+NS_IMETHODIMP
+LoadLoadableRootsTask::Run()
+{
+ nsNSSShutDownPreventionLock lock;
+ if (isAlreadyShutDown()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ nsresult rv = LoadLoadableRoots(lock);
+ if (NS_FAILED(rv)) {
+ MOZ_LOG(gPIPNSSLog, LogLevel::Error, ("LoadLoadableRoots failed"));
+ // We don't return rv here because then BlockUntilLoadableRootsLoaded 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.
+ }
+
+ if (NS_SUCCEEDED(rv)) {
+ if (NS_FAILED(LoadExtendedValidationInfo(lock))) {
+ // 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"));
+ }
+ }
+ {
+ MonitorAutoLock rootsLoadedLock(mNSSComponent->mLoadableRootsLoadedMonitor);
+ mNSSComponent->mLoadableRootsLoaded = true;
+ // Cache the result of LoadLoadableRoots so BlockUntilLoadableRootsLoaded
+ // can return it to all callers later.
+ mNSSComponent->mLoadableRootsLoadedResult = rv;
+ rv = mNSSComponent->mLoadableRootsLoadedMonitor.NotifyAll();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ }
+ return NS_OK;
+}
+
+nsresult
+nsNSSComponent::BlockUntilLoadableRootsLoaded()
+{
+ MonitorAutoLock rootsLoadedLock(mLoadableRootsLoadedMonitor);
+ while (!mLoadableRootsLoaded) {
+ nsresult rv = rootsLoadedLock.Wait();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ }
+ MOZ_ASSERT(mLoadableRootsLoaded);
+ return mLoadableRootsLoadedResult;
+}
+
+// Returns by reference the path to the directory containing the file that has
+// been loaded as DLL_PREFIX nss3 DLL_SUFFIX.
+static nsresult
+GetNSS3Directory(nsCString& result)
+{
+ UniquePRString nss3Path(
+ PR_GetLibraryFilePathname(DLL_PREFIX "nss3" 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 nss3Directory->GetNativePath(result);
+}
+
+// Returns by reference the path to the desired directory, based on the current
+// settings in the directory service.
+static nsresult
+GetDirectoryPath(const char* directoryKey, nsCString& result)
+{
+ 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 directory->GetNativePath(result);
+}
+
+
+nsresult
+LoadLoadableRootsTask::LoadLoadableRoots(
+ const nsNSSShutDownPreventionLock& /*proofOfLock*/)
{
// Find the best Roots module for our purposes.
// Prefer the application's installation directory,
// but also ensure the library is at least the version we expect.
nsAutoString modName;
- nsresult rv = GetPIPNSSBundleString("RootCertModuleName", modName);
+ nsresult rv = mNSSComponent->GetPIPNSSBundleString("RootCertModuleName",
+ modName);
if (NS_FAILED(rv)) {
// When running Cpp unit tests on Android, this will fail because string
// bundles aren't available (see bug 1311077, bug 1228175 comment 12, and
// bug 929655). Because the module name is really only for display purposes,
// we can just hard-code the value here. Furthermore, if we want to be able
// to stop using string bundles in PSM in this way, we'll have to hard-code
// the string and only use the localized version when displaying it to the
// user, so this is a step in that direction anyway.
modName.AssignLiteral("Builtin Roots Module");
}
-
- nsCOMPtr<nsIProperties> directoryService(do_GetService(NS_DIRECTORY_SERVICE_CONTRACTID));
- if (!directoryService)
- return;
-
- static const char nss_lib[] = "nss3";
- const char* possible_ckbi_locations[] = {
- nss_lib, // This special value means: search for ckbi in the directory
- // where nss3 is.
- NS_XPCOM_CURRENT_PROCESS_DIR,
- NS_GRE_DIR,
- 0 // This special value means:
- // search for ckbi in the directories on the shared
- // library/DLL search path
- };
-
- for (size_t il = 0; il < sizeof(possible_ckbi_locations)/sizeof(const char*); ++il) {
- nsAutoCString libDir;
+ NS_ConvertUTF16toUTF8 modNameUTF8(modName);
- if (possible_ckbi_locations[il]) {
- nsCOMPtr<nsIFile> mozFile;
- if (possible_ckbi_locations[il] == nss_lib) {
- // Get the location of the nss3 library.
- char* nss_path = PR_GetLibraryFilePathname(DLL_PREFIX "nss3" DLL_SUFFIX,
- (PRFuncPtr) NSS_Initialize);
- if (!nss_path) {
- continue;
- }
- // Get the directory containing the nss3 library.
- nsCOMPtr<nsIFile> nssLib(do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv));
- if (NS_SUCCEEDED(rv)) {
- rv = nssLib->InitWithNativePath(nsDependentCString(nss_path));
- }
- PR_Free(nss_path); // PR_GetLibraryFilePathname() uses PR_Malloc().
- if (NS_SUCCEEDED(rv)) {
- nsCOMPtr<nsIFile> file;
- if (NS_SUCCEEDED(nssLib->GetParent(getter_AddRefs(file)))) {
- mozFile = do_QueryInterface(file);
- }
- }
- } else {
- directoryService->Get( possible_ckbi_locations[il],
- NS_GET_IID(nsIFile),
- getter_AddRefs(mozFile));
- }
+ Vector<nsCString> possibleCKBILocations;
+ // First try in the directory where we've already loaded
+ // DLL_PREFIX nss3 DLL_SUFFIX, since that's likely to be correct.
+ nsAutoCString nss3Dir;
+ rv = GetNSS3Directory(nss3Dir);
+ if (NS_SUCCEEDED(rv)) {
+ if (!possibleCKBILocations.append(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 (!possibleCKBILocations.append(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 (!possibleCKBILocations.append(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 (!possibleCKBILocations.append(Move(emptyString))) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
- if (!mozFile) {
- continue;
- }
-
- if (NS_FAILED(mozFile->GetNativePath(libDir))) {
- continue;
- }
- }
-
- NS_ConvertUTF16toUTF8 modNameUTF8(modName);
- if (mozilla::psm::LoadLoadableRoots(libDir, modNameUTF8)) {
- break;
+ for (const auto& possibleCKBILocation : possibleCKBILocations) {
+ if (mozilla::psm::LoadLoadableRoots(possibleCKBILocation, modNameUTF8)) {
+ MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("loaded CKBI from %s",
+ possibleCKBILocation.get()));
+ return NS_OK;
}
}
+ MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("could not load loadable roots"));
+ return NS_ERROR_FAILURE;
}
void
nsNSSComponent::UnloadLoadableRoots()
{
nsresult rv;
nsAutoString modName;
rv = GetPIPNSSBundleString("RootCertModuleName", modName);
@@ -1846,23 +1999,16 @@ nsNSSComponent::InitializeNSS()
SSL_OptionSetDefault(SSL_V2_COMPATIBLE_HELLO, false);
rv = setEnabledTLSVersions();
if (NS_FAILED(rv)) {
return NS_ERROR_UNEXPECTED;
}
DisableMD5();
- LoadLoadableRoots();
-
- rv = LoadExtendedValidationInfo();
- if (NS_FAILED(rv)) {
- MOZ_LOG(gPIPNSSLog, LogLevel::Error, ("failed to load EV info"));
- return rv;
- }
MaybeEnableFamilySafetyCompatibility();
MaybeImportEnterpriseRoots();
ConfigureTLSSessionIdentifiers();
bool requireSafeNegotiation =
Preferences::GetBool("security.ssl.require_safe_negotiation",
@@ -1918,16 +2064,19 @@ nsNSSComponent::InitializeNSS()
#endif
mozilla::pkix::RegisterErrorTable();
if (PK11_IsFIPS()) {
Telemetry::Accumulate(Telemetry::FIPS_ENABLED, true);
}
+ // Gather telemetry on any PKCS#11 modules we have loaded. Note that because
+ // we load the built-in root module asynchronously after this, the telemetry
+ // will not include it.
{ // Introduce scope for the AutoSECMODListReadLock.
AutoSECMODListReadLock lock;
for (SECMODModuleList* list = SECMOD_GetDefaultModuleList(); list;
list = list->next) {
nsAutoString scalarKey;
GetModuleNameForTelemetry(list->module, scalarKey);
// Scalar keys must be between 0 and 70 characters (exclusive).
// GetModuleNameForTelemetry takes care of keys that are too long. If for
@@ -1954,17 +2103,19 @@ nsNSSComponent::InitializeNSS()
#endif
mContentSigningRootHash.Truncate();
Preferences::GetString("security.content.signature.root_hash",
mContentSigningRootHash);
mNSSInitialized = true;
}
- return NS_OK;
+ RefPtr<LoadLoadableRootsTask> loadLoadableRootsTask(
+ new LoadLoadableRootsTask(this));
+ return loadLoadableRootsTask->Dispatch();
}
void
nsNSSComponent::ShutdownNSS()
{
MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsNSSComponent::ShutdownNSS\n"));
MOZ_RELEASE_ASSERT(NS_IsMainThread());
--- a/security/manager/ssl/nsNSSComponent.h
+++ b/security/manager/ssl/nsNSSComponent.h
@@ -5,16 +5,17 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef _nsNSSComponent_h_
#define _nsNSSComponent_h_
#include "ScopedNSSTypes.h"
#include "SharedCertVerifier.h"
#include "mozilla/Attributes.h"
+#include "mozilla/Monitor.h"
#include "mozilla/Mutex.h"
#include "mozilla/RefPtr.h"
#include "nsCOMPtr.h"
#include "nsIObserver.h"
#include "nsIStringBundle.h"
#include "nsNSSCallbacks.h"
#include "prerror.h"
#include "sslt.h"
@@ -77,29 +78,35 @@ public:
#endif
NS_IMETHOD IsCertContentSigningRoot(CERTCertificate* cert, bool& result) = 0;
#ifdef XP_WIN
NS_IMETHOD GetEnterpriseRoots(nsIX509CertList** enterpriseRoots) = 0;
#endif
+ NS_IMETHOD BlockUntilLoadableRootsLoaded() = 0;
+
virtual ::already_AddRefed<mozilla::psm::SharedCertVerifier>
GetDefaultCertVerifier() = 0;
};
NS_DEFINE_STATIC_IID_ACCESSOR(nsINSSComponent, NS_INSSCOMPONENT_IID)
class nsNSSShutDownList;
// Implementation of the PSM component interface.
class nsNSSComponent final : public nsINSSComponent
, public nsIObserver
{
public:
+ // LoadLoadableRootsTask updates mLoadableRootsLoaded and
+ // mLoadableRootsLoadedResult and then signals mLoadableRootsLoadedMonitor.
+ friend class LoadLoadableRootsTask;
+
NS_DEFINE_STATIC_CID_ACCESSOR( NS_NSSCOMPONENT_CID )
nsNSSComponent();
NS_DECL_THREADSAFE_ISUPPORTS
NS_DECL_NSIOBSERVER
nsresult Init();
@@ -130,16 +137,18 @@ public:
#endif
NS_IMETHOD IsCertContentSigningRoot(CERTCertificate* cert, bool& result) override;
#ifdef XP_WIN
NS_IMETHOD GetEnterpriseRoots(nsIX509CertList** enterpriseRoots) override;
#endif
+ NS_IMETHOD BlockUntilLoadableRootsLoaded() override;
+
::already_AddRefed<mozilla::psm::SharedCertVerifier>
GetDefaultCertVerifier() override;
// The following two methods are thread-safe.
static bool AreAnyWeakCiphersEnabled();
static void UseWeakCiphersOnSocket(PRFileDesc* fd);
static void FillTLSVersionRange(SSLVersionRange& rangeOut,
@@ -149,17 +158,16 @@ public:
protected:
virtual ~nsNSSComponent();
private:
nsresult InitializeNSS();
void ShutdownNSS();
- void LoadLoadableRoots();
void UnloadLoadableRoots();
void setValidationOptions(bool isInitialSetting);
nsresult setEnabledTLSVersions();
nsresult InitializePIPNSSBundle();
nsresult ConfigureInternalPKCS11Token();
nsresult RegisterObservers();
void MaybeEnableFamilySafetyCompatibility();
@@ -170,16 +178,21 @@ private:
nsresult MaybeImportFamilySafetyRoot(PCCERT_CONTEXT certificate,
bool& wasFamilySafetyRoot);
nsresult LoadFamilySafetyRoot();
void UnloadFamilySafetyRoot();
void UnloadEnterpriseRoots(const mozilla::MutexAutoLock& proofOfLock);
#endif // XP_WIN
+ // mLoadableRootsLoadedMonitor protects mLoadableRootsLoaded.
+ mozilla::Monitor mLoadableRootsLoadedMonitor;
+ bool mLoadableRootsLoaded;
+ nsresult mLoadableRootsLoadedResult;
+
// mMutex protects all members that are accessed from more than one thread.
// While this lock is held, the same thread must not attempt to acquire a
// nsNSSShutDownPreventionLock (acquiring a nsNSSShutDownPreventionLock and
// then acquiring this lock is fine).
mozilla::Mutex mMutex;
// The following members are accessed from more than one thread:
nsCOMPtr<nsIStringBundle> mPIPNSSBundle;
@@ -197,16 +210,26 @@ private:
// The following members are accessed only on the main thread:
#ifndef MOZ_NO_SMART_CARDS
SmartCardThreadList* mThreadList;
#endif
static int mInstanceCount;
};
+inline nsresult
+BlockUntilLoadableRootsLoaded()
+{
+ nsCOMPtr<nsINSSComponent> component(do_GetService(PSM_COMPONENT_CONTRACTID));
+ if (!component) {
+ return NS_ERROR_FAILURE;
+ }
+ return component->BlockUntilLoadableRootsLoaded();
+}
+
class nsNSSErrors
{
public:
static const char* getDefaultErrorStringName(PRErrorCode err);
static const char* getOverrideErrorStringName(PRErrorCode aErrorCode);
static nsresult getErrorMessageFromCode(PRErrorCode err,
nsINSSComponent* component,
nsString& returnedMessage);
--- a/security/manager/ssl/nsPK11TokenDB.cpp
+++ b/security/manager/ssl/nsPK11TokenDB.cpp
@@ -448,16 +448,21 @@ nsPK11TokenDB::FindTokenByName(const nsA
{
NS_ENSURE_ARG_POINTER(_retval);
nsNSSShutDownPreventionLock locker;
if (isAlreadyShutDown()) {
return NS_ERROR_NOT_AVAILABLE;
}
+ nsresult rv = BlockUntilLoadableRootsLoaded();
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
if (tokenName.IsEmpty()) {
return NS_ERROR_ILLEGAL_VALUE;
}
UniquePK11SlotInfo slot(
PK11_FindSlotByName(PromiseFlatCString(tokenName).get()));
if (!slot) {
return NS_ERROR_FAILURE;
@@ -474,16 +479,21 @@ nsPK11TokenDB::ListTokens(nsISimpleEnume
{
NS_ENSURE_ARG_POINTER(_retval);
nsNSSShutDownPreventionLock locker;
if (isAlreadyShutDown()) {
return NS_ERROR_NOT_AVAILABLE;
}
+ nsresult rv = BlockUntilLoadableRootsLoaded();
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
nsCOMPtr<nsIMutableArray> array = do_CreateInstance(NS_ARRAY_CONTRACTID);
if (!array) {
return NS_ERROR_FAILURE;
}
*_retval = nullptr;
UniquePK11SlotList list(
--- a/security/manager/ssl/nsPKCS11Slot.cpp
+++ b/security/manager/ssl/nsPKCS11Slot.cpp
@@ -7,16 +7,17 @@
#include <string.h>
#include "mozilla/Casting.h"
#include "mozilla/Logging.h"
#include "mozilla/Telemetry.h"
#include "mozilla/Unused.h"
#include "nsCOMPtr.h"
#include "nsIMutableArray.h"
+#include "nsNSSComponent.h"
#include "nsPK11TokenDB.h"
#include "nsPromiseFlatString.h"
#include "secmod.h"
using mozilla::LogLevel;
extern mozilla::LazyLogModule gPIPNSSLog;
@@ -424,16 +425,21 @@ nsPKCS11ModuleDB::FindModuleByName(const
{
NS_ENSURE_ARG_POINTER(_retval);
nsNSSShutDownPreventionLock locker;
if (isAlreadyShutDown()) {
return NS_ERROR_NOT_AVAILABLE;
}
+ nsresult rv = BlockUntilLoadableRootsLoaded();
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
UniqueSECMODModule mod(SECMOD_FindModule(PromiseFlatCString(name).get()));
if (!mod) {
return NS_ERROR_FAILURE;
}
nsCOMPtr<nsIPKCS11Module> module = new nsPKCS11Module(mod.get());
module.forget(_retval);
return NS_OK;
@@ -448,16 +454,21 @@ nsPKCS11ModuleDB::FindSlotByName(const n
{
NS_ENSURE_ARG_POINTER(_retval);
nsNSSShutDownPreventionLock locker;
if (isAlreadyShutDown()) {
return NS_ERROR_NOT_AVAILABLE;
}
+ nsresult rv = BlockUntilLoadableRootsLoaded();
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
if (name.IsEmpty()) {
return NS_ERROR_ILLEGAL_VALUE;
}
UniquePK11SlotInfo slotInfo(
PK11_FindSlotByName(PromiseFlatCString(name).get()));
if (!slotInfo) {
return NS_ERROR_FAILURE;
@@ -473,16 +484,21 @@ nsPKCS11ModuleDB::ListModules(nsISimpleE
{
NS_ENSURE_ARG_POINTER(_retval);
nsNSSShutDownPreventionLock locker;
if (isAlreadyShutDown()) {
return NS_ERROR_NOT_AVAILABLE;
}
+ nsresult rv = BlockUntilLoadableRootsLoaded();
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
nsCOMPtr<nsIMutableArray> array = do_CreateInstance(NS_ARRAY_CONTRACTID);
if (!array) {
return NS_ERROR_FAILURE;
}
/* lock down the list for reading */
AutoSECMODListReadLock lock;
for (SECMODModuleList* list = SECMOD_GetDefaultModuleList(); list;
--- a/security/manager/ssl/tests/unit/test_pkcs11_module.js
+++ b/security/manager/ssl/tests/unit/test_pkcs11_module.js
@@ -56,17 +56,16 @@ function checkTestModuleExists() {
"Test module should be findable by name");
return testModule;
}
function checkModuleTelemetry(additionalExpectedModule = undefined) {
let expectedModules = [
"NSS Internal PKCS #11 Module",
- `${AppConstants.DLL_PREFIX}nssckbi${AppConstants.DLL_SUFFIX}`,
];
if (additionalExpectedModule) {
expectedModules.push(additionalExpectedModule);
}
expectedModules.sort();
let telemetryService = Cc["@mozilla.org/base/telemetry;1"]
.getService(Ci.nsITelemetry);
let telemetry = telemetryService.snapshotKeyedScalars(