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 <CoreFoundation/CoreFoundation.h> | |
| 8 #include <Security/Security.h> | |
| 9 | |
| 10 #include "base/logging.h" | |
| 11 #include "base/mac/foundation_util.h" | |
| 12 #include "base/mac/mac_logging.h" | |
| 13 #include "base/memory/ptr_util.h" | |
| 14 #include "base/synchronization/lock.h" | |
| 15 #include "crypto/cssm_init.h" // for LogCSSMError. | |
| 16 #include "crypto/mac_security_services_lock.h" | |
| 17 #include "net/cert/internal/cert_errors.h" | |
| 18 #include "net/cert/internal/parse_name.h" | |
| 19 #include "net/cert/internal/parsed_certificate.h" | |
| 20 #include "net/cert/test_keychain_search_list_mac.h" | |
| 21 #include "net/cert/x509_certificate.h" | |
| 22 | |
| 23 namespace net { | |
| 24 | |
| 25 // XXX for SecCertificateGetData and CSSM_DATA. Could use SecCertificateCopyData | |
| 26 // instead. | |
| 27 // CSSM functions are deprecated as of OSX 10.7, but have no replacement. | |
| 28 // https://bugs.chromium.org/p/chromium/issues/detail?id=590914#c1 | |
| 29 #pragma clang diagnostic push | |
| 30 #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
| |
| 31 | |
| 32 namespace { | |
| 33 | |
| 34 // XXX | |
| 35 // The overall trust settings for a certificate are the sum of all the usage | |
| 36 // constraints dictionaries that match the use for which that certificate is | |
| 37 // being evaluated. Trust settings for a given use apply if any of the | |
| 38 // dictionaries in the certificate’s trust settings array satisfies the | |
| 39 // specified use. Thus, when a certificate has multiple usage constraints | |
| 40 // dictionaries in its trust settings array, the overall trust settings for the | |
| 41 // certificate are: | |
| 42 // | |
| 43 // ((usage constraint dictionary 0 component 0) AND (usage constraint dictionary | |
| 44 // 0 component 1) AND (...)) OR ((usage constraint dictionary 1 component 0) AND | |
| 45 // (usage constraint dictionary 1 component 1) AND (...)) OR (...) ... | |
| 46 // | |
| 47 // If the value of the kSecTrustSettingsResult component is not | |
| 48 // kSecTrustSettingsResultUnspecified for a usage constraints dictionary that | |
| 49 // has no constraints, the default value kSecTrustSettingsResultTrustRoot is | |
| 50 // assumed. To specify a value for the kSecTrustSettingsAllowedError component | |
| 51 // without explicitly trusting or distrusting the associated certificate, | |
| 52 // specify a value of kSecTrustSettingsResultUnspecified for the | |
| 53 // kSecTrustSettingsResult component. | |
| 54 // | |
| 55 // An empty trust settings array (that is, the trustSettings parameter returns a | |
| 56 // valid but empty CFArray) means "always trust this certificate” with an | |
| 57 // overall trust setting for the certificate of | |
| 58 // kSecTrustSettingsResultTrustRoot. Note that an empty trust settings array is | |
| 59 // not the same as no trust settings (the trustSettings parameter returns NULL), | |
| 60 // which means "this certificate must be verified to a known trusted | |
| 61 // certificate”. | |
| 62 // | |
| 63 // Note the distinction between the results kSecTrustSettingsResultTrustRoot and | |
| 64 // kSecTrustSettingsResultTrustAsRoot: The former can only be applied to root | |
| 65 // (self-signed) certificates; the latter can only be applied to non-root | |
| 66 // certificates. Therefore, an empty trust settings array for a non-root | |
| 67 // certificate is invalid, because the default value of | |
| 68 // kSecTrustSettingsResultTrustRoot is not valid for a non-root certificate. | |
|
Ryan Sleevi
2017/02/06 23:59:04
A++ documentation - would read again.
| |
| 69 | |
| 70 bool IsTrustDictionaryTrustedForPolicy(CFDictionaryRef trust_dict, | |
| 71 bool is_self_signed, | |
| 72 const CFStringRef target_policy_oid) { | |
| 73 DVLOG(2) << "IsTrustDictionaryTrustedForPolicy?"; | |
| 74 // An empty trust dict should be interpreted as | |
| 75 // kSecTrustSettingsResultTrustRoot. This is handled by falling through all | |
| 76 // the conditions below with the default value of |trust_settings_result|. | |
| 77 | |
| 78 // Ignore application-specific trust settings. | |
| 79 // TODO: should handle these? (checking if there are application settings | |
| 80 // 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.
| |
| 81 if (CFDictionaryContainsKey(trust_dict, kSecTrustSettingsApplication)) { | |
| 82 DVLOG(2) << "ignored trust dictionary with kSecTrustSettingsApplication"; | |
| 83 return false; | |
| 84 } | |
| 85 // Ignore trust settings that contain policy-specific constraints. (E.g., | |
| 86 // for SSL, settings that apply to a single hostname.) | |
| 87 if (CFDictionaryContainsKey(trust_dict, kSecTrustSettingsPolicyString)) { | |
| 88 DVLOG(2) << "ignored trust dictionary with kSecTrustSettingsPolicyString"; | |
| 89 return false; | |
| 90 } | |
| 91 | |
| 92 // XXX do we care about keyusage? | |
| 93 // CFNumberRef key_usage = | |
| 94 // base::mac::GetValueFromDictionary<CFNumberRef>(trust_dict, | |
| 95 // 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.
| |
| 96 | |
| 97 CFTypeRef policy_typeref = | |
| 98 CFDictionaryGetValue(trust_dict, kSecTrustSettingsPolicy); | |
| 99 SecPolicyRef policy_ref = nullptr; | |
| 100 if (policy_typeref) { | |
| 101 if (CFGetTypeID(policy_typeref) != SecPolicyGetTypeID()) { | |
| 102 DVLOG(2) << "kSecTrustSettingsPolicy not a SecPolicyRef"; | |
| 103 return false; | |
| 104 } | |
| 105 policy_ref = | |
| 106 reinterpret_cast<SecPolicyRef>(const_cast<void*>(policy_typeref)); | |
| 107 } | |
| 108 // XXX do some manual testing that this policy stuff actually works properly. | |
| 109 // XXX is there any hierarchy to the policies? (eg, does | |
| 110 // 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
| |
| 111 if (policy_ref) { | |
| 112 CFDictionaryRef policy_dict = SecPolicyCopyProperties(policy_ref); | |
| 113 CFTypeRef value = CFDictionaryGetValue(policy_dict, kSecPolicyOid); | |
| 114 // Having a kSecTrustSettingsPolicy without a kSecPolicyOid element is an | |
| 115 // error. | |
| 116 if (!value) { | |
| 117 DVLOG(2) << "kSecTrustSettingsPolicy with no kSecPolicyOid!"; | |
| 118 return false; | |
| 119 } | |
| 120 CFStringRef policy_oid = base::mac::CFCast<CFStringRef>(value); | |
| 121 if (!policy_oid) { | |
| 122 DVLOG(2) << "kSecPolicyOid value not a CFStringRef!"; | |
| 123 return false; | |
| 124 } | |
| 125 base::ScopedCFTypeRef<CFDictionaryRef> scoped_policy_dict(policy_dict); | |
| 126 if (!CFEqual(policy_oid, target_policy_oid)) { | |
| 127 DVLOG(2) << "kSecPolicyOid does not match target policy oid"; | |
| 128 return false; | |
| 129 } | |
| 130 // XXX care about kSecPolicyName, kSecPolicyClient, kSecPolicyKU_<foo> ? | |
| 131 } | |
| 132 | |
| 133 // If kSecTrustSettingsResult is not present in the trust dict, | |
| 134 // kSecTrustSettingsResultTrustRoot is assumed. | |
| 135 int trust_settings_result = kSecTrustSettingsResultTrustRoot; | |
| 136 CFNumberRef trust_settings_result_ref = | |
| 137 base::mac::GetValueFromDictionary<CFNumberRef>(trust_dict, | |
| 138 kSecTrustSettingsResult); | |
| 139 | |
| 140 if (trust_settings_result_ref) { | |
| 141 if (!CFNumberGetValue(trust_settings_result_ref, kCFNumberIntType, | |
| 142 &trust_settings_result)) { | |
| 143 DVLOG(2) << "CFNumberGetValue fail"; | |
| 144 return false; | |
| 145 } | |
| 146 } | |
| 147 | |
| 148 // TODO: handle distrust (kSecTrustSettingsResultDeny) | |
| 149 | |
| 150 // kSecTrustSettingsResultTrustRoot can only be applied to root(self-signed) | |
| 151 // certs. | |
| 152 if (is_self_signed) | |
| 153 return trust_settings_result == kSecTrustSettingsResultTrustRoot; | |
| 154 | |
| 155 // kSecTrustSettingsResultTrustAsRoot can only be applied to non-root certs. | |
| 156 return trust_settings_result == kSecTrustSettingsResultTrustAsRoot; | |
| 157 } | |
| 158 | |
| 159 bool IsTrustSettingsTrustedForPolicy(CFArrayRef trust_settings, | |
| 160 bool is_self_signed, | |
| 161 const CFStringRef policy_oid) { | |
| 162 DVLOG(2) << "IsTrustSettingsTrustedForPolicy?"; | |
| 163 // An empty trust settings array (that is, the trustSettings parameter returns | |
| 164 // a valid but empty CFArray) means "always trust this certificate” with an | |
| 165 // overall trust setting for the certificate of | |
| 166 // kSecTrustSettingsResultTrustRoot. | |
| 167 if (CFArrayGetCount(trust_settings) == 0 && is_self_signed) { | |
| 168 DVLOG(2) << "Empty trust settings array and self-signed cert = trusted"; | |
| 169 return true; | |
| 170 } | |
| 171 | |
| 172 for (CFIndex i = 0, settings_count = CFArrayGetCount(trust_settings); | |
| 173 i < settings_count; ++i) { | |
| 174 DVLOG(2) << "IsTrustSettingsTrustedForPolicy i=" << i << " of " | |
| 175 << settings_count; | |
| 176 CFDictionaryRef trust_dict = reinterpret_cast<CFDictionaryRef>( | |
| 177 const_cast<void*>(CFArrayGetValueAtIndex(trust_settings, i))); | |
| 178 if (IsTrustDictionaryTrustedForPolicy(trust_dict, is_self_signed, | |
| 179 policy_oid)) | |
| 180 return true; | |
| 181 } | |
| 182 return false; | |
| 183 } | |
| 184 | |
| 185 bool IsSecCertificateTrustedForPolicy(SecCertificateRef cert_handle, | |
| 186 bool is_self_signed, | |
| 187 const CFStringRef policy_oid) { | |
| 188 for (const auto& trust_domain : | |
| 189 {kSecTrustSettingsDomainUser, kSecTrustSettingsDomainAdmin, | |
| 190 kSecTrustSettingsDomainSystem}) { | |
| 191 DVLOG(2) << "IsSecCertificateTrustedForPolicy domain = " << trust_domain; | |
| 192 CFArrayRef trust_settings; | |
| 193 OSStatus err = SecTrustSettingsCopyTrustSettings(cert_handle, trust_domain, | |
| 194 &trust_settings); | |
| 195 if (err == errSecItemNotFound) { | |
| 196 DVLOG(2) << "no trust settings for that domain"; | |
| 197 // No trust settings for that domain.. try the next. | |
| 198 continue; | |
| 199 } | |
| 200 if (err) { | |
| 201 OSSTATUS_LOG(ERROR, err) << "SecTrustSettingsCopyTrustSettings error"; | |
| 202 continue; | |
| 203 } | |
| 204 base::ScopedCFTypeRef<CFArrayRef> scoped_trust_settings(trust_settings); | |
| 205 if (IsTrustSettingsTrustedForPolicy(trust_settings, is_self_signed, | |
| 206 policy_oid)) | |
| 207 return true; | |
| 208 } | |
| 209 | |
| 210 // No trust settings, or none of the settings were for the correct policy, or | |
| 211 // had the correct trust result. | |
| 212 return false; | |
| 213 } | |
| 214 | |
| 215 } // namespace | |
| 216 | |
| 217 TrustStoreMac::TrustStoreMac(const void* policy_oid) | |
| 218 : policy_oid_(base::mac::CFCast<CFStringRef>(policy_oid)) { | |
| 219 DCHECK(policy_oid_); | |
| 220 } | |
| 221 | |
| 222 TrustStoreMac::~TrustStoreMac() = default; | |
| 223 | |
| 224 // static | |
| 225 base::ScopedCFTypeRef<CFArrayRef> | |
| 226 TrustStoreMac::FindMatchingCertificatesForMacNormalizedSubject( | |
| 227 CFDataRef name_data) { | |
| 228 base::ScopedCFTypeRef<CFArrayRef> matching_items; | |
| 229 base::ScopedCFTypeRef<CFMutableDictionaryRef> query( | |
| 230 CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks, | |
| 231 &kCFTypeDictionaryValueCallBacks)); | |
| 232 | |
| 233 CFDictionarySetValue(query, kSecClass, kSecClassCertificate); | |
| 234 CFDictionarySetValue(query, kSecReturnRef, kCFBooleanTrue); | |
| 235 CFDictionarySetValue(query, kSecMatchLimit, kSecMatchLimitAll); | |
| 236 CFDictionarySetValue(query, kSecAttrSubject, name_data); | |
| 237 | |
| 238 base::ScopedCFTypeRef<CFArrayRef> scoped_alternate_keychain_search_list; | |
| 239 if (TestKeychainSearchList::HasInstance()) { | |
| 240 CFArrayRef alternate_keychain_search_list; | |
| 241 OSStatus status = TestKeychainSearchList::GetInstance()->CopySearchList( | |
| 242 &alternate_keychain_search_list); | |
| 243 if (status) { | |
| 244 OSSTATUS_LOG(ERROR, status) | |
| 245 << "TestKeychainSearchList::CopySearchList error"; | |
| 246 return matching_items; | |
| 247 } | |
| 248 scoped_alternate_keychain_search_list.reset(alternate_keychain_search_list); | |
| 249 } | |
| 250 // XXX this is all a bit similar (but not quite the same) as | |
| 251 // cert_verify_proc_mac keychain stuff.... is there a way to reduce | |
| 252 // duplication? | |
| 253 // | |
| 254 // If a TestKeychainSearchList is present, it will have already set | |
| 255 // |scoped_alternate_keychain_search_list|, which will be used as the | |
| 256 // basis for reordering the keychain. Otherwise, get the current keychain | |
| 257 // search list and use that. | |
| 258 if (!scoped_alternate_keychain_search_list) { | |
| 259 CFArrayRef keychain_search_list; | |
| 260 OSStatus status = SecKeychainCopySearchList(&keychain_search_list); | |
| 261 if (status) { | |
| 262 OSSTATUS_LOG(ERROR, status) << "SecKeychainCopySearchList error"; | |
| 263 return matching_items; | |
| 264 } | |
| 265 scoped_alternate_keychain_search_list.reset(keychain_search_list); | |
| 266 } | |
| 267 CFMutableArrayRef mutable_keychain_search_list = CFArrayCreateMutableCopy( | |
| 268 kCFAllocatorDefault, | |
| 269 CFArrayGetCount(scoped_alternate_keychain_search_list.get()) + 1, | |
| 270 scoped_alternate_keychain_search_list.get()); | |
| 271 if (!mutable_keychain_search_list) { | |
| 272 LOG(ERROR) << "CFArrayCreateMutableCopy"; | |
| 273 return matching_items; | |
| 274 } | |
| 275 scoped_alternate_keychain_search_list.reset(mutable_keychain_search_list); | |
| 276 SecKeychainRef roots_keychain; | |
| 277 // The System Roots keychain is not normally searched by SecItemCopyMatching. | |
| 278 // Get a reference to it and include in the keychain search list. | |
| 279 OSStatus status = SecKeychainOpen( | |
| 280 "/System/Library/Keychains/SystemRootCertificates.keychain", | |
| 281 &roots_keychain); | |
| 282 if (status) { | |
| 283 OSSTATUS_LOG(ERROR, status) << "SecKeychainOpen error"; | |
| 284 return matching_items; | |
| 285 } | |
| 286 base::ScopedCFTypeRef<SecKeychainRef> scoped_roots_keychain(roots_keychain); | |
| 287 CFArrayAppendValue(mutable_keychain_search_list, roots_keychain); | |
| 288 CFDictionarySetValue(query, kSecMatchSearchList, | |
| 289 scoped_alternate_keychain_search_list.get()); | |
| 290 | |
| 291 OSStatus err; | |
| 292 { | |
| 293 base::AutoLock lock(crypto::GetMacSecurityServicesLock()); | |
| 294 err = | |
| 295 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
| |
| 296 } | |
| 297 if (err == errSecItemNotFound) { | |
| 298 DVLOG(2) << "SecItemCopyMatching: no matches found"; | |
| 299 return matching_items; | |
| 300 } | |
| 301 if (err) { | |
| 302 crypto::LogCSSMError("SecItemCopyMatching", err); | |
| 303 OSSTATUS_LOG(ERROR, err) << "SecItemCopyMatching error"; | |
| 304 return matching_items; | |
| 305 } | |
| 306 return matching_items; | |
| 307 } | |
| 308 | |
| 309 // static | |
| 310 void TrustStoreMac::FilterTrustedCertificates(CFArrayRef matching_items, | |
| 311 const CFStringRef policy_oid, | |
| 312 TrustAnchors* out_anchors) { | |
| 313 for (CFIndex i = 0, item_count = CFArrayGetCount(matching_items); | |
| 314 i < item_count; ++i) { | |
| 315 DVLOG(2) << "i=" << i << " of " << item_count; | |
| 316 SecCertificateRef match_cert_handle = reinterpret_cast<SecCertificateRef>( | |
| 317 const_cast<void*>(CFArrayGetValueAtIndex(matching_items, i))); | |
| 318 | |
| 319 if (!IsSecCertificateTrustedForPolicy( | |
| 320 match_cert_handle, X509Certificate::IsSelfSigned(match_cert_handle), | |
| 321 policy_oid)) { | |
| 322 DVLOG(2) << "not trusted for policy"; | |
| 323 continue; | |
| 324 } else { | |
| 325 DVLOG(2) << "trusted!"; | |
| 326 } | |
| 327 | |
| 328 CSSM_DATA der_data; | |
| 329 OSStatus err = SecCertificateGetData(match_cert_handle, &der_data); | |
| 330 if (err != noErr) { | |
| 331 OSSTATUS_LOG(ERROR, err) << "SecCertificateGetData error"; | |
| 332 continue; | |
| 333 } | |
| 334 | |
| 335 CertErrors errors; | |
| 336 ParseCertificateOptions options; | |
| 337 options.allow_invalid_serial_numbers = true; | |
| 338 scoped_refptr<ParsedCertificate> anchor_cert = ParsedCertificate::Create( | |
| 339 der_data.Data, der_data.Length, options, &errors); | |
| 340 // TODO(mattm): if parsing fails, it would be legit to return a TrustAnchor | |
| 341 // that doesn't have an associated cert, getting the SPKI and Subject | |
| 342 // 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
| |
| 343 if (!anchor_cert) { | |
| 344 // TODO(crbug.com/634443): return errors better. | |
| 345 LOG(ERROR) << "Error parsing issuer certificate:\n" | |
| 346 << errors.ToDebugString(); | |
| 347 continue; | |
| 348 } | |
| 349 | |
| 350 DVLOG(2) << "added cert to out_anchors"; | |
| 351 out_anchors->push_back(TrustAnchor::CreateFromCertificateNoConstraints( | |
| 352 std::move(anchor_cert))); | |
| 353 } | |
| 354 } | |
| 355 | |
| 356 // static | |
| 357 base::ScopedCFTypeRef<CFDataRef> TrustStoreMac::GetMacNormalizedIssuer( | |
| 358 const scoped_refptr<ParsedCertificate>& cert) { | |
| 359 base::ScopedCFTypeRef<CFDataRef> name_data; | |
| 360 // There does not appear to be any public API to get the normalized version | |
| 361 // 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.
| |
| 362 X509Certificate::OSCertHandle cert_handle = | |
| 363 X509Certificate::CreateOSCertHandleFromBytes( | |
| 364 cert->der_cert().AsStringPiece().data(), cert->der_cert().Length()); | |
| 365 if (!cert_handle) { | |
| 366 LOG(ERROR) << "CreateOSCertHandleFromBytes"; | |
| 367 return name_data; | |
| 368 } | |
| 369 base::ScopedCFTypeRef<SecCertificateRef> scoped_cert_handle(cert_handle); | |
| 370 name_data.reset( | |
| 371 SecCertificateCopyNormalizedIssuerContent(cert_handle, nullptr)); | |
| 372 if (!name_data) | |
| 373 LOG(ERROR) << "SecCertificateCopyNormalizedIssuerContent"; | |
| 374 return name_data; | |
| 375 } | |
| 376 | |
| 377 void TrustStoreMac::FindTrustAnchorsByMacNormalizedSubject( | |
| 378 CFDataRef name_data, | |
| 379 TrustAnchors* out_anchors) const { | |
| 380 base::ScopedCFTypeRef<CFArrayRef> scoped_matching_items = | |
| 381 FindMatchingCertificatesForMacNormalizedSubject(name_data); | |
| 382 if (!scoped_matching_items) | |
| 383 return; | |
| 384 | |
| 385 FilterTrustedCertificates(scoped_matching_items.get(), policy_oid_, | |
| 386 out_anchors); | |
| 387 } | |
| 388 | |
| 389 void TrustStoreMac::FindTrustAnchorsForCert( | |
| 390 const scoped_refptr<ParsedCertificate>& cert, | |
| 391 TrustAnchors* out_anchors) const { | |
| 392 DVLOG(2) << "FindTrustAnchorsForCert"; | |
| 393 | |
| 394 base::ScopedCFTypeRef<CFDataRef> name_data = GetMacNormalizedIssuer(cert); | |
| 395 | |
| 396 FindTrustAnchorsByMacNormalizedSubject(name_data, out_anchors); | |
| 397 } | |
| 398 | |
| 399 } // namespace net | |
| OLD | NEW |