| Index: net/cert/internal/trust_store_mac.cc
|
| diff --git a/net/cert/internal/trust_store_mac.cc b/net/cert/internal/trust_store_mac.cc
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..a088a32f4c51d924bf1e2e02ebcbaa105d9c32de
|
| --- /dev/null
|
| +++ b/net/cert/internal/trust_store_mac.cc
|
| @@ -0,0 +1,351 @@
|
| +// Copyright 2017 The Chromium Authors. All rights reserved.
|
| +// Use of this source code is governed by a BSD-style license that can be
|
| +// found in the LICENSE file.
|
| +
|
| +#include "net/cert/internal/trust_store_mac.h"
|
| +
|
| +#include <Security/Security.h>
|
| +
|
| +#include "base/logging.h"
|
| +#include "base/mac/foundation_util.h"
|
| +#include "base/mac/mac_logging.h"
|
| +#include "base/memory/ptr_util.h"
|
| +#include "base/synchronization/lock.h"
|
| +#include "crypto/mac_security_services_lock.h"
|
| +#include "net/cert/internal/cert_errors.h"
|
| +#include "net/cert/internal/parse_name.h"
|
| +#include "net/cert/internal/parsed_certificate.h"
|
| +#include "net/cert/test_keychain_search_list_mac.h"
|
| +#include "net/cert/x509_certificate.h"
|
| +#include "net/cert/x509_util.h"
|
| +
|
| +namespace net {
|
| +
|
| +namespace {
|
| +
|
| +// The rules for interpreting trust settings are documented at:
|
| +// https://developer.apple.com/reference/security/1400261-sectrustsettingscopytrustsetting?language=objc
|
| +
|
| +// Indicates the trust status of a certificate.
|
| +enum class TrustStatus {
|
| + // Certificate inherits trust value from its issuer. If the certificate is the
|
| + // root of the chain, this implies distrust.
|
| + UNSPECIFIED,
|
| + // Certificate is a trust anchor.
|
| + TRUSTED,
|
| + // Certificate is blacklisted / explicitly distrusted.
|
| + DISTRUSTED
|
| +};
|
| +
|
| +// Returns trust status of usage constraints dictionary |trust_dict| for a
|
| +// certificate that |is_self_signed|.
|
| +TrustStatus IsTrustDictionaryTrustedForPolicy(
|
| + CFDictionaryRef trust_dict,
|
| + bool is_self_signed,
|
| + const CFStringRef target_policy_oid) {
|
| + // An empty trust dict should be interpreted as
|
| + // kSecTrustSettingsResultTrustRoot. This is handled by falling through all
|
| + // the conditions below with the default value of |trust_settings_result|.
|
| +
|
| + // Trust settings may be scoped to a single application, by checking that the
|
| + // code signing identity of the current application matches the serialized
|
| + // code signing identity in the kSecTrustSettingsApplication key.
|
| + // As this is not presently supported, skip any trust settings scoped to the
|
| + // application.
|
| + if (CFDictionaryContainsKey(trust_dict, kSecTrustSettingsApplication))
|
| + return TrustStatus::UNSPECIFIED;
|
| +
|
| + // Trust settings may be scoped using policy-specific constraints. For
|
| + // example, SSL trust settings might be scoped to a single hostname, or EAP
|
| + // settings specific to a particular WiFi network.
|
| + // As this is not presently supported, skip any policy-specific trust
|
| + // settings.
|
| + if (CFDictionaryContainsKey(trust_dict, kSecTrustSettingsPolicyString))
|
| + return TrustStatus::UNSPECIFIED;
|
| +
|
| + // Ignoring kSecTrustSettingsKeyUsage for now; it does not seem relevant to
|
| + // the TLS case.
|
| +
|
| + // If the trust settings are scoped to a specific policy (via
|
| + // kSecTrustSettingsPolicy), ensure that the policy is the same policy as
|
| + // |target_policy_oid|. If there is no kSecTrustSettingsPolicy key, it's
|
| + // considered a match for all policies.
|
| + SecPolicyRef policy_ref = base::mac::GetValueFromDictionary<SecPolicyRef>(
|
| + trust_dict, kSecTrustSettingsPolicy);
|
| + if (policy_ref) {
|
| + base::ScopedCFTypeRef<CFDictionaryRef> policy_dict;
|
| + {
|
| + base::AutoLock lock(crypto::GetMacSecurityServicesLock());
|
| + policy_dict.reset(SecPolicyCopyProperties(policy_ref));
|
| + }
|
| +
|
| + // kSecPolicyOid is guaranteed to be present in the policy dictionary.
|
| + //
|
| + // TODO(mattm): remove the CFCastStrict below once Chromium builds against
|
| + // the 10.11 SDK.
|
| + CFStringRef policy_oid = base::mac::GetValueFromDictionary<CFStringRef>(
|
| + policy_dict, base::mac::CFCastStrict<CFStringRef>(kSecPolicyOid));
|
| +
|
| + if (!CFEqual(policy_oid, target_policy_oid))
|
| + return TrustStatus::UNSPECIFIED;
|
| + }
|
| +
|
| + // If kSecTrustSettingsResult is not present in the trust dict,
|
| + // kSecTrustSettingsResultTrustRoot is assumed.
|
| + int trust_settings_result = kSecTrustSettingsResultTrustRoot;
|
| + CFNumberRef trust_settings_result_ref =
|
| + base::mac::GetValueFromDictionary<CFNumberRef>(trust_dict,
|
| + kSecTrustSettingsResult);
|
| + if (trust_settings_result_ref &&
|
| + !CFNumberGetValue(trust_settings_result_ref, kCFNumberIntType,
|
| + &trust_settings_result)) {
|
| + return TrustStatus::UNSPECIFIED;
|
| + }
|
| +
|
| + if (trust_settings_result == kSecTrustSettingsResultDeny)
|
| + return TrustStatus::DISTRUSTED;
|
| +
|
| + // kSecTrustSettingsResultTrustRoot can only be applied to root(self-signed)
|
| + // certs.
|
| + if (is_self_signed)
|
| + return (trust_settings_result == kSecTrustSettingsResultTrustRoot)
|
| + ? TrustStatus::TRUSTED
|
| + : TrustStatus::UNSPECIFIED;
|
| +
|
| + // kSecTrustSettingsResultTrustAsRoot can only be applied to non-root certs.
|
| + return (trust_settings_result == kSecTrustSettingsResultTrustAsRoot)
|
| + ? TrustStatus::TRUSTED
|
| + : TrustStatus::UNSPECIFIED;
|
| +}
|
| +
|
| +// Returns true if the trust settings array |trust_settings| for a certificate
|
| +// that |is_self_signed| should be treated as a trust anchor.
|
| +TrustStatus IsTrustSettingsTrustedForPolicy(CFArrayRef trust_settings,
|
| + bool is_self_signed,
|
| + const CFStringRef policy_oid) {
|
| + // An empty trust settings array (that is, the trust_settings parameter
|
| + // returns a valid but empty CFArray) means "always trust this certificate"
|
| + // with an overall trust setting for the certificate of
|
| + // kSecTrustSettingsResultTrustRoot.
|
| + if (CFArrayGetCount(trust_settings) == 0 && is_self_signed)
|
| + return TrustStatus::TRUSTED;
|
| +
|
| + for (CFIndex i = 0, settings_count = CFArrayGetCount(trust_settings);
|
| + i < settings_count; ++i) {
|
| + CFDictionaryRef trust_dict = reinterpret_cast<CFDictionaryRef>(
|
| + const_cast<void*>(CFArrayGetValueAtIndex(trust_settings, i)));
|
| + TrustStatus trust = IsTrustDictionaryTrustedForPolicy(
|
| + trust_dict, is_self_signed, policy_oid);
|
| + if (trust != TrustStatus::UNSPECIFIED)
|
| + return trust;
|
| + }
|
| + return TrustStatus::UNSPECIFIED;
|
| +}
|
| +
|
| +// Returns true if the certificate |cert_handle| is trusted for the policy
|
| +// |policy_oid|.
|
| +TrustStatus IsSecCertificateTrustedForPolicy(SecCertificateRef cert_handle,
|
| + const CFStringRef policy_oid) {
|
| + const bool is_self_signed = X509Certificate::IsSelfSigned(cert_handle);
|
| + // Evaluate trust domains in user, admin, system order. Admin settings can
|
| + // override system ones, and user settings can override both admin and system.
|
| + for (const auto& trust_domain :
|
| + {kSecTrustSettingsDomainUser, kSecTrustSettingsDomainAdmin,
|
| + kSecTrustSettingsDomainSystem}) {
|
| + base::ScopedCFTypeRef<CFArrayRef> trust_settings;
|
| + OSStatus err;
|
| + {
|
| + base::AutoLock lock(crypto::GetMacSecurityServicesLock());
|
| + err = SecTrustSettingsCopyTrustSettings(cert_handle, trust_domain,
|
| + trust_settings.InitializeInto());
|
| + }
|
| + if (err == errSecItemNotFound) {
|
| + // No trust settings for that domain.. try the next.
|
| + continue;
|
| + }
|
| + if (err) {
|
| + OSSTATUS_LOG(ERROR, err) << "SecTrustSettingsCopyTrustSettings error";
|
| + continue;
|
| + }
|
| + TrustStatus trust = IsTrustSettingsTrustedForPolicy(
|
| + trust_settings, is_self_signed, policy_oid);
|
| + if (trust != TrustStatus::UNSPECIFIED)
|
| + return trust;
|
| + }
|
| +
|
| + // No trust settings, or none of the settings were for the correct policy, or
|
| + // had the correct trust result.
|
| + return TrustStatus::UNSPECIFIED;
|
| +}
|
| +
|
| +// Filters an array of SecCertificateRef by trust for |policy_oid|, returning
|
| +// the results as TrustAnchors in |out_anchors|.
|
| +void FilterTrustedCertificates(CFArrayRef matching_items,
|
| + const CFStringRef policy_oid,
|
| + TrustAnchors* out_anchors) {
|
| + for (CFIndex i = 0, item_count = CFArrayGetCount(matching_items);
|
| + i < item_count; ++i) {
|
| + SecCertificateRef match_cert_handle = reinterpret_cast<SecCertificateRef>(
|
| + const_cast<void*>(CFArrayGetValueAtIndex(matching_items, i)));
|
| +
|
| + if (IsSecCertificateTrustedForPolicy(match_cert_handle, policy_oid) !=
|
| + TrustStatus::TRUSTED)
|
| + continue;
|
| +
|
| + base::ScopedCFTypeRef<CFDataRef> der_data(
|
| + SecCertificateCopyData(match_cert_handle));
|
| + if (!der_data) {
|
| + LOG(ERROR) << "SecCertificateCopyData error";
|
| + continue;
|
| + }
|
| +
|
| + CertErrors errors;
|
| + ParseCertificateOptions options;
|
| + options.allow_invalid_serial_numbers = true;
|
| + scoped_refptr<ParsedCertificate> anchor_cert = ParsedCertificate::Create(
|
| + x509_util::CreateCryptoBuffer(CFDataGetBytePtr(der_data.get()),
|
| + CFDataGetLength(der_data.get())),
|
| + options, &errors);
|
| + if (!anchor_cert) {
|
| + // TODO(crbug.com/634443): return errors better.
|
| + LOG(ERROR) << "Error parsing issuer certificate:\n"
|
| + << errors.ToDebugString();
|
| + continue;
|
| + }
|
| +
|
| + out_anchors->push_back(TrustAnchor::CreateFromCertificateNoConstraints(
|
| + std::move(anchor_cert)));
|
| + }
|
| +}
|
| +
|
| +} // namespace
|
| +
|
| +TrustStoreMac::TrustStoreMac(CFTypeRef policy_oid)
|
| + : policy_oid_(base::mac::CFCastStrict<CFStringRef>(policy_oid)) {
|
| + DCHECK(policy_oid_);
|
| +}
|
| +
|
| +TrustStoreMac::~TrustStoreMac() = default;
|
| +
|
| +void TrustStoreMac::FindTrustAnchorsForCert(
|
| + const scoped_refptr<ParsedCertificate>& cert,
|
| + TrustAnchors* out_anchors) const {
|
| + base::ScopedCFTypeRef<CFDataRef> name_data = GetMacNormalizedIssuer(cert);
|
| +
|
| + FindTrustAnchorsByMacNormalizedSubject(name_data, out_anchors);
|
| +}
|
| +
|
| +// static
|
| +base::ScopedCFTypeRef<CFArrayRef>
|
| +TrustStoreMac::FindMatchingCertificatesForMacNormalizedSubject(
|
| + CFDataRef name_data) {
|
| + base::ScopedCFTypeRef<CFArrayRef> matching_items;
|
| + base::ScopedCFTypeRef<CFMutableDictionaryRef> query(
|
| + CFDictionaryCreateMutable(nullptr, 0, &kCFTypeDictionaryKeyCallBacks,
|
| + &kCFTypeDictionaryValueCallBacks));
|
| +
|
| + CFDictionarySetValue(query, kSecClass, kSecClassCertificate);
|
| + CFDictionarySetValue(query, kSecReturnRef, kCFBooleanTrue);
|
| + CFDictionarySetValue(query, kSecMatchLimit, kSecMatchLimitAll);
|
| + CFDictionarySetValue(query, kSecAttrSubject, name_data);
|
| +
|
| + base::ScopedCFTypeRef<CFArrayRef> scoped_alternate_keychain_search_list;
|
| + if (TestKeychainSearchList::HasInstance()) {
|
| + OSStatus status = TestKeychainSearchList::GetInstance()->CopySearchList(
|
| + scoped_alternate_keychain_search_list.InitializeInto());
|
| + if (status) {
|
| + OSSTATUS_LOG(ERROR, status)
|
| + << "TestKeychainSearchList::CopySearchList error";
|
| + return matching_items;
|
| + }
|
| + }
|
| +
|
| + base::AutoLock lock(crypto::GetMacSecurityServicesLock());
|
| +
|
| + // If a TestKeychainSearchList is present, it will have already set
|
| + // |scoped_alternate_keychain_search_list|, which will be used as the
|
| + // basis for reordering the keychain. Otherwise, get the current keychain
|
| + // search list and use that.
|
| + if (!scoped_alternate_keychain_search_list) {
|
| + OSStatus status = SecKeychainCopySearchList(
|
| + scoped_alternate_keychain_search_list.InitializeInto());
|
| + if (status) {
|
| + OSSTATUS_LOG(ERROR, status) << "SecKeychainCopySearchList error";
|
| + return matching_items;
|
| + }
|
| + }
|
| +
|
| + CFMutableArrayRef mutable_keychain_search_list = CFArrayCreateMutableCopy(
|
| + kCFAllocatorDefault,
|
| + CFArrayGetCount(scoped_alternate_keychain_search_list.get()) + 1,
|
| + scoped_alternate_keychain_search_list.get());
|
| + if (!mutable_keychain_search_list) {
|
| + LOG(ERROR) << "CFArrayCreateMutableCopy";
|
| + return matching_items;
|
| + }
|
| + scoped_alternate_keychain_search_list.reset(mutable_keychain_search_list);
|
| +
|
| + base::ScopedCFTypeRef<SecKeychainRef> roots_keychain;
|
| + // The System Roots keychain is not normally searched by SecItemCopyMatching.
|
| + // Get a reference to it and include in the keychain search list.
|
| + OSStatus status = SecKeychainOpen(
|
| + "/System/Library/Keychains/SystemRootCertificates.keychain",
|
| + roots_keychain.InitializeInto());
|
| + if (status) {
|
| + OSSTATUS_LOG(ERROR, status) << "SecKeychainOpen error";
|
| + return matching_items;
|
| + }
|
| + CFArrayAppendValue(mutable_keychain_search_list, roots_keychain);
|
| +
|
| + CFDictionarySetValue(query, kSecMatchSearchList,
|
| + scoped_alternate_keychain_search_list.get());
|
| +
|
| + OSStatus err = SecItemCopyMatching(
|
| + query, reinterpret_cast<CFTypeRef*>(matching_items.InitializeInto()));
|
| + if (err == errSecItemNotFound) {
|
| + // No matches found.
|
| + return matching_items;
|
| + }
|
| + if (err) {
|
| + OSSTATUS_LOG(ERROR, err) << "SecItemCopyMatching error";
|
| + return matching_items;
|
| + }
|
| + return matching_items;
|
| +}
|
| +
|
| +// static
|
| +base::ScopedCFTypeRef<CFDataRef> TrustStoreMac::GetMacNormalizedIssuer(
|
| + const scoped_refptr<ParsedCertificate>& cert) {
|
| + base::ScopedCFTypeRef<CFDataRef> name_data;
|
| + // There does not appear to be any public API to get the normalized version
|
| + // of a Name without creating a SecCertificate.
|
| + base::ScopedCFTypeRef<SecCertificateRef> cert_handle(
|
| + X509Certificate::CreateOSCertHandleFromBytes(
|
| + cert->der_cert().AsStringPiece().data(), cert->der_cert().Length()));
|
| + if (!cert_handle) {
|
| + LOG(ERROR) << "CreateOSCertHandleFromBytes";
|
| + return name_data;
|
| + }
|
| + {
|
| + base::AutoLock lock(crypto::GetMacSecurityServicesLock());
|
| + name_data.reset(
|
| + SecCertificateCopyNormalizedIssuerContent(cert_handle, nullptr));
|
| + }
|
| + if (!name_data)
|
| + LOG(ERROR) << "SecCertificateCopyNormalizedIssuerContent";
|
| + return name_data;
|
| +}
|
| +
|
| +void TrustStoreMac::FindTrustAnchorsByMacNormalizedSubject(
|
| + CFDataRef name_data,
|
| + TrustAnchors* out_anchors) const {
|
| + base::ScopedCFTypeRef<CFArrayRef> scoped_matching_items =
|
| + FindMatchingCertificatesForMacNormalizedSubject(name_data);
|
| + if (!scoped_matching_items)
|
| + return;
|
| +
|
| + FilterTrustedCertificates(scoped_matching_items.get(), policy_oid_,
|
| + out_anchors);
|
| +}
|
| +
|
| +} // namespace net
|
|
|