Source code

Revision control

Copy as Markdown

Other Tools

/* -*- 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 "PKCS11ModuleDB.h"
#include "mozilla/ArrayUtils.h"
#include "mozilla/IntegerPrintfMacros.h"
#include "mozilla/Casting.h"
#include "mozilla/Logging.h"
#include "mozilla/Unused.h"
#include "mozpkix/Result.h"
#include "nsCRT.h"
#include "nsThreadUtils.h"
#ifdef MOZ_WIDGET_ANDROID
# include "mozilla/java/EnterpriseRootsWrappers.h"
#endif // MOZ_WIDGET_ANDROID
#ifdef XP_MACOSX
# include <Security/Security.h>
# include "KeychainSecret.h"
#endif
#ifdef XP_WIN
# include <windows.h>
# include <wincrypt.h>
#endif // XP_WIN
extern mozilla::LazyLogModule gPIPNSSLog;
using namespace mozilla;
using namespace psm;
void EnterpriseCert::CopyBytes(nsTArray<uint8_t>& dest) const {
dest.Assign(mDER);
}
pkix::Result EnterpriseCert::GetInput(pkix::Input& input) const {
return input.Init(mDER.Elements(), mDER.Length());
}
bool EnterpriseCert::GetIsRoot() const { return mIsRoot; }
bool EnterpriseCert::IsKnownRoot(UniqueSECMODModule& rootsModule) {
if (!rootsModule) {
return false;
}
SECItem certItem = {siBuffer, mDER.Elements(),
static_cast<unsigned int>(mDER.Length())};
AutoSECMODListReadLock lock;
for (int i = 0; i < rootsModule->slotCount; i++) {
PK11SlotInfo* slot = rootsModule->slots[i];
if (PK11_FindEncodedCertInSlot(slot, &certItem, nullptr) !=
CK_INVALID_HANDLE) {
return true;
}
}
return false;
}
#ifdef XP_WIN
struct CertStoreLocation {
const wchar_t* mName;
const bool mIsRoot;
CertStoreLocation(const wchar_t* name, bool isRoot)
: mName(name), mIsRoot(isRoot) {}
};
// The documentation doesn't make this clear, but the certificate location
// identified by "ROOT" contains trusted root certificates. The certificate
// location identified by "CA" contains intermediate certificates.
const CertStoreLocation kCertStoreLocations[] = {
CertStoreLocation(L"ROOT", true), CertStoreLocation(L"CA", false)};
// 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;
};
// To determine if a certificate would be useful when verifying a server
// certificate for TLS server auth, Windows provides the function
// `CertGetEnhancedKeyUsage`, which combines the extended key usage extension
// with something called "enhanced key usage", which appears to be a Microsoft
// concept.
static bool CertCanBeUsedForTLSServerAuth(PCCERT_CONTEXT certificate) {
DWORD usageSize = 0;
if (!CertGetEnhancedKeyUsage(certificate, 0, NULL, &usageSize)) {
return false;
}
nsTArray<uint8_t> usageBytes;
usageBytes.SetLength(usageSize);
PCERT_ENHKEY_USAGE usage(
reinterpret_cast<PCERT_ENHKEY_USAGE>(usageBytes.Elements()));
if (!CertGetEnhancedKeyUsage(certificate, 0, usage, &usageSize)) {
return false;
}
// "If the cUsageIdentifier member is zero, the certificate might be valid
// for all uses or the certificate might have no valid uses. The return from
// a call to GetLastError can be used to determine whether the certificate is
// good for all uses or for none. If GetLastError returns CRYPT_E_NOT_FOUND,
// the certificate is good for all uses. If it returns zero, the certificate
// has no valid uses."
if (usage->cUsageIdentifier == 0) {
return GetLastError() == static_cast<DWORD>(CRYPT_E_NOT_FOUND);
}
for (DWORD i = 0; i < usage->cUsageIdentifier; i++) {
if (!nsCRT::strcmp(usage->rgpszUsageIdentifier[i],
szOID_PKIX_KP_SERVER_AUTH) ||
!nsCRT::strcmp(usage->rgpszUsageIdentifier[i],
szOID_ANY_ENHANCED_KEY_USAGE)) {
return true;
}
}
return false;
}
// 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,
nsTArray<EnterpriseCert>& certs,
UniqueSECMODModule& rootsModule) {
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
for (const auto& location : kCertStoreLocations) {
ScopedCertStore certStore(CertOpenStore(CERT_STORE_PROV_SYSTEM_REGISTRY_W,
0, NULL, flags, location.mName));
if (!certStore.get()) {
MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
("failed to open certificate store"));
continue;
}
PCCERT_CONTEXT certificate = nullptr;
uint32_t numImported = 0;
while ((certificate = CertFindCertificateInStore(
certStore.get(), X509_ASN_ENCODING, 0, CERT_FIND_ANY, nullptr,
certificate))) {
if (!CertCanBeUsedForTLSServerAuth(certificate)) {
MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
("skipping cert not relevant for TLS server auth"));
continue;
}
EnterpriseCert enterpriseCert(certificate->pbCertEncoded,
certificate->cbCertEncoded,
location.mIsRoot);
if (enterpriseCert.GetIsRoot() ||
!enterpriseCert.IsKnownRoot(rootsModule)) {
certs.AppendElement(std::move(enterpriseCert));
numImported++;
} else {
MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
("skipping intermediate that is a known root cert"));
}
}
MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
("imported %u certs from %S", numImported, location.mName));
}
}
static void GatherEnterpriseCertsWindows(nsTArray<EnterpriseCert>& certs,
UniqueSECMODModule& rootsModule) {
GatherEnterpriseCertsForLocation(CERT_SYSTEM_STORE_LOCAL_MACHINE, certs,
rootsModule);
GatherEnterpriseCertsForLocation(CERT_SYSTEM_STORE_LOCAL_MACHINE_GROUP_POLICY,
certs, rootsModule);
GatherEnterpriseCertsForLocation(CERT_SYSTEM_STORE_LOCAL_MACHINE_ENTERPRISE,
certs, rootsModule);
GatherEnterpriseCertsForLocation(CERT_SYSTEM_STORE_CURRENT_USER, certs,
rootsModule);
GatherEnterpriseCertsForLocation(CERT_SYSTEM_STORE_CURRENT_USER_GROUP_POLICY,
certs, rootsModule);
}
#endif // XP_WIN
#ifdef XP_MACOSX
enum class CertificateTrustResult {
CanUseAsIntermediate,
CanUseAsTrustAnchor,
DoNotUse,
};
ScopedCFType<CFArrayRef> GetCertificateTrustSettingsInDomain(
const SecCertificateRef certificate, SecTrustSettingsDomain domain) {
CFArrayRef trustSettingsRaw;
OSStatus rv =
SecTrustSettingsCopyTrustSettings(certificate, domain, &trustSettingsRaw);
if (rv != errSecSuccess || !trustSettingsRaw) {
MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
(" SecTrustSettingsCopyTrustSettings failed (or not found) for "
"domain %" PRIu32,
domain));
return nullptr;
}
ScopedCFType<CFArrayRef> trustSettings(trustSettingsRaw);
return trustSettings;
}
// This function processes trust settings returned by
// SecTrustSettingsCopyTrustSettings. See the documentation at
// `trustSettings` is an array of CFDictionaryRef. Each dictionary may impose
// a constraint.
CertificateTrustResult ProcessCertificateTrustSettings(
ScopedCFType<CFArrayRef>& trustSettings) {
// If the array is empty, the certificate is a trust anchor.
const CFIndex numTrustDictionaries = CFArrayGetCount(trustSettings.get());
if (numTrustDictionaries == 0) {
MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
(" empty trust settings -> trust anchor"));
return CertificateTrustResult::CanUseAsTrustAnchor;
}
CertificateTrustResult currentTrustSettings =
CertificateTrustResult::CanUseAsIntermediate;
for (CFIndex i = 0; i < numTrustDictionaries; i++) {
CFDictionaryRef trustDictionary = reinterpret_cast<CFDictionaryRef>(
CFArrayGetValueAtIndex(trustSettings.get(), i));
// kSecTrustSettingsApplication specifies an external application that
// determines the certificate's trust settings.
// kSecTrustSettingsPolicyString appears to be a mechanism like name
// constraints.
// These are not supported, so conservatively assume this certificate is
// distrusted if either are present.
if (CFDictionaryContainsKey(trustDictionary,
kSecTrustSettingsApplication) ||
CFDictionaryContainsKey(trustDictionary,
kSecTrustSettingsPolicyString)) {
MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
(" found unsupported policy -> assuming distrusted"));
return CertificateTrustResult::DoNotUse;
}
// kSecTrustSettingsKeyUsage seems to be essentially the equivalent of the
// x509 keyUsage extension. For parity, we allow
// kSecTrustSettingsKeyUseSignature, kSecTrustSettingsKeyUseSignCert, and
// kSecTrustSettingsKeyUseAny.
if (CFDictionaryContainsKey(trustDictionary, kSecTrustSettingsKeyUsage)) {
CFNumberRef keyUsage = (CFNumberRef)CFDictionaryGetValue(
trustDictionary, kSecTrustSettingsKeyUsage);
int32_t keyUsageValue;
if (!keyUsage ||
CFNumberGetValue(keyUsage, kCFNumberSInt32Type, &keyUsageValue) ||
keyUsageValue < 0) {
MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
(" no trust settings key usage or couldn't get value"));
return CertificateTrustResult::DoNotUse;
}
switch ((uint64_t)keyUsageValue) {
case kSecTrustSettingsKeyUseSignature: // fall-through
case kSecTrustSettingsKeyUseSignCert: // fall-through
case kSecTrustSettingsKeyUseAny:
break;
default:
return CertificateTrustResult::DoNotUse;
}
}
// If there is a specific policy, ensure that it's for the
// 'kSecPolicyAppleSSL' policy, which is the TLS server auth policy (i.e.
// x509 + domain name checking).
if (CFDictionaryContainsKey(trustDictionary, kSecTrustSettingsPolicy)) {
SecPolicyRef policy = (SecPolicyRef)CFDictionaryGetValue(
trustDictionary, kSecTrustSettingsPolicy);
if (!policy) {
MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
(" kSecTrustSettingsPolicy present, but null?"));
continue;
}
ScopedCFType<CFDictionaryRef> policyProperties(
SecPolicyCopyProperties(policy));
CFStringRef policyOid = (CFStringRef)CFDictionaryGetValue(
policyProperties.get(), kSecPolicyOid);
if (!CFEqual(policyOid, kSecPolicyAppleSSL)) {
MOZ_LOG(gPIPNSSLog, LogLevel::Debug, (" policy doesn't match"));
continue;
}
}
// By default, the trust setting result value is
// kSecTrustSettingsResultTrustRoot.
int32_t trustSettingsValue = kSecTrustSettingsResultTrustRoot;
if (CFDictionaryContainsKey(trustDictionary, kSecTrustSettingsResult)) {
CFNumberRef trustSetting = (CFNumberRef)CFDictionaryGetValue(
trustDictionary, kSecTrustSettingsResult);
if (!trustSetting || !CFNumberGetValue(trustSetting, kCFNumberSInt32Type,
&trustSettingsValue)) {
MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
(" no trust settings result or couldn't get value"));
continue;
}
}
MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
(" trust setting: %d", trustSettingsValue));
if (trustSettingsValue == kSecTrustSettingsResultDeny) {
return CertificateTrustResult::DoNotUse;
}
if (trustSettingsValue == kSecTrustSettingsResultTrustRoot ||
trustSettingsValue == kSecTrustSettingsResultTrustAsRoot) {
currentTrustSettings = CertificateTrustResult::CanUseAsTrustAnchor;
}
}
return currentTrustSettings;
}
CertificateTrustResult GetCertificateTrustResult(
const SecCertificateRef certificate) {
ScopedCFType<CFStringRef> subject(
SecCertificateCopySubjectSummary(certificate));
MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
("determining trust for '%s'",
CFStringGetCStringPtr(subject.get(), kCFStringEncodingUTF8)));
// There are three trust settings domains: kSecTrustSettingsDomainUser,
// kSecTrustSettingsDomainAdmin, and kSecTrustSettingsDomainSystem. User
// overrides admin and admin overrides system. However, if the given
// certificate has trust settings in the system domain, it shipped with the
// OS, so we don't want to use it.
ScopedCFType<CFArrayRef> systemTrustSettings(
GetCertificateTrustSettingsInDomain(certificate,
kSecTrustSettingsDomainSystem));
if (systemTrustSettings) {
return CertificateTrustResult::DoNotUse;
}
// At this point, if there is no trust information regarding this
// certificate, it can be used as an intermediate.
CertificateTrustResult certificateTrustResult =
CertificateTrustResult::CanUseAsIntermediate;
// Process trust information in the user domain, if any.
ScopedCFType<CFArrayRef> userTrustSettings(
GetCertificateTrustSettingsInDomain(certificate,
kSecTrustSettingsDomainUser));
if (userTrustSettings) {
certificateTrustResult = ProcessCertificateTrustSettings(userTrustSettings);
// If there is definite information one way or another (either indicating
// this is a trusted root or a distrusted certificate), use that
// information.
if (certificateTrustResult !=
CertificateTrustResult::CanUseAsIntermediate) {
return certificateTrustResult;
}
}
// Process trust information in the admin domain, if any.
ScopedCFType<CFArrayRef> adminTrustSettings(
GetCertificateTrustSettingsInDomain(certificate,
kSecTrustSettingsDomainAdmin));
if (adminTrustSettings) {
certificateTrustResult =
ProcessCertificateTrustSettings(adminTrustSettings);
}
// Use whatever result we ended up with.
return certificateTrustResult;
}
OSStatus GatherEnterpriseCertsMacOS(nsTArray<EnterpriseCert>& certs,
UniqueSECMODModule& rootsModule) {
// The following builds a search dictionary corresponding to:
// { class: "certificate",
// match limit: "match all" }
// This operates on items that have been added to the keychain and thus gives
// us all 3rd party certificates. Unfortunately, if a root that shipped with
// the OS has had its trust settings changed, it can also be returned from
// this query. Further work (below) filters such certificates out.
const CFStringRef keys[] = {kSecClass, kSecMatchLimit};
const void* values[] = {kSecClassCertificate, kSecMatchLimitAll};
static_assert(ArrayLength(keys) == ArrayLength(values),
"mismatched SecItemCopyMatching key/value array sizes");
ScopedCFType<CFDictionaryRef> searchDictionary(CFDictionaryCreate(
nullptr, (const void**)&keys, (const void**)&values, ArrayLength(keys),
&kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks));
CFTypeRef items;
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<CFArrayRef> arr(reinterpret_cast<CFArrayRef>(items));
CFIndex count = CFArrayGetCount(arr.get());
uint32_t numImported = 0;
for (CFIndex i = 0; i < count; i++) {
// Because we asked for certificates, each CFTypeRef in the array is really
// a SecCertificateRef.
const SecCertificateRef certificate =
(const SecCertificateRef)CFArrayGetValueAtIndex(arr.get(), i);
CertificateTrustResult certificateTrustResult =
GetCertificateTrustResult(certificate);
if (certificateTrustResult == CertificateTrustResult::DoNotUse) {
MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("skipping distrusted cert"));
continue;
}
ScopedCFType<CFDataRef> der(SecCertificateCopyData(certificate));
if (!der) {
MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
("couldn't get bytes of certificate?"));
continue;
}
bool isRoot =
certificateTrustResult == CertificateTrustResult::CanUseAsTrustAnchor;
EnterpriseCert enterpriseCert(CFDataGetBytePtr(der.get()),
CFDataGetLength(der.get()), isRoot);
if (enterpriseCert.GetIsRoot() ||
!enterpriseCert.IsKnownRoot(rootsModule)) {
certs.AppendElement(std::move(enterpriseCert));
numImported++;
MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
("importing as %s", isRoot ? "root" : "intermediate"));
} else {
MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
("skipping intermediate that is a known root cert"));
}
}
MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("imported %u certs", numImported));
return errSecSuccess;
}
#endif // XP_MACOSX
#ifdef MOZ_WIDGET_ANDROID
void GatherEnterpriseCertsAndroid(nsTArray<EnterpriseCert>& certs,
UniqueSECMODModule& rootsModule) {
if (!jni::IsAvailable()) {
MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("JNI not available"));
return;
}
jni::ObjectArray::LocalRef roots =
java::EnterpriseRoots::GatherEnterpriseRoots();
uint32_t numImported = 0;
for (size_t i = 0; i < roots->Length(); i++) {
jni::ByteArray::LocalRef root = roots->GetElement(i);
// Currently we treat all certificates gleaned from the Android
// CA store as roots.
EnterpriseCert enterpriseCert(
reinterpret_cast<uint8_t*>(root->GetElements().Elements()),
root->Length(), true);
if (enterpriseCert.GetIsRoot() ||
!enterpriseCert.IsKnownRoot(rootsModule)) {
certs.AppendElement(std::move(enterpriseCert));
numImported++;
} else {
MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
("skipping intermediate that is a known root cert"));
}
}
MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("imported %u certs", numImported));
}
#endif // MOZ_WIDGET_ANDROID
nsresult GatherEnterpriseCerts(nsTArray<EnterpriseCert>& certs) {
MOZ_ASSERT(!NS_IsMainThread());
if (NS_IsMainThread()) {
return NS_ERROR_NOT_SAME_THREAD;
}
certs.Clear();
UniqueSECMODModule rootsModule(SECMOD_FindModule(kRootModuleName.get()));
#ifdef XP_WIN
GatherEnterpriseCertsWindows(certs, rootsModule);
#endif // XP_WIN
#ifdef XP_MACOSX
OSStatus rv = GatherEnterpriseCertsMacOS(certs, rootsModule);
if (rv != errSecSuccess) {
return NS_ERROR_FAILURE;
}
#endif // XP_MACOSX
#ifdef MOZ_WIDGET_ANDROID
GatherEnterpriseCertsAndroid(certs, rootsModule);
#endif // MOZ_WIDGET_ANDROID
return NS_OK;
}