| // 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/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_util.h" |
| #include "net/cert/x509_util_mac.h" |
| #include "starboard/types.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 = x509_util::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; |
| } |
| |
| } // namespace |
| |
| TrustStoreMac::TrustStoreMac(CFTypeRef policy_oid) |
| : policy_oid_(base::mac::CFCastStrict<CFStringRef>(policy_oid)) { |
| DCHECK(policy_oid_); |
| } |
| |
| TrustStoreMac::~TrustStoreMac() = default; |
| |
| void TrustStoreMac::SyncGetIssuersOf(const ParsedCertificate* cert, |
| ParsedCertificateList* issuers) { |
| base::ScopedCFTypeRef<CFDataRef> name_data = GetMacNormalizedIssuer(cert); |
| if (!name_data) |
| return; |
| |
| base::ScopedCFTypeRef<CFArrayRef> matching_items = |
| FindMatchingCertificatesForMacNormalizedSubject(name_data); |
| if (!matching_items) |
| return; |
| |
| // Convert to ParsedCertificate. |
| 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))); |
| |
| 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; |
| } |
| |
| issuers->push_back(std::move(anchor_cert)); |
| } |
| } |
| |
| void TrustStoreMac::GetTrust(const scoped_refptr<ParsedCertificate>& cert, |
| CertificateTrust* trust) const { |
| // TODO(eroman): Inefficient -- path building will convert between |
| // SecCertificateRef and ParsedCertificate representations multiple times |
| // (when getting the issuers, and again here). |
| base::ScopedCFTypeRef<SecCertificateRef> cert_handle = |
| x509_util::CreateSecCertificateFromBytes(cert->der_cert().UnsafeData(), |
| cert->der_cert().Length()); |
| if (!cert_handle) { |
| *trust = CertificateTrust::ForUnspecified(); |
| return; |
| } |
| |
| TrustStatus trust_status = |
| IsSecCertificateTrustedForPolicy(cert_handle, policy_oid_); |
| switch (trust_status) { |
| case TrustStatus::TRUSTED: |
| *trust = CertificateTrust::ForTrustAnchor(); |
| return; |
| case TrustStatus::DISTRUSTED: |
| *trust = CertificateTrust::ForDistrusted(); |
| return; |
| case TrustStatus::UNSPECIFIED: |
| *trust = CertificateTrust::ForUnspecified(); |
| return; |
| } |
| |
| *trust = CertificateTrust::ForUnspecified(); |
| return; |
| } |
| |
| // 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 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( |
| x509_util::CreateSecCertificateFromBytes(cert->der_cert().UnsafeData(), |
| cert->der_cert().Length())); |
| if (!cert_handle) { |
| LOG(ERROR) << "CreateCertBufferFromBytes"; |
| return name_data; |
| } |
| { |
| base::AutoLock lock(crypto::GetMacSecurityServicesLock()); |
| name_data.reset( |
| SecCertificateCopyNormalizedIssuerContent(cert_handle, nullptr)); |
| } |
| if (!name_data) |
| LOG(ERROR) << "SecCertificateCopyNormalizedIssuerContent"; |
| return name_data; |
| } |
| |
| } // namespace net |