Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(1164)

Unified Diff: net/cert/internal/trust_store_mac.cc

Issue 2585963003: PKI library Mac trust store integration (Closed)
Patch Set: updates for rebase & cl format Created 3 years, 10 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « net/cert/internal/trust_store_mac.h ('k') | net/cert/internal/trust_store_mac_unittest.cc » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
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
« no previous file with comments | « net/cert/internal/trust_store_mac.h ('k') | net/cert/internal/trust_store_mac_unittest.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698