Chromium Code Reviews| OLD | NEW |
|---|---|
| (Empty) | |
| 1 // Copyright 2016 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 | |
| 21 namespace net { | |
| 22 | |
| 23 namespace { | |
| 24 | |
| 25 // The rules for interpreting trust settings are documented at: | |
| 26 // https://developer.apple.com/reference/security/1400261-sectrustsettingscopytr ustsetting?language=objc | |
| 27 | |
| 28 // Returns true if a usage constraints dictionary |trust_dict| for a certificate | |
| 29 // that |is_self_signed| should be treated as a trust anchor. | |
| 30 bool IsTrustDictionaryTrustedForPolicy(CFDictionaryRef trust_dict, | |
| 31 bool is_self_signed, | |
| 32 const CFStringRef target_policy_oid) { | |
| 33 // An empty trust dict should be interpreted as | |
| 34 // kSecTrustSettingsResultTrustRoot. This is handled by falling through all | |
| 35 // the conditions below with the default value of |trust_settings_result|. | |
| 36 | |
| 37 // Trust settings may be scoped to a single application, by checking that the | |
| 38 // code signing identity of the current application matches the serialized | |
| 39 // code signing identity in the kSecTrustSettingsApplication key. | |
| 40 // As this is not presently supported, skip any trust settings scoped to the | |
| 41 // application. | |
| 42 if (CFDictionaryContainsKey(trust_dict, kSecTrustSettingsApplication)) | |
| 43 return false; | |
| 44 | |
| 45 // Trust settings may be scoped using policy-specific constraints. For | |
| 46 // example, SSL trust settings might be scoped to a single hostname, or EAP | |
| 47 // settings specific to a particular WiFi network. | |
| 48 // As this is not presently supported, skip any policy-specific trust | |
| 49 // settings. | |
| 50 if (CFDictionaryContainsKey(trust_dict, kSecTrustSettingsPolicyString)) | |
| 51 return false; | |
| 52 | |
| 53 // Ignoring kSecTrustSettingsKeyUsage for now; it does not seem relevant to | |
| 54 // the TLS case. | |
| 55 | |
| 56 // If the trust settings are scoped to a specific policy (via | |
| 57 // kSecTrustSettingsPolicy), ensure that the policy is the same policy as | |
| 58 // |target_policy_oid|. If there is no kSecTrustSettingsPolicy key, it's | |
| 59 // considered a match for all policies. | |
| 60 SecPolicyRef policy_ref = base::mac::GetValueFromDictionary<SecPolicyRef>( | |
| 61 trust_dict, kSecTrustSettingsPolicy); | |
| 62 if (policy_ref) { | |
| 63 base::ScopedCFTypeRef<CFDictionaryRef> policy_dict( | |
| 64 SecPolicyCopyProperties(policy_ref)); | |
| 65 | |
| 66 // kSecPolicyOid is guaranteed to be present in the policy dictionary. | |
| 67 // | |
| 68 // TODO(mattm): remove the CFCastStrict below once Chromium builds against | |
| 69 // the 10.11 SDK. | |
| 70 CFStringRef policy_oid = base::mac::GetValueFromDictionary<CFStringRef>( | |
| 71 policy_dict, base::mac::CFCastStrict<CFStringRef>(kSecPolicyOid)); | |
| 72 | |
| 73 if (!CFEqual(policy_oid, target_policy_oid)) | |
| 74 return false; | |
| 75 } | |
| 76 | |
| 77 // If kSecTrustSettingsResult is not present in the trust dict, | |
| 78 // kSecTrustSettingsResultTrustRoot is assumed. | |
| 79 int trust_settings_result = kSecTrustSettingsResultTrustRoot; | |
| 80 CFNumberRef trust_settings_result_ref = | |
| 81 base::mac::GetValueFromDictionary<CFNumberRef>(trust_dict, | |
| 82 kSecTrustSettingsResult); | |
| 83 | |
| 84 if (trust_settings_result_ref) { | |
| 85 if (!CFNumberGetValue(trust_settings_result_ref, kCFNumberIntType, | |
|
Ryan Sleevi
2017/02/16 22:27:22
Perhaps fold this in to line 84 with an &&
mattm
2017/02/17 00:04:18
Done.
| |
| 86 &trust_settings_result)) { | |
| 87 return false; | |
| 88 } | |
| 89 } | |
| 90 | |
| 91 // TODO: handle distrust (kSecTrustSettingsResultDeny) | |
|
Ryan Sleevi
2017/02/16 22:27:22
It looks like you're already handling that, by way
mattm
2017/02/17 00:04:18
Isn't there still the case where you have a distru
Ryan Sleevi
2017/02/17 00:54:13
Yup. I was thinking solely for the low-hanging fru
| |
| 92 | |
| 93 // kSecTrustSettingsResultTrustRoot can only be applied to root(self-signed) | |
| 94 // certs. | |
| 95 if (is_self_signed) | |
| 96 return trust_settings_result == kSecTrustSettingsResultTrustRoot; | |
| 97 | |
| 98 // kSecTrustSettingsResultTrustAsRoot can only be applied to non-root certs. | |
| 99 return trust_settings_result == kSecTrustSettingsResultTrustAsRoot; | |
| 100 } | |
| 101 | |
| 102 // Returns true if the trust settings array |trust_settings| for a certificate | |
| 103 // that |is_self_signed| should be treated as a trust anchor. | |
| 104 bool IsTrustSettingsTrustedForPolicy(CFArrayRef trust_settings, | |
| 105 bool is_self_signed, | |
| 106 const CFStringRef policy_oid) { | |
| 107 // An empty trust settings array (that is, the trust_settings parameter | |
| 108 // returns a valid but empty CFArray) means "always trust this certificate" | |
| 109 // with an overall trust setting for the certificate of | |
| 110 // kSecTrustSettingsResultTrustRoot. | |
| 111 if (CFArrayGetCount(trust_settings) == 0 && is_self_signed) | |
| 112 return true; | |
| 113 | |
| 114 for (CFIndex i = 0, settings_count = CFArrayGetCount(trust_settings); | |
| 115 i < settings_count; ++i) { | |
| 116 CFDictionaryRef trust_dict = reinterpret_cast<CFDictionaryRef>( | |
| 117 const_cast<void*>(CFArrayGetValueAtIndex(trust_settings, i))); | |
| 118 if (IsTrustDictionaryTrustedForPolicy(trust_dict, is_self_signed, | |
| 119 policy_oid)) | |
| 120 return true; | |
| 121 } | |
| 122 return false; | |
| 123 } | |
| 124 | |
| 125 // Returns true if the certificate |cert_handle| is trusted for the policy | |
| 126 // |policy_oid|. | |
| 127 bool IsSecCertificateTrustedForPolicy(SecCertificateRef cert_handle, | |
| 128 const CFStringRef policy_oid) { | |
| 129 const bool is_self_signed = X509Certificate::IsSelfSigned(cert_handle); | |
| 130 // Evaluate trust domains in user, admin, system order. Admin settings can | |
| 131 // override system ones, and user settings can override both admin and system. | |
| 132 for (const auto& trust_domain : | |
| 133 {kSecTrustSettingsDomainUser, kSecTrustSettingsDomainAdmin, | |
| 134 kSecTrustSettingsDomainSystem}) { | |
| 135 base::ScopedCFTypeRef<CFArrayRef> trust_settings; | |
| 136 OSStatus err = SecTrustSettingsCopyTrustSettings( | |
| 137 cert_handle, trust_domain, trust_settings.InitializeInto()); | |
| 138 if (err == errSecItemNotFound) { | |
| 139 // No trust settings for that domain.. try the next. | |
| 140 continue; | |
| 141 } | |
| 142 if (err) { | |
| 143 OSSTATUS_LOG(ERROR, err) << "SecTrustSettingsCopyTrustSettings error"; | |
| 144 continue; | |
| 145 } | |
| 146 if (IsTrustSettingsTrustedForPolicy(trust_settings, is_self_signed, | |
| 147 policy_oid)) | |
|
Ryan Sleevi
2017/02/16 22:27:22
To the general point of handling distrust records
mattm
2017/02/17 00:04:18
Would work for this part, but see the question abo
mattm
2017/02/17 02:03:36
I added the tri-state.
| |
| 148 return true; | |
| 149 } | |
| 150 | |
| 151 // No trust settings, or none of the settings were for the correct policy, or | |
| 152 // had the correct trust result. | |
| 153 return false; | |
| 154 } | |
| 155 | |
| 156 // Filters an array of SecCertificateRef by trust for |policy_oid|, returning | |
| 157 // the results as TrustAnchors in |out_anchors|. | |
| 158 void FilterTrustedCertificates(CFArrayRef matching_items, | |
| 159 const CFStringRef policy_oid, | |
| 160 TrustAnchors* out_anchors) { | |
| 161 for (CFIndex i = 0, item_count = CFArrayGetCount(matching_items); | |
| 162 i < item_count; ++i) { | |
| 163 SecCertificateRef match_cert_handle = reinterpret_cast<SecCertificateRef>( | |
| 164 const_cast<void*>(CFArrayGetValueAtIndex(matching_items, i))); | |
| 165 | |
| 166 if (!IsSecCertificateTrustedForPolicy(match_cert_handle, policy_oid)) | |
| 167 continue; | |
| 168 | |
| 169 base::ScopedCFTypeRef<CFDataRef> der_data( | |
| 170 SecCertificateCopyData(match_cert_handle)); | |
| 171 if (!der_data) { | |
| 172 LOG(ERROR) << "SecCertificateCopyData error"; | |
| 173 continue; | |
| 174 } | |
| 175 | |
| 176 CertErrors errors; | |
| 177 ParseCertificateOptions options; | |
| 178 options.allow_invalid_serial_numbers = true; | |
| 179 scoped_refptr<ParsedCertificate> anchor_cert = ParsedCertificate::Create( | |
| 180 CFDataGetBytePtr(der_data.get()), CFDataGetLength(der_data.get()), | |
| 181 options, &errors); | |
| 182 if (!anchor_cert) { | |
| 183 // TODO(crbug.com/634443): return errors better. | |
| 184 LOG(ERROR) << "Error parsing issuer certificate:\n" | |
| 185 << errors.ToDebugString(); | |
| 186 continue; | |
| 187 } | |
| 188 | |
| 189 out_anchors->push_back(TrustAnchor::CreateFromCertificateNoConstraints( | |
| 190 std::move(anchor_cert))); | |
| 191 } | |
| 192 } | |
| 193 | |
| 194 } // namespace | |
| 195 | |
| 196 TrustStoreMac::TrustStoreMac(const CFTypeRef policy_oid) | |
| 197 : policy_oid_(base::mac::CFCastStrict<CFStringRef>(policy_oid)) { | |
| 198 DCHECK(policy_oid_); | |
| 199 } | |
| 200 | |
| 201 TrustStoreMac::~TrustStoreMac() = default; | |
| 202 | |
| 203 void TrustStoreMac::FindTrustAnchorsForCert( | |
| 204 const scoped_refptr<ParsedCertificate>& cert, | |
| 205 TrustAnchors* out_anchors) const { | |
| 206 base::ScopedCFTypeRef<CFDataRef> name_data = GetMacNormalizedIssuer(cert); | |
| 207 | |
| 208 FindTrustAnchorsByMacNormalizedSubject(name_data, out_anchors); | |
| 209 } | |
| 210 | |
| 211 // static | |
| 212 base::ScopedCFTypeRef<CFArrayRef> | |
| 213 TrustStoreMac::FindMatchingCertificatesForMacNormalizedSubject( | |
| 214 CFDataRef name_data) { | |
| 215 base::ScopedCFTypeRef<CFArrayRef> matching_items; | |
| 216 base::ScopedCFTypeRef<CFMutableDictionaryRef> query( | |
| 217 CFDictionaryCreateMutable(nullptr, 0, &kCFTypeDictionaryKeyCallBacks, | |
| 218 &kCFTypeDictionaryValueCallBacks)); | |
| 219 | |
| 220 CFDictionarySetValue(query, kSecClass, kSecClassCertificate); | |
| 221 CFDictionarySetValue(query, kSecReturnRef, kCFBooleanTrue); | |
| 222 CFDictionarySetValue(query, kSecMatchLimit, kSecMatchLimitAll); | |
| 223 CFDictionarySetValue(query, kSecAttrSubject, name_data); | |
| 224 | |
| 225 base::ScopedCFTypeRef<CFArrayRef> scoped_alternate_keychain_search_list; | |
| 226 if (TestKeychainSearchList::HasInstance()) { | |
| 227 OSStatus status = TestKeychainSearchList::GetInstance()->CopySearchList( | |
| 228 scoped_alternate_keychain_search_list.InitializeInto()); | |
| 229 if (status) { | |
| 230 OSSTATUS_LOG(ERROR, status) | |
| 231 << "TestKeychainSearchList::CopySearchList error"; | |
| 232 return matching_items; | |
| 233 } | |
| 234 } | |
| 235 | |
| 236 // If a TestKeychainSearchList is present, it will have already set | |
| 237 // |scoped_alternate_keychain_search_list|, which will be used as the | |
| 238 // basis for reordering the keychain. Otherwise, get the current keychain | |
| 239 // search list and use that. | |
| 240 if (!scoped_alternate_keychain_search_list) { | |
| 241 OSStatus status = SecKeychainCopySearchList( | |
| 242 scoped_alternate_keychain_search_list.InitializeInto()); | |
| 243 if (status) { | |
| 244 OSSTATUS_LOG(ERROR, status) << "SecKeychainCopySearchList error"; | |
| 245 return matching_items; | |
| 246 } | |
| 247 } | |
| 248 CFMutableArrayRef mutable_keychain_search_list = CFArrayCreateMutableCopy( | |
|
Ryan Sleevi
2017/02/16 22:27:22
newline between 247/248
mattm
2017/02/17 00:04:18
Done.
| |
| 249 kCFAllocatorDefault, | |
| 250 CFArrayGetCount(scoped_alternate_keychain_search_list.get()) + 1, | |
| 251 scoped_alternate_keychain_search_list.get()); | |
| 252 if (!mutable_keychain_search_list) { | |
| 253 LOG(ERROR) << "CFArrayCreateMutableCopy"; | |
| 254 return matching_items; | |
| 255 } | |
| 256 scoped_alternate_keychain_search_list.reset(mutable_keychain_search_list); | |
| 257 base::ScopedCFTypeRef<SecKeychainRef> roots_keychain; | |
| 258 // The System Roots keychain is not normally searched by SecItemCopyMatching. | |
| 259 // Get a reference to it and include in the keychain search list. | |
| 260 OSStatus status = SecKeychainOpen( | |
| 261 "/System/Library/Keychains/SystemRootCertificates.keychain", | |
| 262 roots_keychain.InitializeInto()); | |
| 263 if (status) { | |
| 264 OSSTATUS_LOG(ERROR, status) << "SecKeychainOpen error"; | |
| 265 return matching_items; | |
| 266 } | |
| 267 CFArrayAppendValue(mutable_keychain_search_list, roots_keychain); | |
| 268 CFDictionarySetValue(query, kSecMatchSearchList, | |
| 269 scoped_alternate_keychain_search_list.get()); | |
| 270 | |
| 271 OSStatus err; | |
| 272 { | |
| 273 base::AutoLock lock(crypto::GetMacSecurityServicesLock()); | |
|
Ryan Sleevi
2017/02/16 22:27:22
Line 260 and 241 would also need to be guarded w/
mattm
2017/02/17 00:04:18
Done.
| |
| 274 err = SecItemCopyMatching( | |
| 275 query, reinterpret_cast<CFTypeRef*>(matching_items.InitializeInto())); | |
| 276 } | |
| 277 if (err == errSecItemNotFound) { | |
| 278 // No matches found. | |
| 279 return matching_items; | |
| 280 } | |
| 281 if (err) { | |
| 282 OSSTATUS_LOG(ERROR, err) << "SecItemCopyMatching error"; | |
| 283 return matching_items; | |
| 284 } | |
| 285 return matching_items; | |
| 286 } | |
| 287 | |
| 288 // static | |
| 289 base::ScopedCFTypeRef<CFDataRef> TrustStoreMac::GetMacNormalizedIssuer( | |
| 290 const scoped_refptr<ParsedCertificate>& cert) { | |
| 291 base::ScopedCFTypeRef<CFDataRef> name_data; | |
| 292 // There does not appear to be any public API to get the normalized version | |
| 293 // of a Name without creating a SecCertificate. | |
| 294 base::ScopedCFTypeRef<SecCertificateRef> cert_handle( | |
| 295 X509Certificate::CreateOSCertHandleFromBytes( | |
| 296 cert->der_cert().AsStringPiece().data(), cert->der_cert().Length())); | |
| 297 if (!cert_handle) { | |
| 298 LOG(ERROR) << "CreateOSCertHandleFromBytes"; | |
| 299 return name_data; | |
| 300 } | |
| 301 name_data.reset( | |
| 302 SecCertificateCopyNormalizedIssuerContent(cert_handle, nullptr)); | |
|
Ryan Sleevi
2017/02/16 22:27:23
This would also need the CSSM lock
mattm
2017/02/17 00:04:18
Hm, that seems a little surprising unless you're s
Ryan Sleevi
2017/02/17 00:54:13
Yup, at least until 10.12 it seems.
At the lowest
mattm
2017/02/17 02:03:36
Acknowledged.
| |
| 303 if (!name_data) | |
| 304 LOG(ERROR) << "SecCertificateCopyNormalizedIssuerContent"; | |
| 305 return name_data; | |
| 306 } | |
| 307 | |
| 308 void TrustStoreMac::FindTrustAnchorsByMacNormalizedSubject( | |
| 309 CFDataRef name_data, | |
| 310 TrustAnchors* out_anchors) const { | |
| 311 base::ScopedCFTypeRef<CFArrayRef> scoped_matching_items = | |
| 312 FindMatchingCertificatesForMacNormalizedSubject(name_data); | |
| 313 if (!scoped_matching_items) | |
| 314 return; | |
| 315 | |
| 316 FilterTrustedCertificates(scoped_matching_items.get(), policy_oid_, | |
| 317 out_anchors); | |
| 318 } | |
| 319 | |
| 320 } // namespace net | |
| OLD | NEW |