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

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

Issue 2585963003: PKI library Mac trust store integration (Closed)
Patch Set: test fixes Created 3 years, 11 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
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..74fd586ead64853bd53dbdb8eed7f103695ff5cc
--- /dev/null
+++ b/net/cert/internal/trust_store_mac.cc
@@ -0,0 +1,399 @@
+// Copyright 2016 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 <CoreFoundation/CoreFoundation.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/cssm_init.h" // for LogCSSMError.
+#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"
+
+namespace net {
+
+// XXX for SecCertificateGetData and CSSM_DATA. Could use SecCertificateCopyData
+// instead.
+// CSSM functions are deprecated as of OSX 10.7, but have no replacement.
+// https://bugs.chromium.org/p/chromium/issues/detail?id=590914#c1
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wdeprecated-declarations"
Ryan Sleevi 2017/02/06 23:59:05 I'm happy to just jump to SecCertificateCopyData t
mattm 2017/02/09 23:40:16 Just that one. I've changed to SecCertificateCopyD
+
+namespace {
+
+// XXX
+// The overall trust settings for a certificate are the sum of all the usage
+// constraints dictionaries that match the use for which that certificate is
+// being evaluated. Trust settings for a given use apply if any of the
+// dictionaries in the certificate’s trust settings array satisfies the
+// specified use. Thus, when a certificate has multiple usage constraints
+// dictionaries in its trust settings array, the overall trust settings for the
+// certificate are:
+//
+// ((usage constraint dictionary 0 component 0) AND (usage constraint dictionary
+// 0 component 1) AND (...)) OR ((usage constraint dictionary 1 component 0) AND
+// (usage constraint dictionary 1 component 1) AND (...)) OR (...) ...
+//
+// If the value of the kSecTrustSettingsResult component is not
+// kSecTrustSettingsResultUnspecified for a usage constraints dictionary that
+// has no constraints, the default value kSecTrustSettingsResultTrustRoot is
+// assumed. To specify a value for the kSecTrustSettingsAllowedError component
+// without explicitly trusting or distrusting the associated certificate,
+// specify a value of kSecTrustSettingsResultUnspecified for the
+// kSecTrustSettingsResult component.
+//
+// An empty trust settings array (that is, the trustSettings parameter returns a
+// valid but empty CFArray) means "always trust this certificate” with an
+// overall trust setting for the certificate of
+// kSecTrustSettingsResultTrustRoot. Note that an empty trust settings array is
+// not the same as no trust settings (the trustSettings parameter returns NULL),
+// which means "this certificate must be verified to a known trusted
+// certificate”.
+//
+// Note the distinction between the results kSecTrustSettingsResultTrustRoot and
+// kSecTrustSettingsResultTrustAsRoot: The former can only be applied to root
+// (self-signed) certificates; the latter can only be applied to non-root
+// certificates. Therefore, an empty trust settings array for a non-root
+// certificate is invalid, because the default value of
+// kSecTrustSettingsResultTrustRoot is not valid for a non-root certificate.
Ryan Sleevi 2017/02/06 23:59:04 A++ documentation - would read again.
+
+bool IsTrustDictionaryTrustedForPolicy(CFDictionaryRef trust_dict,
+ bool is_self_signed,
+ const CFStringRef target_policy_oid) {
+ DVLOG(2) << "IsTrustDictionaryTrustedForPolicy?";
+ // 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|.
+
+ // Ignore application-specific trust settings.
+ // TODO: should handle these? (checking if there are application settings
+ // specific to chrome?)
Ryan Sleevi 2017/02/06 23:59:04 I'm OK with saying no to this, because then it get
mattm 2017/02/09 23:40:16 Acknowledged.
+ if (CFDictionaryContainsKey(trust_dict, kSecTrustSettingsApplication)) {
+ DVLOG(2) << "ignored trust dictionary with kSecTrustSettingsApplication";
+ return false;
+ }
+ // Ignore trust settings that contain policy-specific constraints. (E.g.,
+ // for SSL, settings that apply to a single hostname.)
+ if (CFDictionaryContainsKey(trust_dict, kSecTrustSettingsPolicyString)) {
+ DVLOG(2) << "ignored trust dictionary with kSecTrustSettingsPolicyString";
+ return false;
+ }
+
+ // XXX do we care about keyusage?
+ // CFNumberRef key_usage =
+ // base::mac::GetValueFromDictionary<CFNumberRef>(trust_dict,
+ // kSecTrustSettingsKeyUsage);
Ryan Sleevi 2017/02/06 23:59:05 I haven't actually seen where Apple surfaces this
mattm 2017/02/09 23:40:16 Acknowledged.
+
+ CFTypeRef policy_typeref =
+ CFDictionaryGetValue(trust_dict, kSecTrustSettingsPolicy);
+ SecPolicyRef policy_ref = nullptr;
+ if (policy_typeref) {
+ if (CFGetTypeID(policy_typeref) != SecPolicyGetTypeID()) {
+ DVLOG(2) << "kSecTrustSettingsPolicy not a SecPolicyRef";
+ return false;
+ }
+ policy_ref =
+ reinterpret_cast<SecPolicyRef>(const_cast<void*>(policy_typeref));
+ }
+ // XXX do some manual testing that this policy stuff actually works properly.
+ // XXX is there any hierarchy to the policies? (eg, does
+ // a trust dict with kSecPolicyAppleX509Basic apply to SSL verification?)
Ryan Sleevi 2017/02/06 23:59:05 AIUI, no; each policy is treated as distinct (you
mattm 2017/02/09 23:40:17 I did some manual testing. If you set a cert as tr
+ if (policy_ref) {
+ CFDictionaryRef policy_dict = SecPolicyCopyProperties(policy_ref);
+ CFTypeRef value = CFDictionaryGetValue(policy_dict, kSecPolicyOid);
+ // Having a kSecTrustSettingsPolicy without a kSecPolicyOid element is an
+ // error.
+ if (!value) {
+ DVLOG(2) << "kSecTrustSettingsPolicy with no kSecPolicyOid!";
+ return false;
+ }
+ CFStringRef policy_oid = base::mac::CFCast<CFStringRef>(value);
+ if (!policy_oid) {
+ DVLOG(2) << "kSecPolicyOid value not a CFStringRef!";
+ return false;
+ }
+ base::ScopedCFTypeRef<CFDictionaryRef> scoped_policy_dict(policy_dict);
+ if (!CFEqual(policy_oid, target_policy_oid)) {
+ DVLOG(2) << "kSecPolicyOid does not match target policy oid";
+ return false;
+ }
+ // XXX care about kSecPolicyName, kSecPolicyClient, kSecPolicyKU_<foo> ?
+ }
+
+ // 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) {
+ if (!CFNumberGetValue(trust_settings_result_ref, kCFNumberIntType,
+ &trust_settings_result)) {
+ DVLOG(2) << "CFNumberGetValue fail";
+ return false;
+ }
+ }
+
+ // TODO: handle distrust (kSecTrustSettingsResultDeny)
+
+ // kSecTrustSettingsResultTrustRoot can only be applied to root(self-signed)
+ // certs.
+ if (is_self_signed)
+ return trust_settings_result == kSecTrustSettingsResultTrustRoot;
+
+ // kSecTrustSettingsResultTrustAsRoot can only be applied to non-root certs.
+ return trust_settings_result == kSecTrustSettingsResultTrustAsRoot;
+}
+
+bool IsTrustSettingsTrustedForPolicy(CFArrayRef trust_settings,
+ bool is_self_signed,
+ const CFStringRef policy_oid) {
+ DVLOG(2) << "IsTrustSettingsTrustedForPolicy?";
+ // An empty trust settings array (that is, the trustSettings 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) {
+ DVLOG(2) << "Empty trust settings array and self-signed cert = trusted";
+ return true;
+ }
+
+ for (CFIndex i = 0, settings_count = CFArrayGetCount(trust_settings);
+ i < settings_count; ++i) {
+ DVLOG(2) << "IsTrustSettingsTrustedForPolicy i=" << i << " of "
+ << settings_count;
+ CFDictionaryRef trust_dict = reinterpret_cast<CFDictionaryRef>(
+ const_cast<void*>(CFArrayGetValueAtIndex(trust_settings, i)));
+ if (IsTrustDictionaryTrustedForPolicy(trust_dict, is_self_signed,
+ policy_oid))
+ return true;
+ }
+ return false;
+}
+
+bool IsSecCertificateTrustedForPolicy(SecCertificateRef cert_handle,
+ bool is_self_signed,
+ const CFStringRef policy_oid) {
+ for (const auto& trust_domain :
+ {kSecTrustSettingsDomainUser, kSecTrustSettingsDomainAdmin,
+ kSecTrustSettingsDomainSystem}) {
+ DVLOG(2) << "IsSecCertificateTrustedForPolicy domain = " << trust_domain;
+ CFArrayRef trust_settings;
+ OSStatus err = SecTrustSettingsCopyTrustSettings(cert_handle, trust_domain,
+ &trust_settings);
+ if (err == errSecItemNotFound) {
+ DVLOG(2) << "no trust settings for that domain";
+ // No trust settings for that domain.. try the next.
+ continue;
+ }
+ if (err) {
+ OSSTATUS_LOG(ERROR, err) << "SecTrustSettingsCopyTrustSettings error";
+ continue;
+ }
+ base::ScopedCFTypeRef<CFArrayRef> scoped_trust_settings(trust_settings);
+ if (IsTrustSettingsTrustedForPolicy(trust_settings, is_self_signed,
+ policy_oid))
+ return true;
+ }
+
+ // No trust settings, or none of the settings were for the correct policy, or
+ // had the correct trust result.
+ return false;
+}
+
+} // namespace
+
+TrustStoreMac::TrustStoreMac(const void* policy_oid)
+ : policy_oid_(base::mac::CFCast<CFStringRef>(policy_oid)) {
+ DCHECK(policy_oid_);
+}
+
+TrustStoreMac::~TrustStoreMac() = default;
+
+// static
+base::ScopedCFTypeRef<CFArrayRef>
+TrustStoreMac::FindMatchingCertificatesForMacNormalizedSubject(
+ CFDataRef name_data) {
+ base::ScopedCFTypeRef<CFArrayRef> matching_items;
+ base::ScopedCFTypeRef<CFMutableDictionaryRef> query(
+ CFDictionaryCreateMutable(NULL, 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()) {
+ CFArrayRef alternate_keychain_search_list;
+ OSStatus status = TestKeychainSearchList::GetInstance()->CopySearchList(
+ &alternate_keychain_search_list);
+ if (status) {
+ OSSTATUS_LOG(ERROR, status)
+ << "TestKeychainSearchList::CopySearchList error";
+ return matching_items;
+ }
+ scoped_alternate_keychain_search_list.reset(alternate_keychain_search_list);
+ }
+ // XXX this is all a bit similar (but not quite the same) as
+ // cert_verify_proc_mac keychain stuff.... is there a way to reduce
+ // duplication?
+ //
+ // 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) {
+ CFArrayRef keychain_search_list;
+ OSStatus status = SecKeychainCopySearchList(&keychain_search_list);
+ if (status) {
+ OSSTATUS_LOG(ERROR, status) << "SecKeychainCopySearchList error";
+ return matching_items;
+ }
+ scoped_alternate_keychain_search_list.reset(keychain_search_list);
+ }
+ 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);
+ 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);
+ if (status) {
+ OSSTATUS_LOG(ERROR, status) << "SecKeychainOpen error";
+ return matching_items;
+ }
+ base::ScopedCFTypeRef<SecKeychainRef> scoped_roots_keychain(roots_keychain);
+ CFArrayAppendValue(mutable_keychain_search_list, roots_keychain);
+ CFDictionarySetValue(query, kSecMatchSearchList,
+ scoped_alternate_keychain_search_list.get());
+
+ OSStatus err;
+ {
+ base::AutoLock lock(crypto::GetMacSecurityServicesLock());
+ err =
+ SecItemCopyMatching(query, (CFTypeRef*)matching_items.InitializeInto());
Ryan Sleevi 2017/02/06 23:59:04 reinterpret_cast ? Although I think it should toll
mattm 2017/02/09 23:40:17 done. I think the reinterpret_cast is needed since
+ }
+ if (err == errSecItemNotFound) {
+ DVLOG(2) << "SecItemCopyMatching: no matches found";
+ return matching_items;
+ }
+ if (err) {
+ crypto::LogCSSMError("SecItemCopyMatching", err);
+ OSSTATUS_LOG(ERROR, err) << "SecItemCopyMatching error";
+ return matching_items;
+ }
+ return matching_items;
+}
+
+// static
+void TrustStoreMac::FilterTrustedCertificates(CFArrayRef matching_items,
+ const CFStringRef policy_oid,
+ TrustAnchors* out_anchors) {
+ for (CFIndex i = 0, item_count = CFArrayGetCount(matching_items);
+ i < item_count; ++i) {
+ DVLOG(2) << "i=" << i << " of " << item_count;
+ SecCertificateRef match_cert_handle = reinterpret_cast<SecCertificateRef>(
+ const_cast<void*>(CFArrayGetValueAtIndex(matching_items, i)));
+
+ if (!IsSecCertificateTrustedForPolicy(
+ match_cert_handle, X509Certificate::IsSelfSigned(match_cert_handle),
+ policy_oid)) {
+ DVLOG(2) << "not trusted for policy";
+ continue;
+ } else {
+ DVLOG(2) << "trusted!";
+ }
+
+ CSSM_DATA der_data;
+ OSStatus err = SecCertificateGetData(match_cert_handle, &der_data);
+ if (err != noErr) {
+ OSSTATUS_LOG(ERROR, err) << "SecCertificateGetData error";
+ continue;
+ }
+
+ CertErrors errors;
+ ParseCertificateOptions options;
+ options.allow_invalid_serial_numbers = true;
+ scoped_refptr<ParsedCertificate> anchor_cert = ParsedCertificate::Create(
+ der_data.Data, der_data.Length, options, &errors);
+ // TODO(mattm): if parsing fails, it would be legit to return a TrustAnchor
+ // that doesn't have an associated cert, getting the SPKI and Subject
+ // through the SecCertificate APIs.
Ryan Sleevi 2017/02/06 23:59:04 I would say if parsing fails, we're good to reject
mattm 2017/02/09 23:40:17 Ack. fwiw, There were two such certs I found (on
+ if (!anchor_cert) {
+ // TODO(crbug.com/634443): return errors better.
+ LOG(ERROR) << "Error parsing issuer certificate:\n"
+ << errors.ToDebugString();
+ continue;
+ }
+
+ DVLOG(2) << "added cert to out_anchors";
+ out_anchors->push_back(TrustAnchor::CreateFromCertificateNoConstraints(
+ std::move(anchor_cert)));
+ }
+}
+
+// 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.
Ryan Sleevi 2017/02/06 23:59:04 Unfortunately, yeah, that's the case. I'd be hesit
mattm 2017/02/09 23:40:16 Acknowledged.
+ X509Certificate::OSCertHandle cert_handle =
+ X509Certificate::CreateOSCertHandleFromBytes(
+ cert->der_cert().AsStringPiece().data(), cert->der_cert().Length());
+ if (!cert_handle) {
+ LOG(ERROR) << "CreateOSCertHandleFromBytes";
+ return name_data;
+ }
+ base::ScopedCFTypeRef<SecCertificateRef> scoped_cert_handle(cert_handle);
+ 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);
+}
+
+void TrustStoreMac::FindTrustAnchorsForCert(
+ const scoped_refptr<ParsedCertificate>& cert,
+ TrustAnchors* out_anchors) const {
+ DVLOG(2) << "FindTrustAnchorsForCert";
+
+ base::ScopedCFTypeRef<CFDataRef> name_data = GetMacNormalizedIssuer(cert);
+
+ FindTrustAnchorsByMacNormalizedSubject(name_data, out_anchors);
+}
+
+} // namespace net

Powered by Google App Engine
This is Rietveld 408576698