From 8ba29d14739344f9caa8a06a031561af453d10b4 Mon Sep 17 00:00:00 2001 From: David Keeler Date: Wed, 13 Apr 2016 15:36:22 -0700 Subject: [PATCH] bug 1265113 - implement platform support for enterprise roots r=Cykesiopka,mhowell,rbarnes MozReview-Commit-ID: JKxwCjoH0Oa --HG-- extra : rebase_source : 9eaf3f1c5371e7b4b4df304bc6ce132ade5775da --- security/manager/ssl/nsIX509CertDB.idl | 6 + security/manager/ssl/nsNSSCertificateDB.cpp | 26 ++ security/manager/ssl/nsNSSComponent.cpp | 356 ++++++++++++++---- security/manager/ssl/nsNSSComponent.h | 33 +- .../ssl/tests/unit/test_enterprise_roots.js | 58 +++ security/manager/ssl/tests/unit/xpcshell.ini | 2 + 6 files changed, 413 insertions(+), 68 deletions(-) create mode 100644 security/manager/ssl/tests/unit/test_enterprise_roots.js diff --git a/security/manager/ssl/nsIX509CertDB.idl b/security/manager/ssl/nsIX509CertDB.idl index 808ea21fccf6..a5f621404b2a 100644 --- a/security/manager/ssl/nsIX509CertDB.idl +++ b/security/manager/ssl/nsIX509CertDB.idl @@ -390,4 +390,10 @@ interface nsIX509CertDB : nsISupports { * Get all the known certs in the database */ nsIX509CertList getCerts(); + + /* + * Get a list of imported enterprise root certificates (currently only + * implemented on Windows). + */ + nsIX509CertList getEnterpriseRoots(); }; diff --git a/security/manager/ssl/nsNSSCertificateDB.cpp b/security/manager/ssl/nsNSSCertificateDB.cpp index 36ce2f273543..6bbc2c49cf3d 100644 --- a/security/manager/ssl/nsNSSCertificateDB.cpp +++ b/security/manager/ssl/nsNSSCertificateDB.cpp @@ -1476,6 +1476,32 @@ nsNSSCertificateDB::GetCerts(nsIX509CertList **_retval) return NS_OK; } +NS_IMETHODIMP +nsNSSCertificateDB::GetEnterpriseRoots(nsIX509CertList** enterpriseRoots) +{ + MOZ_ASSERT(NS_IsMainThread()); + if (!NS_IsMainThread()) { + return NS_ERROR_NOT_SAME_THREAD; + } + + NS_ENSURE_ARG_POINTER(enterpriseRoots); + + nsNSSShutDownPreventionLock locker; + if (isAlreadyShutDown()) { + return NS_ERROR_NOT_AVAILABLE; + } + +#ifdef XP_WIN + nsCOMPtr psm(do_GetService(PSM_COMPONENT_CONTRACTID)); + if (!psm) { + return NS_ERROR_FAILURE; + } + return psm->GetEnterpriseRoots(enterpriseRoots); +#else + return NS_ERROR_NOT_IMPLEMENTED; +#endif +} + nsresult VerifyCertAtTime(nsIX509Cert* aCert, int64_t /*SECCertificateUsage*/ aUsage, diff --git a/security/manager/ssl/nsNSSComponent.cpp b/security/manager/ssl/nsNSSComponent.cpp index fad1076d624a..d90842f1575d 100644 --- a/security/manager/ssl/nsNSSComponent.cpp +++ b/security/manager/ssl/nsNSSComponent.cpp @@ -12,6 +12,7 @@ #include "SharedSSLState.h" #include "cert.h" #include "certdb.h" +#include "mozilla/ArrayUtils.h" #include "mozilla/Casting.h" #include "mozilla/Preferences.h" #include "mozilla/PublicSSL.h" @@ -19,6 +20,7 @@ #include "mozilla/StaticPtr.h" #include "mozilla/SyncRunnable.h" #include "mozilla/Telemetry.h" +#include "mozilla/unused.h" #include "nsAppDirectoryServiceDefs.h" #include "nsCRT.h" #include "nsCertVerificationThread.h" @@ -442,7 +444,7 @@ GetUserSid(nsAString& sidString) } char sid_buffer[SECURITY_MAX_SID_SIZE]; SID* sid = BitwiseCast(sid_buffer); - DWORD cbSid = MOZ_ARRAY_LENGTH(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 @@ -652,31 +654,50 @@ AccountHasFamilySafetyEnabled(bool& enabled) return NS_OK; } -const char* kImportedFamilySafetyRootPref = - "security.family_safety.imported_root.db_key"; +// It would be convenient to just use nsIX509CertDB in the following code. +// However, since nsIX509CertDB depends on nsNSSComponent initialization (and +// since this code runs during that initialization), we can't use it. Instead, +// we can use NSS APIs directly (as long as we're called late enough in +// nsNSSComponent initialization such that those APIs are safe to use). -static nsresult -MaybeImportFamilySafetyRoot(PCCERT_CONTEXT certificate, - bool& wasFamilySafetyRoot) +// Helper function to convert a PCCERT_CONTEXT (i.e. a certificate obtained via +// a Windows API) to a temporary CERTCertificate (i.e. a certificate for use +// with NSS APIs). +static UniqueCERTCertificate +PCCERT_CONTEXTToCERTCertificate(PCCERT_CONTEXT pccert) { - MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("MaybeImportFamilySafetyRoot")); - wasFamilySafetyRoot = false; - - // It would be convenient to just use nsIX509CertDB here. However, since - // nsIX509CertDB depends on nsNSSComponent initialization, we can't use it. - // Instead, we can use NSS APIs directly (as long as we're called late enough - // in nsNSSComponent initialization such that those APIs are safe to use). + MOZ_ASSERT(pccert); + if (!pccert) { + return nullptr; + } SECItem derCert = { siBuffer, - certificate->pbCertEncoded, - certificate->cbCertEncoded + pccert->pbCertEncoded, + pccert->cbCertEncoded }; - UniqueCERTCertificate nssCertificate( + return UniqueCERTCertificate( CERT_NewTempCertificate(CERT_GetDefaultCertDB(), &derCert, nullptr, // nickname unnecessary false, // not permanent true)); // copy DER +} + +static const char* kMicrosoftFamilySafetyCN = "Microsoft Family Safety"; + +nsresult +nsNSSComponent::MaybeImportFamilySafetyRoot(PCCERT_CONTEXT certificate, + bool& wasFamilySafetyRoot) +{ + MOZ_ASSERT(NS_IsMainThread()); + if (!NS_IsMainThread()) { + return NS_ERROR_NOT_SAME_THREAD; + } + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("MaybeImportFamilySafetyRoot")); + wasFamilySafetyRoot = false; + + UniqueCERTCertificate nssCertificate( + PCCERT_CONTEXTToCERTCertificate(certificate)); if (!nssCertificate) { MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("couldn't decode certificate")); return NS_ERROR_FAILURE; @@ -685,34 +706,30 @@ MaybeImportFamilySafetyRoot(PCCERT_CONTEXT certificate, UniquePORTString subjectName(CERT_GetCommonName(&nssCertificate->subject)); MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("subject name is '%s'", subjectName.get())); - if (nsCRT::strcmp(subjectName.get(), "Microsoft Family Safety") == 0) { + if (nsCRT::strcmp(subjectName.get(), kMicrosoftFamilySafetyCN) == 0) { wasFamilySafetyRoot = true; CERTCertTrust trust = { CERTDB_TRUSTED_CA | CERTDB_VALID_CA | CERTDB_USER, 0, 0 }; - SECStatus srv = __CERT_AddTempCertToPerm( - nssCertificate.get(), "Microsoft Family Safety", &trust); - if (srv != SECSuccess) { + if (CERT_ChangeCertTrust(nullptr, nssCertificate.get(), &trust) + != SECSuccess) { MOZ_LOG(gPIPNSSLog, LogLevel::Debug, - ("couldn't permanently add certificate")); + ("couldn't trust certificate for TLS server auth")); return NS_ERROR_FAILURE; } - nsAutoCString dbKey; - nsresult rv = nsNSSCertificate::GetDbKey(nssCertificate, dbKey); - if (NS_FAILED(rv)) { - MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("GetDbKey failed")); - return rv; - } - Preferences::SetCString(kImportedFamilySafetyRootPref, dbKey); + MOZ_ASSERT(!mFamilySafetyRoot); + mFamilySafetyRoot = Move(nssCertificate); MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("added Family Safety root")); } return NS_OK; } // Because HCERTSTORE is just a typedef void*, we can't use any of the nice -// scoped pointer templates. +// scoped or unique pointer templates. To elaborate, any attempt would +// instantiate those templates with T = void. When T gets used in the context +// of T&, this results in void&, which isn't legal. class ScopedCertStore final { public: @@ -734,25 +751,25 @@ private: HCERTSTORE certstore; }; -static const wchar_t* WindowsDefaultRootStoreName = L"ROOT"; +static const wchar_t* kWindowsDefaultRootStoreName = L"ROOT"; -static nsresult -LoadFamilySafetyRoot() +nsresult +nsNSSComponent::LoadFamilySafetyRoot() { ScopedCertStore certstore( - CertOpenSystemStore(0, WindowsDefaultRootStoreName)); + CertOpenSystemStore(0, kWindowsDefaultRootStoreName)); if (!certstore.get()) { MOZ_LOG(gPIPNSSLog, LogLevel::Debug, - ("couldn't get certstore '%S'", WindowsDefaultRootStoreName)); + ("couldn't get certstore '%S'", kWindowsDefaultRootStoreName)); return NS_ERROR_FAILURE; } // Any resources held by the certificate are released by the next call to // CertFindCertificateInStore. PCCERT_CONTEXT certificate = nullptr; - while (certificate = CertFindCertificateInStore(certstore.get(), - X509_ASN_ENCODING, 0, - CERT_FIND_ANY, nullptr, - certificate)) { + while ((certificate = CertFindCertificateInStore(certstore.get(), + X509_ASN_ENCODING, 0, + CERT_FIND_ANY, nullptr, + certificate))) { bool wasFamilySafetyRoot = false; nsresult rv = MaybeImportFamilySafetyRoot(certificate, wasFamilySafetyRoot); @@ -763,38 +780,32 @@ LoadFamilySafetyRoot() return NS_ERROR_FAILURE; } -static void -UnloadFamilySafetyRoot() +void +nsNSSComponent::UnloadFamilySafetyRoot() { + MOZ_ASSERT(NS_IsMainThread()); + if (!NS_IsMainThread()) { + return; + } MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("UnloadFamilySafetyRoot")); - nsAdoptingCString dbKey = Preferences::GetCString( - kImportedFamilySafetyRootPref); - if (!dbKey || dbKey.IsEmpty()) { - MOZ_LOG(gPIPNSSLog, LogLevel::Debug, - ("Family Safety root wasn't previously imported")); + if (!mFamilySafetyRoot) { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("Family Safety Root wasn't present")); return; } - UniqueCERTCertificate cert; - nsresult rv = nsNSSCertificateDB::FindCertByDBKey(dbKey, cert); - if (NS_FAILED(rv)) { + // It would be intuitive to set the trust to { 0, 0, 0 } here. However, this + // doesn't work for temporary certificates because CERT_ChangeCertTrust first + // looks up the current trust settings in the permanent cert database, finds + // that such trust doesn't exist, considers the current trust to be + // { 0, 0, 0 }, and decides that it doesn't need to update the trust since + // they're the same. To work around this, we set a non-zero flag to ensure + // that the trust will get updated. + CERTCertTrust trust = { CERTDB_USER, 0, 0 }; + if (CERT_ChangeCertTrust(nullptr, mFamilySafetyRoot.get(), &trust) + != SECSuccess) { MOZ_LOG(gPIPNSSLog, LogLevel::Debug, - ("finding previously-imported Family Safety root failed")); - return; + ("couldn't untrust certificate for TLS server auth")); } - if (!cert) { - MOZ_LOG(gPIPNSSLog, LogLevel::Debug, - ("previously-imported Family Safety root not found")); - return; - } - SECStatus srv = SEC_DeletePermCertificate(cert.get()); - if (srv != SECSuccess) { - MOZ_LOG(gPIPNSSLog, LogLevel::Debug, - ("couldn't delete previously-imported Family Safety root")); - return; - } - MOZ_LOG(gPIPNSSLog, LogLevel::Debug, - ("deleted previously-imported Family Safety root")); - Preferences::ClearUser(kImportedFamilySafetyRootPref); + mFamilySafetyRoot = nullptr; } #endif // XP_WIN @@ -812,8 +823,8 @@ const char* kFamilySafetyModePref = "security.family_safety.mode"; // 5: Family Safety was enabled // 6: failed to import the Family Safety root // 7: successfully imported the root -static void -MaybeEnableFamilySafetyCompatibility() +void +nsNSSComponent::MaybeEnableFamilySafetyCompatibility() { #ifdef XP_WIN UnloadFamilySafetyRoot(); @@ -853,6 +864,211 @@ MaybeEnableFamilySafetyCompatibility() #endif // XP_WIN } +#ifdef XP_WIN +// Helper function to determine if the OS considers the given certificate to be +// a trust anchor for TLS server auth certificates. This is to be used in the +// context of importing what are presumed to be root certificates from the OS. +// If this function returns true but it turns out that the given certificate is +// in some way unsuitable to issue certificates, mozilla::pkix will never build +// a valid chain that includes the certificate, so importing it even if it +// isn't a valid CA poses no risk. +static bool +CertIsTrustAnchorForTLSServerAuth(PCCERT_CONTEXT certificate) +{ + MOZ_ASSERT(certificate); + if (!certificate) { + return false; + } + + PCCERT_CHAIN_CONTEXT pChainContext = nullptr; + CERT_ENHKEY_USAGE enhkeyUsage; + memset(&enhkeyUsage, 0, sizeof(CERT_ENHKEY_USAGE)); + LPSTR identifiers[] = { + "1.3.6.1.5.5.7.3.1", // id-kp-serverAuth + }; + enhkeyUsage.cUsageIdentifier = ArrayLength(identifiers); + enhkeyUsage.rgpszUsageIdentifier = identifiers; + CERT_USAGE_MATCH certUsage; + memset(&certUsage, 0, sizeof(CERT_USAGE_MATCH)); + certUsage.dwType = USAGE_MATCH_TYPE_AND; + certUsage.Usage = enhkeyUsage; + CERT_CHAIN_PARA chainPara; + memset(&chainPara, 0, sizeof(CERT_CHAIN_PARA)); + chainPara.cbSize = sizeof(CERT_CHAIN_PARA); + chainPara.RequestedUsage = certUsage; + + if (!CertGetCertificateChain(nullptr, certificate, nullptr, nullptr, + &chainPara, 0, nullptr, &pChainContext)) { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("CertGetCertificateChain failed")); + return false; + } + bool trusted = pChainContext->TrustStatus.dwErrorStatus == + CERT_TRUST_NO_ERROR; + bool isRoot = pChainContext->cChain == 1; + CertFreeCertificateChain(pChainContext); + if (trusted && isRoot) { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, + ("certificate is trust anchor for TLS server auth")); + return true; + } + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, + ("certificate not trust anchor for TLS server auth")); + return false; +} + +void +nsNSSComponent::UnloadEnterpriseRoots() +{ + MOZ_ASSERT(NS_IsMainThread()); + if (!NS_IsMainThread()) { + return; + } + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("UnloadEnterpriseRoots")); + if (!mEnterpriseRoots) { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("no enterprise roots were present")); + return; + } + // It would be intuitive to set the trust to { 0, 0, 0 } here. However, this + // doesn't work for temporary certificates because CERT_ChangeCertTrust first + // looks up the current trust settings in the permanent cert database, finds + // that such trust doesn't exist, considers the current trust to be + // { 0, 0, 0 }, and decides that it doesn't need to update the trust since + // they're the same. To work around this, we set a non-zero flag to ensure + // that the trust will get updated. + CERTCertTrust trust = { CERTDB_USER, 0, 0 }; + for (CERTCertListNode* n = CERT_LIST_HEAD(mEnterpriseRoots.get()); + !CERT_LIST_END(n, mEnterpriseRoots.get()); n = CERT_LIST_NEXT(n)) { + if (!n || !n->cert) { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, + ("library failure: CERTCertListNode null or lacks cert")); + continue; + } + if (CERT_ChangeCertTrust(nullptr, n->cert, &trust) != SECSuccess) { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, + ("couldn't untrust certificate for TLS server auth")); + } + } + mEnterpriseRoots = nullptr; + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("unloaded enterprise roots")); +} + +NS_IMETHODIMP +nsNSSComponent::GetEnterpriseRoots(nsIX509CertList** enterpriseRoots) +{ + MOZ_ASSERT(NS_IsMainThread()); + if (!NS_IsMainThread()) { + return NS_ERROR_NOT_SAME_THREAD; + } + NS_ENSURE_ARG_POINTER(enterpriseRoots); + + nsNSSShutDownPreventionLock lock; + // nsNSSComponent isn't a nsNSSShutDownObject, so we can't check + // isAlreadyShutDown(). However, since mEnterpriseRoots is cleared when NSS + // shuts down, we can use that as a proxy for checking for NSS shutdown. + // (Of course, it may also be the case that no enterprise roots were imported, + // so we should just return a null list and NS_OK in this case.) + if (!mEnterpriseRoots) { + *enterpriseRoots = nullptr; + return NS_OK; + } + UniqueCERTCertList enterpriseRootsCopy( + nsNSSCertList::DupCertList(mEnterpriseRoots, lock)); + if (!enterpriseRootsCopy) { + return NS_ERROR_FAILURE; + } + nsCOMPtr enterpriseRootsCertList( + new nsNSSCertList(Move(enterpriseRootsCopy), lock)); + if (!enterpriseRootsCertList) { + return NS_ERROR_FAILURE; + } + enterpriseRootsCertList.forget(enterpriseRoots); + return NS_OK; +} +#endif // XP_WIN + +static const char* kEnterpriseRootModePref = "security.enterprise_roots.enabled"; + +void +nsNSSComponent::MaybeImportEnterpriseRoots() +{ +#ifdef XP_WIN + MOZ_ASSERT(NS_IsMainThread()); + if (!NS_IsMainThread()) { + return; + } + UnloadEnterpriseRoots(); + bool importEnterpriseRoots = Preferences::GetBool(kEnterpriseRootModePref, + false); + if (!importEnterpriseRoots) { + return; + } + DWORD flags = CERT_SYSTEM_STORE_LOCAL_MACHINE | + CERT_STORE_OPEN_EXISTING_FLAG | + CERT_STORE_READONLY_FLAG; + // The certificate store being opened should consist only of certificates + // added by a user or administrator and not any certificates that are part + // of Microsoft's root store program. + // The 3rd parameter to CertOpenStore should be NULL according to + // https://msdn.microsoft.com/en-us/library/windows/desktop/aa376559%28v=vs.85%29.aspx + ScopedCertStore enterpriseRootStore(CertOpenStore( + CERT_STORE_PROV_SYSTEM_REGISTRY_W, 0, NULL, flags, + kWindowsDefaultRootStoreName)); + if (!enterpriseRootStore.get()) { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("failed to open enterprise root store")); + return; + } + MOZ_ASSERT(!mEnterpriseRoots); + mEnterpriseRoots.reset(CERT_NewCertList()); + CERTCertTrust trust = { + CERTDB_TRUSTED_CA | CERTDB_VALID_CA | CERTDB_USER, + 0, + 0 + }; + PCCERT_CONTEXT certificate = nullptr; + uint32_t numImported = 0; + while ((certificate = CertFindCertificateInStore(enterpriseRootStore.get(), + X509_ASN_ENCODING, 0, + CERT_FIND_ANY, nullptr, + certificate))) { + if (!CertIsTrustAnchorForTLSServerAuth(certificate)) { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, + ("skipping cert not trust anchor for TLS server auth")); + continue; + } + UniqueCERTCertificate nssCertificate( + PCCERT_CONTEXTToCERTCertificate(certificate)); + if (!nssCertificate) { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("couldn't decode certificate")); + continue; + } + // Don't import the Microsoft Family Safety root (this prevents the + // Enterprise Roots feature from interacting poorly with the Family + // Safety support). + UniquePORTString subjectName( + CERT_GetCommonName(&nssCertificate->subject)); + if (nsCRT::strcmp(subjectName.get(), kMicrosoftFamilySafetyCN) == 0) { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("skipping Family Safety Root")); + continue; + } + if (CERT_AddCertToListTail(mEnterpriseRoots.get(), nssCertificate.get()) + != SECSuccess) { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("couldn't add cert to list")); + continue; + } + if (CERT_ChangeCertTrust(nullptr, nssCertificate.get(), &trust) + != SECSuccess) { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, + ("couldn't trust certificate for TLS server auth")); + } + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("Imported '%s'", subjectName.get())); + numImported++; + // now owned by mEnterpriseRoots + Unused << nssCertificate.release(); + } + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("imported %u roots", numImported)); +#endif // XP_WIN +} + void nsNSSComponent::LoadLoadableRoots() { @@ -1559,6 +1775,7 @@ nsNSSComponent::InitializeNSS() LoadLoadableRoots(); MaybeEnableFamilySafetyCompatibility(); + MaybeImportEnterpriseRoots(); ConfigureTLSSessionIdentifiers(); @@ -1657,6 +1874,11 @@ nsNSSComponent::ShutdownNSS() Preferences::RemoveObserver(this, "security."); +#ifdef XP_WIN + mFamilySafetyRoot = nullptr; + mEnterpriseRoots = nullptr; +#endif + #ifndef MOZ_NO_SMART_CARDS ShutdownSmartCardThreads(); #endif @@ -1800,6 +2022,8 @@ nsNSSComponent::Observe(nsISupports* aSubject, const char* aTopic, MutexAutoLock lock(mutex); mContentSigningRootHash = Preferences::GetString("security.content.signature.root_hash"); + } else if (prefName.Equals(kEnterpriseRootModePref)) { + MaybeImportEnterpriseRoots(); } else { clearSessionCache = false; } diff --git a/security/manager/ssl/nsNSSComponent.h b/security/manager/ssl/nsNSSComponent.h index 0a326edb64d1..5f13e356e5f8 100644 --- a/security/manager/ssl/nsNSSComponent.h +++ b/security/manager/ssl/nsNSSComponent.h @@ -7,18 +7,25 @@ #ifndef _nsNSSComponent_h_ #define _nsNSSComponent_h_ +#include "ScopedNSSTypes.h" +#include "SharedCertVerifier.h" #include "mozilla/Mutex.h" #include "mozilla/RefPtr.h" #include "nsCOMPtr.h" -#include "nsIStringBundle.h" #include "nsIObserver.h" +#include "nsIStringBundle.h" #include "nsNSSCallbacks.h" -#include "SharedCertVerifier.h" #include "prerror.h" #include "sslt.h" +#ifdef XP_WIN +#include "windows.h" // this needs to be before the following includes +#include "wincrypt.h" +#endif // XP_WIN + class nsIDOMWindow; class nsIPrompt; +class nsIX509CertList; class SmartCardThreadList; namespace mozilla { namespace psm { @@ -87,6 +94,10 @@ public: NS_IMETHOD IsCertContentSigningRoot(CERTCertificate* cert, bool& result) = 0; +#ifdef XP_WIN + NS_IMETHOD GetEnterpriseRoots(nsIX509CertList** enterpriseRoots) = 0; +#endif + virtual ::already_AddRefed GetDefaultCertVerifier() = 0; }; @@ -141,6 +152,10 @@ public: NS_IMETHOD IsCertContentSigningRoot(CERTCertificate* cert, bool& result) override; +#ifdef XP_WIN + NS_IMETHOD GetEnterpriseRoots(nsIX509CertList** enterpriseRoots) override; +#endif + ::already_AddRefed GetDefaultCertVerifier() override; @@ -171,6 +186,20 @@ private: void DoProfileBeforeChange(); + void MaybeEnableFamilySafetyCompatibility(); + void MaybeImportEnterpriseRoots(); +#ifdef XP_WIN + nsresult MaybeImportFamilySafetyRoot(PCCERT_CONTEXT certificate, + bool& wasFamilySafetyRoot); + nsresult LoadFamilySafetyRoot(); + void UnloadFamilySafetyRoot(); + + void UnloadEnterpriseRoots(); + + mozilla::UniqueCERTCertificate mFamilySafetyRoot; + mozilla::UniqueCERTCertList mEnterpriseRoots; +#endif // XP_WIN + mozilla::Mutex mutex; nsCOMPtr mPIPNSSBundle; diff --git a/security/manager/ssl/tests/unit/test_enterprise_roots.js b/security/manager/ssl/tests/unit/test_enterprise_roots.js new file mode 100644 index 000000000000..de1b3a529132 --- /dev/null +++ b/security/manager/ssl/tests/unit/test_enterprise_roots.js @@ -0,0 +1,58 @@ +// -*- indent-tabs-mode: nil; js-indent-level: 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/. + +"use strict"; + +// Tests enterprise root certificate support. When configured to do so, the +// platform will attempt to find and import enterprise root certificates. This +// feature is specific to Windows. + +do_get_profile(); // must be called before getting nsIX509CertDB + +function check_no_enterprise_roots_imported(certDB, dbKey = undefined) { + let enterpriseRoots = certDB.getEnterpriseRoots(); + equal(enterpriseRoots, null, "should not have imported any enterprise roots"); + if (dbKey) { + let cert = certDB.findCertByDBKey(dbKey); + // If the garbage-collector hasn't run, there may be reachable copies of + // imported enterprise root certificates. If so, they shouldn't be trusted + // to issue TLS server auth certificates. + if (cert) { + ok(!certDB.isCertTrusted(cert, Ci.nsIX509Cert.CA_CERT, + Ci.nsIX509CertDB.TRUSTED_SSL), + "previously-imported enterprise root shouldn't be trusted to issue " + + "TLS server auth certificates"); + } + } +} + +function check_some_enterprise_roots_imported(certDB) { + let enterpriseRoots = certDB.getEnterpriseRoots(); + notEqual(enterpriseRoots, null, "should have imported some enterprise roots"); + let enumerator = enterpriseRoots.getEnumerator(); + let foundNonBuiltIn = false; + let savedDBKey = null; + while (enumerator.hasMoreElements()) { + let cert = enumerator.getNext().QueryInterface(Ci.nsIX509Cert); + if (!cert.isBuiltInRoot && !savedDBKey) { + foundNonBuiltIn = true; + savedDBKey = cert.dbKey; + do_print("saving dbKey from " + cert.commonName); + } + } + ok(foundNonBuiltIn, "should have found non-built-in root"); + return savedDBKey; +} + +function run_test() { + let certDB = Cc["@mozilla.org/security/x509certdb;1"] + .getService(Ci.nsIX509CertDB); + Services.prefs.setBoolPref("security.enterprise_roots.enabled", false); + check_no_enterprise_roots_imported(certDB); + Services.prefs.setBoolPref("security.enterprise_roots.enabled", true); + let savedDBKey = check_some_enterprise_roots_imported(certDB); + Services.prefs.setBoolPref("security.enterprise_roots.enabled", false); + check_no_enterprise_roots_imported(certDB, savedDBKey); +} diff --git a/security/manager/ssl/tests/unit/xpcshell.ini b/security/manager/ssl/tests/unit/xpcshell.ini index 30a9d9b11dd1..c63a88d772e1 100644 --- a/security/manager/ssl/tests/unit/xpcshell.ini +++ b/security/manager/ssl/tests/unit/xpcshell.ini @@ -57,6 +57,8 @@ skip-if = toolkit == 'android' || buildapp == 'b2g' [test_constructX509FromBase64.js] [test_content_signing.js] [test_datasignatureverifier.js] +[test_enterprise_roots.js] +skip-if = os != 'win' # tests a Windows-specific feature [test_ev_certs.js] run-sequentially = hardcoded ports [test_getchain.js]