Bug 1871011 - use policy APIs rather than building certificate chains to determine enterprise roots on macOS r=jschanck
Building certificate chains (at startup, no less) is slow. Using the policy APIs to determine if an enterprise certificate is a trusted root or an intermediate should be faster. Differential Revision: https://phabricator.services.mozilla.com/D197740
This commit is contained in:
@@ -224,23 +224,197 @@ static void GatherEnterpriseCertsWindows(nsTArray<EnterpriseCert>& certs,
|
|||||||
#endif // XP_WIN
|
#endif // XP_WIN
|
||||||
|
|
||||||
#ifdef XP_MACOSX
|
#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
|
||||||
|
// https://developer.apple.com/documentation/security/1400261-sectrustsettingscopytrustsetting
|
||||||
|
// `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,
|
OSStatus GatherEnterpriseCertsMacOS(nsTArray<EnterpriseCert>& certs,
|
||||||
UniqueSECMODModule& rootsModule) {
|
UniqueSECMODModule& rootsModule) {
|
||||||
// The following builds a search dictionary corresponding to:
|
// The following builds a search dictionary corresponding to:
|
||||||
// { class: "certificate",
|
// { class: "certificate",
|
||||||
// match limit: "match all",
|
// 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
|
// 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
|
// us all 3rd party certificates. Unfortunately, if a root that shipped with
|
||||||
// is what we want (thus we don't import built-in root certificates that ship
|
// the OS has had its trust settings changed, it can also be returned from
|
||||||
// with the OS).
|
// this query. Further work (below) filters such certificates out.
|
||||||
const CFStringRef keys[] = {kSecClass, kSecMatchLimit, kSecMatchPolicy,
|
const CFStringRef keys[] = {kSecClass, kSecMatchLimit};
|
||||||
kSecMatchTrustedOnly};
|
const void* values[] = {kSecClassCertificate, kSecMatchLimitAll};
|
||||||
// https://developer.apple.com/documentation/security/1392592-secpolicycreatessl
|
|
||||||
ScopedCFType<SecPolicyRef> sslPolicy(SecPolicyCreateSSL(true, nullptr));
|
|
||||||
const void* values[] = {kSecClassCertificate, kSecMatchLimitAll,
|
|
||||||
sslPolicy.get(), kCFBooleanTrue};
|
|
||||||
static_assert(ArrayLength(keys) == ArrayLength(values),
|
static_assert(ArrayLength(keys) == ArrayLength(values),
|
||||||
"mismatched SecItemCopyMatching key/value array sizes");
|
"mismatched SecItemCopyMatching key/value array sizes");
|
||||||
// https://developer.apple.com/documentation/corefoundation/1516782-cfdictionarycreate
|
// https://developer.apple.com/documentation/corefoundation/1516782-cfdictionarycreate
|
||||||
@@ -260,40 +434,31 @@ OSStatus GatherEnterpriseCertsMacOS(nsTArray<EnterpriseCert>& certs,
|
|||||||
CFIndex count = CFArrayGetCount(arr.get());
|
CFIndex count = CFArrayGetCount(arr.get());
|
||||||
uint32_t numImported = 0;
|
uint32_t numImported = 0;
|
||||||
for (CFIndex i = 0; i < count; i++) {
|
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<SecTrustRef> 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!SecTrustEvaluateWithError(trustHandle.get(), nullptr)) {
|
|
||||||
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
|
// Because we asked for certificates, each CFTypeRef in the array is really
|
||||||
// a SecCertificateRef.
|
// a SecCertificateRef.
|
||||||
const SecCertificateRef s = (const SecCertificateRef)c;
|
const SecCertificateRef certificate =
|
||||||
ScopedCFType<CFDataRef> der(SecCertificateCopyData(s));
|
(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()),
|
EnterpriseCert enterpriseCert(CFDataGetBytePtr(der.get()),
|
||||||
CFDataGetLength(der.get()), isRoot);
|
CFDataGetLength(der.get()), isRoot);
|
||||||
if (!enterpriseCert.IsKnownRoot(rootsModule)) {
|
if (!enterpriseCert.IsKnownRoot(rootsModule)) {
|
||||||
certs.AppendElement(std::move(enterpriseCert));
|
certs.AppendElement(std::move(enterpriseCert));
|
||||||
numImported++;
|
numImported++;
|
||||||
|
MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
|
||||||
|
("importing as %s", isRoot ? "root" : "intermediate"));
|
||||||
} else {
|
} else {
|
||||||
MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("skipping known root cert"));
|
MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("skipping known root cert"));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,6 +17,8 @@ class ScopedCFType {
|
|||||||
public:
|
public:
|
||||||
explicit ScopedCFType(T value) : mValue(value) {}
|
explicit ScopedCFType(T value) : mValue(value) {}
|
||||||
|
|
||||||
|
MOZ_IMPLICIT ScopedCFType(decltype(nullptr)) : mValue(nullptr) {}
|
||||||
|
|
||||||
~ScopedCFType() {
|
~ScopedCFType() {
|
||||||
if (mValue) {
|
if (mValue) {
|
||||||
CFRelease((CFTypeRef)mValue);
|
CFRelease((CFTypeRef)mValue);
|
||||||
|
|||||||
Reference in New Issue
Block a user