/* -*- 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 "EnterpriseRoots.h" #include "mozilla/ArrayUtils.h" #include "mozilla/Logging.h" #include "mozilla/Unused.h" #include "mozpkix/Result.h" #include "nsThreadUtils.h" #ifdef MOZ_WIDGET_ANDROID # include "mozilla/java/EnterpriseRootsWrappers.h" #endif // MOZ_WIDGET_ANDROID #ifdef XP_MACOSX # include # include "KeychainSecret.h" // for ScopedCFType # include "nsCocoaFeatures.h" #endif // XP_MACOSX #ifdef XP_WIN # include # include #endif // XP_WIN extern mozilla::LazyLogModule gPIPNSSLog; using namespace mozilla; nsresult EnterpriseCert::Init(const uint8_t* data, size_t len, bool isRoot) { mDER.clear(); if (!mDER.append(data, len)) { return NS_ERROR_OUT_OF_MEMORY; } mIsRoot = isRoot; return NS_OK; } nsresult EnterpriseCert::Init(const EnterpriseCert& orig) { return Init(orig.mDER.begin(), orig.mDER.length(), orig.mIsRoot); } nsresult EnterpriseCert::CopyBytes(nsTArray& dest) const { dest.Clear(); // XXX(Bug 1631371) Check if this should use a fallible operation as it // pretended earlier, or change the return type to void. dest.AppendElements(mDER.begin(), mDER.length()); return NS_OK; } pkix::Result EnterpriseCert::GetInput(pkix::Input& input) const { return input.Init(mDER.begin(), mDER.length()); } bool EnterpriseCert::GetIsRoot() const { return mIsRoot; } #ifdef XP_WIN const wchar_t* kWindowsDefaultRootStoreNames[] = {L"ROOT", L"CA"}; // 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 void CertIsTrustAnchorForTLSServerAuth(PCCERT_CONTEXT certificate, bool& isTrusted, bool& isRoot) { isTrusted = false; isRoot = false; MOZ_ASSERT(certificate); if (!certificate) { return; } PCCERT_CHAIN_CONTEXT pChainContext = nullptr; CERT_ENHKEY_USAGE enhkeyUsage; memset(&enhkeyUsage, 0, sizeof(CERT_ENHKEY_USAGE)); LPCSTR identifiers[] = { "1.3.6.1.5.5.7.3.1", // id-kp-serverAuth }; enhkeyUsage.cUsageIdentifier = ArrayLength(identifiers); enhkeyUsage.rgpszUsageIdentifier = const_cast(identifiers); // -Wwritable-strings 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; // Disable anything that could result in network I/O. DWORD flags = CERT_CHAIN_REVOCATION_CHECK_CACHE_ONLY | CERT_CHAIN_CACHE_ONLY_URL_RETRIEVAL | CERT_CHAIN_DISABLE_AUTH_ROOT_AUTO_UPDATE | CERT_CHAIN_DISABLE_AIA; if (!CertGetCertificateChain(nullptr, certificate, nullptr, nullptr, &chainPara, flags, nullptr, &pChainContext)) { MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("CertGetCertificateChain failed")); return; } isTrusted = pChainContext->TrustStatus.dwErrorStatus == CERT_TRUST_NO_ERROR; if (isTrusted && pChainContext->cChain > 0) { // The so-called "final chain" is what we're after: // https://docs.microsoft.com/en-us/windows/desktop/api/wincrypt/ns-wincrypt-_cert_chain_context CERT_SIMPLE_CHAIN* finalChain = pChainContext->rgpChain[pChainContext->cChain - 1]; // This is a root if the final chain consists of only one certificate (i.e. // this one). isRoot = finalChain->cElement == 1; } CertFreeCertificateChain(pChainContext); } // Because HCERTSTORE is just a typedef void*, we can't use any of the nice // 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: explicit ScopedCertStore(HCERTSTORE certstore) : certstore(certstore) {} ~ScopedCertStore() { CertCloseStore(certstore, 0); } HCERTSTORE get() { return certstore; } private: ScopedCertStore(const ScopedCertStore&) = delete; ScopedCertStore& operator=(const ScopedCertStore&) = delete; HCERTSTORE certstore; }; // Loads the enterprise roots at the registry location corresponding to the // given location flag. // Supported flags are: // CERT_SYSTEM_STORE_LOCAL_MACHINE // (for HKLM\SOFTWARE\Microsoft\SystemCertificates) // CERT_SYSTEM_STORE_LOCAL_MACHINE_GROUP_POLICY // (for HKLM\SOFTWARE\Policy\Microsoft\SystemCertificates) // CERT_SYSTEM_STORE_LOCAL_MACHINE_ENTERPRISE // (for HKLM\SOFTWARE\Microsoft\EnterpriseCertificates) // CERT_SYSTEM_STORE_CURRENT_USER // (for HKCU\SOFTWARE\Microsoft\SystemCertificates) // CERT_SYSTEM_STORE_CURRENT_USER_GROUP_POLICY // (for HKCU\SOFTWARE\Policy\Microsoft\SystemCertificates) static void GatherEnterpriseCertsForLocation(DWORD locationFlag, Vector& certs) { MOZ_ASSERT(locationFlag == CERT_SYSTEM_STORE_LOCAL_MACHINE || locationFlag == CERT_SYSTEM_STORE_LOCAL_MACHINE_GROUP_POLICY || locationFlag == CERT_SYSTEM_STORE_LOCAL_MACHINE_ENTERPRISE || locationFlag == CERT_SYSTEM_STORE_CURRENT_USER || locationFlag == CERT_SYSTEM_STORE_CURRENT_USER_GROUP_POLICY, "unexpected locationFlag for GatherEnterpriseRootsForLocation"); if (!(locationFlag == CERT_SYSTEM_STORE_LOCAL_MACHINE || locationFlag == CERT_SYSTEM_STORE_LOCAL_MACHINE_GROUP_POLICY || locationFlag == CERT_SYSTEM_STORE_LOCAL_MACHINE_ENTERPRISE || locationFlag == CERT_SYSTEM_STORE_CURRENT_USER || locationFlag == CERT_SYSTEM_STORE_CURRENT_USER_GROUP_POLICY)) { return; } DWORD flags = locationFlag | 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 for (auto name : kWindowsDefaultRootStoreNames) { ScopedCertStore enterpriseRootStore( CertOpenStore(CERT_STORE_PROV_SYSTEM_REGISTRY_W, 0, NULL, flags, name)); if (!enterpriseRootStore.get()) { MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("failed to open enterprise root store")); continue; } PCCERT_CONTEXT certificate = nullptr; uint32_t numImported = 0; while ((certificate = CertFindCertificateInStore( enterpriseRootStore.get(), X509_ASN_ENCODING, 0, CERT_FIND_ANY, nullptr, certificate))) { bool isTrusted; bool isRoot; CertIsTrustAnchorForTLSServerAuth(certificate, isTrusted, isRoot); if (!isTrusted) { MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("skipping cert not trusted for TLS server auth")); continue; } EnterpriseCert enterpriseCert; if (NS_FAILED(enterpriseCert.Init(certificate->pbCertEncoded, certificate->cbCertEncoded, isRoot))) { // Best-effort. We probably ran out of memory. continue; } if (!certs.append(std::move(enterpriseCert))) { // Best-effort again. continue; } numImported++; } MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("imported %u certs from %S", numImported, name)); } } static void GatherEnterpriseCertsWindows(Vector& certs) { GatherEnterpriseCertsForLocation(CERT_SYSTEM_STORE_LOCAL_MACHINE, certs); GatherEnterpriseCertsForLocation(CERT_SYSTEM_STORE_LOCAL_MACHINE_GROUP_POLICY, certs); GatherEnterpriseCertsForLocation(CERT_SYSTEM_STORE_LOCAL_MACHINE_ENTERPRISE, certs); GatherEnterpriseCertsForLocation(CERT_SYSTEM_STORE_CURRENT_USER, certs); GatherEnterpriseCertsForLocation(CERT_SYSTEM_STORE_CURRENT_USER_GROUP_POLICY, certs); } #endif // XP_WIN #ifdef XP_MACOSX OSStatus GatherEnterpriseCertsMacOS(Vector& certs) { // The following builds a search dictionary corresponding to: // { class: "certificate", // match limit: "match all", // policy: "SSL (TLS)", // only include trusted certificates: true } // This operates on items that have been added to the keychain and thus gives // us all 3rd party certificates that have been trusted for SSL (TLS), which // is what we want (thus we don't import built-in root certificates that ship // with the OS). const CFStringRef keys[] = {kSecClass, kSecMatchLimit, kSecMatchPolicy, kSecMatchTrustedOnly}; // https://developer.apple.com/documentation/security/1392592-secpolicycreatessl ScopedCFType sslPolicy(SecPolicyCreateSSL(true, nullptr)); const void* values[] = {kSecClassCertificate, kSecMatchLimitAll, sslPolicy.get(), kCFBooleanTrue}; static_assert(ArrayLength(keys) == ArrayLength(values), "mismatched SecItemCopyMatching key/value array sizes"); // https://developer.apple.com/documentation/corefoundation/1516782-cfdictionarycreate ScopedCFType searchDictionary(CFDictionaryCreate( nullptr, (const void**)&keys, (const void**)&values, ArrayLength(keys), &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks)); CFTypeRef items; // https://developer.apple.com/documentation/security/1398306-secitemcopymatching OSStatus rv = SecItemCopyMatching(searchDictionary.get(), &items); if (rv != errSecSuccess) { MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("SecItemCopyMatching failed")); return rv; } // If given a match limit greater than 1 (which we did), SecItemCopyMatching // returns a CFArrayRef. ScopedCFType arr(reinterpret_cast(items)); CFIndex count = CFArrayGetCount(arr.get()); uint32_t numImported = 0; for (CFIndex i = 0; i < count; i++) { const CFTypeRef c = CFArrayGetValueAtIndex(arr.get(), i); SecTrustRef trust; rv = SecTrustCreateWithCertificates(c, sslPolicy.get(), &trust); if (rv != errSecSuccess) { MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("SecTrustCreateWithCertificates failed")); continue; } ScopedCFType trustHandle(trust); // Disable AIA chasing to avoid network I/O. rv = SecTrustSetNetworkFetchAllowed(trustHandle.get(), false); if (rv != errSecSuccess) { MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("SecTrustSetNetworkFetchAllowed failed")); continue; } bool isTrusted = false; bool fallBackToDeprecatedAPI = true; # if defined MAC_OS_X_VERSION_10_14 if (nsCocoaFeatures::OnMojaveOrLater()) { // This is an awkward way to express what we want, but the compiler // complains if we try to put __builtin_available in a compound logical // statement. if (__builtin_available(macOS 10.14, *)) { isTrusted = SecTrustEvaluateWithError(trustHandle.get(), nullptr); fallBackToDeprecatedAPI = false; } } # endif // MAC_OS_X_VERSION_10_14 if (fallBackToDeprecatedAPI) { SecTrustResultType result; rv = SecTrustEvaluate(trustHandle.get(), &result); if (rv != errSecSuccess) { MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("SecTrustEvaluate failed")); continue; } // 'kSecTrustResultProceed' means the trust evaluation succeeded and that // this is a trusted certificate. // 'kSecTrustResultUnspecified' means the trust evaluation succeeded and // that this certificate inherits its trust. isTrusted = result == kSecTrustResultProceed || result == kSecTrustResultUnspecified; } if (!isTrusted) { MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("skipping cert not trusted")); continue; } CFIndex count = SecTrustGetCertificateCount(trustHandle.get()); bool isRoot = count == 1; // Because we asked for certificates, each CFTypeRef in the array is really // a SecCertificateRef. const SecCertificateRef s = (const SecCertificateRef)c; ScopedCFType der(SecCertificateCopyData(s)); EnterpriseCert enterpriseCert; if (NS_FAILED(enterpriseCert.Init(CFDataGetBytePtr(der.get()), CFDataGetLength(der.get()), isRoot))) { // Best-effort. We probably ran out of memory. continue; } if (!certs.append(std::move(enterpriseCert))) { // Best-effort again. continue; } numImported++; } MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("imported %u certs", numImported)); return errSecSuccess; } #endif // XP_MACOSX #ifdef MOZ_WIDGET_ANDROID void GatherEnterpriseCertsAndroid(Vector& certs) { if (!jni::IsAvailable()) { MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("JNI not available")); return; } jni::ObjectArray::LocalRef roots = java::EnterpriseRoots::GatherEnterpriseRoots(); for (size_t i = 0; i < roots->Length(); i++) { jni::ByteArray::LocalRef root = roots->GetElement(i); EnterpriseCert cert; // Currently we treat all certificates gleaned from the Android // CA store as roots. if (NS_SUCCEEDED(cert.Init( reinterpret_cast(root->GetElements().Elements()), root->Length(), true))) { Unused << certs.append(std::move(cert)); } } } #endif // MOZ_WIDGET_ANDROID nsresult GatherEnterpriseCerts(Vector& certs) { MOZ_ASSERT(!NS_IsMainThread()); if (NS_IsMainThread()) { return NS_ERROR_NOT_SAME_THREAD; } certs.clear(); #ifdef XP_WIN GatherEnterpriseCertsWindows(certs); #endif // XP_WIN #ifdef XP_MACOSX OSStatus rv = GatherEnterpriseCertsMacOS(certs); if (rv != errSecSuccess) { return NS_ERROR_FAILURE; } #endif // XP_MACOSX #ifdef MOZ_WIDGET_ANDROID GatherEnterpriseCertsAndroid(certs); #endif // MOZ_WIDGET_ANDROID return NS_OK; }