mattm | ea4ed823 | 2017-02-28 23:13:23 | [diff] [blame] | 1 | // Copyright 2017 The Chromium Authors. All rights reserved. |
| 2 | // Use of this source code is governed by a BSD-style license that can be |
| 3 | // found in the LICENSE file. |
| 4 | |
| 5 | #include "net/cert/internal/trust_store_mac.h" |
| 6 | |
| 7 | #include <Security/Security.h> |
| 8 | |
| 9 | #include "base/logging.h" |
| 10 | #include "base/mac/foundation_util.h" |
| 11 | #include "base/mac/mac_logging.h" |
| 12 | #include "base/memory/ptr_util.h" |
| 13 | #include "base/synchronization/lock.h" |
| 14 | #include "crypto/mac_security_services_lock.h" |
| 15 | #include "net/cert/internal/cert_errors.h" |
| 16 | #include "net/cert/internal/parse_name.h" |
| 17 | #include "net/cert/internal/parsed_certificate.h" |
| 18 | #include "net/cert/test_keychain_search_list_mac.h" |
| 19 | #include "net/cert/x509_certificate.h" |
| 20 | #include "net/cert/x509_util.h" |
| 21 | |
| 22 | namespace net { |
| 23 | |
| 24 | namespace { |
| 25 | |
| 26 | // The rules for interpreting trust settings are documented at: |
| 27 | // https://developer.apple.com/reference/security/1400261-sectrustsettingscopytrustsetting?language=objc |
| 28 | |
| 29 | // Indicates the trust status of a certificate. |
| 30 | enum class TrustStatus { |
| 31 | // Certificate inherits trust value from its issuer. If the certificate is the |
| 32 | // root of the chain, this implies distrust. |
| 33 | UNSPECIFIED, |
| 34 | // Certificate is a trust anchor. |
| 35 | TRUSTED, |
| 36 | // Certificate is blacklisted / explicitly distrusted. |
| 37 | DISTRUSTED |
| 38 | }; |
| 39 | |
| 40 | // Returns trust status of usage constraints dictionary |trust_dict| for a |
| 41 | // certificate that |is_self_signed|. |
| 42 | TrustStatus IsTrustDictionaryTrustedForPolicy( |
| 43 | CFDictionaryRef trust_dict, |
| 44 | bool is_self_signed, |
| 45 | const CFStringRef target_policy_oid) { |
| 46 | // An empty trust dict should be interpreted as |
| 47 | // kSecTrustSettingsResultTrustRoot. This is handled by falling through all |
| 48 | // the conditions below with the default value of |trust_settings_result|. |
| 49 | |
| 50 | // Trust settings may be scoped to a single application, by checking that the |
| 51 | // code signing identity of the current application matches the serialized |
| 52 | // code signing identity in the kSecTrustSettingsApplication key. |
| 53 | // As this is not presently supported, skip any trust settings scoped to the |
| 54 | // application. |
| 55 | if (CFDictionaryContainsKey(trust_dict, kSecTrustSettingsApplication)) |
| 56 | return TrustStatus::UNSPECIFIED; |
| 57 | |
| 58 | // Trust settings may be scoped using policy-specific constraints. For |
| 59 | // example, SSL trust settings might be scoped to a single hostname, or EAP |
| 60 | // settings specific to a particular WiFi network. |
| 61 | // As this is not presently supported, skip any policy-specific trust |
| 62 | // settings. |
| 63 | if (CFDictionaryContainsKey(trust_dict, kSecTrustSettingsPolicyString)) |
| 64 | return TrustStatus::UNSPECIFIED; |
| 65 | |
| 66 | // Ignoring kSecTrustSettingsKeyUsage for now; it does not seem relevant to |
| 67 | // the TLS case. |
| 68 | |
| 69 | // If the trust settings are scoped to a specific policy (via |
| 70 | // kSecTrustSettingsPolicy), ensure that the policy is the same policy as |
| 71 | // |target_policy_oid|. If there is no kSecTrustSettingsPolicy key, it's |
| 72 | // considered a match for all policies. |
| 73 | SecPolicyRef policy_ref = base::mac::GetValueFromDictionary<SecPolicyRef>( |
| 74 | trust_dict, kSecTrustSettingsPolicy); |
| 75 | if (policy_ref) { |
| 76 | base::ScopedCFTypeRef<CFDictionaryRef> policy_dict; |
| 77 | { |
| 78 | base::AutoLock lock(crypto::GetMacSecurityServicesLock()); |
| 79 | policy_dict.reset(SecPolicyCopyProperties(policy_ref)); |
| 80 | } |
| 81 | |
| 82 | // kSecPolicyOid is guaranteed to be present in the policy dictionary. |
| 83 | // |
| 84 | // TODO(mattm): remove the CFCastStrict below once Chromium builds against |
| 85 | // the 10.11 SDK. |
| 86 | CFStringRef policy_oid = base::mac::GetValueFromDictionary<CFStringRef>( |
| 87 | policy_dict, base::mac::CFCastStrict<CFStringRef>(kSecPolicyOid)); |
| 88 | |
| 89 | if (!CFEqual(policy_oid, target_policy_oid)) |
| 90 | return TrustStatus::UNSPECIFIED; |
| 91 | } |
| 92 | |
| 93 | // If kSecTrustSettingsResult is not present in the trust dict, |
| 94 | // kSecTrustSettingsResultTrustRoot is assumed. |
| 95 | int trust_settings_result = kSecTrustSettingsResultTrustRoot; |
| 96 | CFNumberRef trust_settings_result_ref = |
| 97 | base::mac::GetValueFromDictionary<CFNumberRef>(trust_dict, |
| 98 | kSecTrustSettingsResult); |
| 99 | if (trust_settings_result_ref && |
| 100 | !CFNumberGetValue(trust_settings_result_ref, kCFNumberIntType, |
| 101 | &trust_settings_result)) { |
| 102 | return TrustStatus::UNSPECIFIED; |
| 103 | } |
| 104 | |
| 105 | if (trust_settings_result == kSecTrustSettingsResultDeny) |
| 106 | return TrustStatus::DISTRUSTED; |
| 107 | |
| 108 | // kSecTrustSettingsResultTrustRoot can only be applied to root(self-signed) |
| 109 | // certs. |
| 110 | if (is_self_signed) |
| 111 | return (trust_settings_result == kSecTrustSettingsResultTrustRoot) |
| 112 | ? TrustStatus::TRUSTED |
| 113 | : TrustStatus::UNSPECIFIED; |
| 114 | |
| 115 | // kSecTrustSettingsResultTrustAsRoot can only be applied to non-root certs. |
| 116 | return (trust_settings_result == kSecTrustSettingsResultTrustAsRoot) |
| 117 | ? TrustStatus::TRUSTED |
| 118 | : TrustStatus::UNSPECIFIED; |
| 119 | } |
| 120 | |
| 121 | // Returns true if the trust settings array |trust_settings| for a certificate |
| 122 | // that |is_self_signed| should be treated as a trust anchor. |
| 123 | TrustStatus IsTrustSettingsTrustedForPolicy(CFArrayRef trust_settings, |
| 124 | bool is_self_signed, |
| 125 | const CFStringRef policy_oid) { |
| 126 | // An empty trust settings array (that is, the trust_settings parameter |
| 127 | // returns a valid but empty CFArray) means "always trust this certificate" |
| 128 | // with an overall trust setting for the certificate of |
| 129 | // kSecTrustSettingsResultTrustRoot. |
| 130 | if (CFArrayGetCount(trust_settings) == 0 && is_self_signed) |
| 131 | return TrustStatus::TRUSTED; |
| 132 | |
| 133 | for (CFIndex i = 0, settings_count = CFArrayGetCount(trust_settings); |
| 134 | i < settings_count; ++i) { |
| 135 | CFDictionaryRef trust_dict = reinterpret_cast<CFDictionaryRef>( |
| 136 | const_cast<void*>(CFArrayGetValueAtIndex(trust_settings, i))); |
| 137 | TrustStatus trust = IsTrustDictionaryTrustedForPolicy( |
| 138 | trust_dict, is_self_signed, policy_oid); |
| 139 | if (trust != TrustStatus::UNSPECIFIED) |
| 140 | return trust; |
| 141 | } |
| 142 | return TrustStatus::UNSPECIFIED; |
| 143 | } |
| 144 | |
| 145 | // Returns true if the certificate |cert_handle| is trusted for the policy |
| 146 | // |policy_oid|. |
| 147 | TrustStatus IsSecCertificateTrustedForPolicy(SecCertificateRef cert_handle, |
| 148 | const CFStringRef policy_oid) { |
| 149 | const bool is_self_signed = X509Certificate::IsSelfSigned(cert_handle); |
| 150 | // Evaluate trust domains in user, admin, system order. Admin settings can |
| 151 | // override system ones, and user settings can override both admin and system. |
| 152 | for (const auto& trust_domain : |
| 153 | {kSecTrustSettingsDomainUser, kSecTrustSettingsDomainAdmin, |
| 154 | kSecTrustSettingsDomainSystem}) { |
| 155 | base::ScopedCFTypeRef<CFArrayRef> trust_settings; |
| 156 | OSStatus err; |
| 157 | { |
| 158 | base::AutoLock lock(crypto::GetMacSecurityServicesLock()); |
| 159 | err = SecTrustSettingsCopyTrustSettings(cert_handle, trust_domain, |
| 160 | trust_settings.InitializeInto()); |
| 161 | } |
| 162 | if (err == errSecItemNotFound) { |
| 163 | // No trust settings for that domain.. try the next. |
| 164 | continue; |
| 165 | } |
| 166 | if (err) { |
| 167 | OSSTATUS_LOG(ERROR, err) << "SecTrustSettingsCopyTrustSettings error"; |
| 168 | continue; |
| 169 | } |
| 170 | TrustStatus trust = IsTrustSettingsTrustedForPolicy( |
| 171 | trust_settings, is_self_signed, policy_oid); |
| 172 | if (trust != TrustStatus::UNSPECIFIED) |
| 173 | return trust; |
| 174 | } |
| 175 | |
| 176 | // No trust settings, or none of the settings were for the correct policy, or |
| 177 | // had the correct trust result. |
| 178 | return TrustStatus::UNSPECIFIED; |
| 179 | } |
| 180 | |
| 181 | // Filters an array of SecCertificateRef by trust for |policy_oid|, returning |
| 182 | // the results as TrustAnchors in |out_anchors|. |
| 183 | void FilterTrustedCertificates(CFArrayRef matching_items, |
| 184 | const CFStringRef policy_oid, |
| 185 | TrustAnchors* out_anchors) { |
| 186 | for (CFIndex i = 0, item_count = CFArrayGetCount(matching_items); |
| 187 | i < item_count; ++i) { |
| 188 | SecCertificateRef match_cert_handle = reinterpret_cast<SecCertificateRef>( |
| 189 | const_cast<void*>(CFArrayGetValueAtIndex(matching_items, i))); |
| 190 | |
| 191 | if (IsSecCertificateTrustedForPolicy(match_cert_handle, policy_oid) != |
| 192 | TrustStatus::TRUSTED) |
| 193 | continue; |
| 194 | |
| 195 | base::ScopedCFTypeRef<CFDataRef> der_data( |
| 196 | SecCertificateCopyData(match_cert_handle)); |
| 197 | if (!der_data) { |
| 198 | LOG(ERROR) << "SecCertificateCopyData error"; |
| 199 | continue; |
| 200 | } |
| 201 | |
| 202 | CertErrors errors; |
| 203 | ParseCertificateOptions options; |
| 204 | options.allow_invalid_serial_numbers = true; |
| 205 | scoped_refptr<ParsedCertificate> anchor_cert = ParsedCertificate::Create( |
| 206 | x509_util::CreateCryptoBuffer(CFDataGetBytePtr(der_data.get()), |
| 207 | CFDataGetLength(der_data.get())), |
| 208 | options, &errors); |
| 209 | if (!anchor_cert) { |
| 210 | // TODO(crbug.com/634443): return errors better. |
| 211 | LOG(ERROR) << "Error parsing issuer certificate:\n" |
| 212 | << errors.ToDebugString(); |
| 213 | continue; |
| 214 | } |
| 215 | |
| 216 | out_anchors->push_back(TrustAnchor::CreateFromCertificateNoConstraints( |
| 217 | std::move(anchor_cert))); |
| 218 | } |
| 219 | } |
| 220 | |
| 221 | } // namespace |
| 222 | |
| 223 | TrustStoreMac::TrustStoreMac(CFTypeRef policy_oid) |
| 224 | : policy_oid_(base::mac::CFCastStrict<CFStringRef>(policy_oid)) { |
| 225 | DCHECK(policy_oid_); |
| 226 | } |
| 227 | |
| 228 | TrustStoreMac::~TrustStoreMac() = default; |
| 229 | |
| 230 | void TrustStoreMac::FindTrustAnchorsForCert( |
| 231 | const scoped_refptr<ParsedCertificate>& cert, |
| 232 | TrustAnchors* out_anchors) const { |
| 233 | base::ScopedCFTypeRef<CFDataRef> name_data = GetMacNormalizedIssuer(cert); |
| 234 | |
| 235 | FindTrustAnchorsByMacNormalizedSubject(name_data, out_anchors); |
| 236 | } |
| 237 | |
| 238 | // static |
| 239 | base::ScopedCFTypeRef<CFArrayRef> |
| 240 | TrustStoreMac::FindMatchingCertificatesForMacNormalizedSubject( |
| 241 | CFDataRef name_data) { |
| 242 | base::ScopedCFTypeRef<CFArrayRef> matching_items; |
| 243 | base::ScopedCFTypeRef<CFMutableDictionaryRef> query( |
| 244 | CFDictionaryCreateMutable(nullptr, 0, &kCFTypeDictionaryKeyCallBacks, |
| 245 | &kCFTypeDictionaryValueCallBacks)); |
| 246 | |
| 247 | CFDictionarySetValue(query, kSecClass, kSecClassCertificate); |
| 248 | CFDictionarySetValue(query, kSecReturnRef, kCFBooleanTrue); |
| 249 | CFDictionarySetValue(query, kSecMatchLimit, kSecMatchLimitAll); |
| 250 | CFDictionarySetValue(query, kSecAttrSubject, name_data); |
| 251 | |
| 252 | base::ScopedCFTypeRef<CFArrayRef> scoped_alternate_keychain_search_list; |
| 253 | if (TestKeychainSearchList::HasInstance()) { |
| 254 | OSStatus status = TestKeychainSearchList::GetInstance()->CopySearchList( |
| 255 | scoped_alternate_keychain_search_list.InitializeInto()); |
| 256 | if (status) { |
| 257 | OSSTATUS_LOG(ERROR, status) |
| 258 | << "TestKeychainSearchList::CopySearchList error"; |
| 259 | return matching_items; |
| 260 | } |
| 261 | } |
| 262 | |
| 263 | base::AutoLock lock(crypto::GetMacSecurityServicesLock()); |
| 264 | |
| 265 | // If a TestKeychainSearchList is present, it will have already set |
| 266 | // |scoped_alternate_keychain_search_list|, which will be used as the |
| 267 | // basis for reordering the keychain. Otherwise, get the current keychain |
| 268 | // search list and use that. |
| 269 | if (!scoped_alternate_keychain_search_list) { |
| 270 | OSStatus status = SecKeychainCopySearchList( |
| 271 | scoped_alternate_keychain_search_list.InitializeInto()); |
| 272 | if (status) { |
| 273 | OSSTATUS_LOG(ERROR, status) << "SecKeychainCopySearchList error"; |
| 274 | return matching_items; |
| 275 | } |
| 276 | } |
| 277 | |
| 278 | CFMutableArrayRef mutable_keychain_search_list = CFArrayCreateMutableCopy( |
| 279 | kCFAllocatorDefault, |
| 280 | CFArrayGetCount(scoped_alternate_keychain_search_list.get()) + 1, |
| 281 | scoped_alternate_keychain_search_list.get()); |
| 282 | if (!mutable_keychain_search_list) { |
| 283 | LOG(ERROR) << "CFArrayCreateMutableCopy"; |
| 284 | return matching_items; |
| 285 | } |
| 286 | scoped_alternate_keychain_search_list.reset(mutable_keychain_search_list); |
| 287 | |
| 288 | base::ScopedCFTypeRef<SecKeychainRef> roots_keychain; |
| 289 | // The System Roots keychain is not normally searched by SecItemCopyMatching. |
| 290 | // Get a reference to it and include in the keychain search list. |
| 291 | OSStatus status = SecKeychainOpen( |
| 292 | "/System/Library/Keychains/SystemRootCertificates.keychain", |
| 293 | roots_keychain.InitializeInto()); |
| 294 | if (status) { |
| 295 | OSSTATUS_LOG(ERROR, status) << "SecKeychainOpen error"; |
| 296 | return matching_items; |
| 297 | } |
| 298 | CFArrayAppendValue(mutable_keychain_search_list, roots_keychain); |
| 299 | |
| 300 | CFDictionarySetValue(query, kSecMatchSearchList, |
| 301 | scoped_alternate_keychain_search_list.get()); |
| 302 | |
| 303 | OSStatus err = SecItemCopyMatching( |
| 304 | query, reinterpret_cast<CFTypeRef*>(matching_items.InitializeInto())); |
| 305 | if (err == errSecItemNotFound) { |
| 306 | // No matches found. |
| 307 | return matching_items; |
| 308 | } |
| 309 | if (err) { |
| 310 | OSSTATUS_LOG(ERROR, err) << "SecItemCopyMatching error"; |
| 311 | return matching_items; |
| 312 | } |
| 313 | return matching_items; |
| 314 | } |
| 315 | |
| 316 | // static |
| 317 | base::ScopedCFTypeRef<CFDataRef> TrustStoreMac::GetMacNormalizedIssuer( |
| 318 | const scoped_refptr<ParsedCertificate>& cert) { |
| 319 | base::ScopedCFTypeRef<CFDataRef> name_data; |
| 320 | // There does not appear to be any public API to get the normalized version |
| 321 | // of a Name without creating a SecCertificate. |
| 322 | base::ScopedCFTypeRef<SecCertificateRef> cert_handle( |
| 323 | X509Certificate::CreateOSCertHandleFromBytes( |
| 324 | cert->der_cert().AsStringPiece().data(), cert->der_cert().Length())); |
| 325 | if (!cert_handle) { |
| 326 | LOG(ERROR) << "CreateOSCertHandleFromBytes"; |
| 327 | return name_data; |
| 328 | } |
| 329 | { |
| 330 | base::AutoLock lock(crypto::GetMacSecurityServicesLock()); |
| 331 | name_data.reset( |
| 332 | SecCertificateCopyNormalizedIssuerContent(cert_handle, nullptr)); |
| 333 | } |
| 334 | if (!name_data) |
| 335 | LOG(ERROR) << "SecCertificateCopyNormalizedIssuerContent"; |
| 336 | return name_data; |
| 337 | } |
| 338 | |
| 339 | void TrustStoreMac::FindTrustAnchorsByMacNormalizedSubject( |
| 340 | CFDataRef name_data, |
| 341 | TrustAnchors* out_anchors) const { |
| 342 | base::ScopedCFTypeRef<CFArrayRef> scoped_matching_items = |
| 343 | FindMatchingCertificatesForMacNormalizedSubject(name_data); |
| 344 | if (!scoped_matching_items) |
| 345 | return; |
| 346 | |
| 347 | FilterTrustedCertificates(scoped_matching_items.get(), policy_oid_, |
| 348 | out_anchors); |
| 349 | } |
| 350 | |
| 351 | } // namespace net |