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/cssm_init.h" // for LogCSSMError. | |
| 15 #include "crypto/mac_security_services_lock.h" | |
| 16 #include "net/cert/internal/cert_errors.h" | |
| 17 #include "net/cert/internal/parse_name.h" | |
| 18 #include "net/cert/internal/parsed_certificate.h" | |
| 19 #include "net/cert/test_keychain_search_list_mac.h" | |
| 20 #include "net/cert/x509_certificate.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-sectrustsettingscopytr ustsetting?language=objc | |
| 28 | |
| 29 // Returns true if a usage constraints dictionary |trust_dict| for a certificate | |
| 30 // that |is_self_signed| should be treated as a trust root. | |
| 31 bool IsTrustDictionaryTrustedForPolicy(CFDictionaryRef trust_dict, | |
| 32 bool is_self_signed, | |
| 33 const CFStringRef target_policy_oid) { | |
| 34 DVLOG(2) << "IsTrustDictionaryTrustedForPolicy?"; | |
|
Ryan Sleevi
2017/02/14 19:26:16
I'm not sure the purpose of this trace; is it pure
mattm
2017/02/16 22:06:08
Yeah the DVLOGs were just for debugging/developmen
| |
| 35 // An empty trust dict should be interpreted as | |
| 36 // kSecTrustSettingsResultTrustRoot. This is handled by falling through all | |
| 37 // the conditions below with the default value of |trust_settings_result|. | |
| 38 | |
| 39 // Ignore application-specific trust settings. | |
|
Ryan Sleevi
2017/02/14 19:26:17
Comment suggestion: More detail
// Trust settings
mattm
2017/02/16 22:06:07
Done.
| |
| 40 if (CFDictionaryContainsKey(trust_dict, kSecTrustSettingsApplication)) { | |
| 41 DVLOG(2) << "ignored trust dictionary with kSecTrustSettingsApplication"; | |
|
Ryan Sleevi
2017/02/14 19:26:16
Necessary?
mattm
2017/02/16 22:06:08
Done.
| |
| 42 return false; | |
| 43 } | |
|
Ryan Sleevi
2017/02/14 19:26:16
newline between 43/44
mattm
2017/02/16 22:06:09
Done.
| |
| 44 // Ignore trust settings that contain policy-specific constraints. (E.g., | |
| 45 // for SSL, settings that apply to a single hostname.) | |
|
Ryan Sleevi
2017/02/14 19:26:16
// Trust settings may be scoped using policy-speci
mattm
2017/02/16 22:06:08
Done.
| |
| 46 if (CFDictionaryContainsKey(trust_dict, kSecTrustSettingsPolicyString)) { | |
| 47 DVLOG(2) << "ignored trust dictionary with kSecTrustSettingsPolicyString"; | |
| 48 return false; | |
| 49 } | |
| 50 | |
| 51 // There doesn't seem to be a need to handle kSecTrustSettingsKeyUsage. | |
|
Ryan Sleevi
2017/02/14 19:26:16
The way this comment is worded seems more conversa
mattm
2017/02/16 22:06:07
Done.
| |
| 52 | |
| 53 CFTypeRef policy_typeref = | |
|
Ryan Sleevi
2017/02/14 19:26:16
It's probably worth documenting this block (namely
mattm
2017/02/16 22:06:08
Done.
| |
| 54 CFDictionaryGetValue(trust_dict, kSecTrustSettingsPolicy); | |
| 55 SecPolicyRef policy_ref = nullptr; | |
| 56 if (policy_typeref) { | |
| 57 if (CFGetTypeID(policy_typeref) != SecPolicyGetTypeID()) { | |
| 58 DVLOG(2) << "kSecTrustSettingsPolicy not a SecPolicyRef"; | |
| 59 return false; | |
| 60 } | |
| 61 policy_ref = | |
| 62 reinterpret_cast<SecPolicyRef>(const_cast<void*>(policy_typeref)); | |
| 63 } | |
|
Ryan Sleevi
2017/02/14 19:26:16
If you modify //base/mac/foundation_util.h to CF_C
mattm
2017/02/16 22:06:09
Done.</noideawhatimdoing>
| |
| 64 if (policy_ref) { | |
| 65 CFDictionaryRef policy_dict = SecPolicyCopyProperties(policy_ref); | |
| 66 CFTypeRef value = CFDictionaryGetValue(policy_dict, kSecPolicyOid); | |
| 67 // Having a kSecTrustSettingsPolicy without a kSecPolicyOid element is an | |
| 68 // error. | |
|
Ryan Sleevi
2017/02/14 19:26:16
Is it necessary to handle this condition? The OS A
mattm
2017/02/16 22:06:07
Done.
| |
| 69 if (!value) { | |
| 70 DVLOG(2) << "kSecTrustSettingsPolicy with no kSecPolicyOid!"; | |
| 71 return false; | |
| 72 } | |
| 73 CFStringRef policy_oid = base::mac::CFCast<CFStringRef>(value); | |
| 74 if (!policy_oid) { | |
| 75 DVLOG(2) << "kSecPolicyOid value not a CFStringRef!"; | |
| 76 return false; | |
| 77 } | |
| 78 base::ScopedCFTypeRef<CFDictionaryRef> scoped_policy_dict(policy_dict); | |
|
Ryan Sleevi
2017/02/14 19:26:16
Is this right? You copy the properties on 65, whic
mattm
2017/02/16 22:06:09
... I have no excuse. Fixed.
| |
| 79 if (!CFEqual(policy_oid, target_policy_oid)) { | |
| 80 DVLOG(2) << "kSecPolicyOid does not match target policy_oid"; | |
| 81 return false; | |
| 82 } | |
| 83 DVLOG(2) << "kSecPolicyOid matches target policy_oid"; | |
| 84 | |
| 85 // XXX care about kSecPolicyName, kSecPolicyClient, kSecPolicyKU_<foo> ? | |
|
Ryan Sleevi
2017/02/14 19:26:16
It's unclear whether you're asking if this needs t
mattm
2017/02/16 22:06:09
Done.
| |
| 86 } | |
| 87 | |
| 88 // If kSecTrustSettingsResult is not present in the trust dict, | |
| 89 // kSecTrustSettingsResultTrustRoot is assumed. | |
| 90 int trust_settings_result = kSecTrustSettingsResultTrustRoot; | |
| 91 CFNumberRef trust_settings_result_ref = | |
| 92 base::mac::GetValueFromDictionary<CFNumberRef>(trust_dict, | |
| 93 kSecTrustSettingsResult); | |
| 94 | |
| 95 if (trust_settings_result_ref) { | |
| 96 if (!CFNumberGetValue(trust_settings_result_ref, kCFNumberIntType, | |
| 97 &trust_settings_result)) { | |
| 98 DVLOG(2) << "CFNumberGetValue fail"; | |
| 99 return false; | |
| 100 } | |
| 101 DVLOG(2) << "kSecTrustSettingsResult = " << trust_settings_result; | |
| 102 } | |
| 103 | |
| 104 // TODO: handle distrust (kSecTrustSettingsResultDeny) | |
|
Ryan Sleevi
2017/02/14 19:26:17
Do you have thoughts on how to handle this with th
mattm
2017/02/16 22:06:09
I've thought about, but I still don't really have
| |
| 105 | |
| 106 // kSecTrustSettingsResultTrustRoot can only be applied to root(self-signed) | |
| 107 // certs. | |
| 108 if (is_self_signed) | |
| 109 return trust_settings_result == kSecTrustSettingsResultTrustRoot; | |
| 110 | |
| 111 // kSecTrustSettingsResultTrustAsRoot can only be applied to non-root certs. | |
| 112 return trust_settings_result == kSecTrustSettingsResultTrustAsRoot; | |
| 113 } | |
| 114 | |
| 115 // Returns true if the trust settings array |trust_settings| for a certificate | |
| 116 // that |is_self_signed| should be treated as a trust root. | |
|
Ryan Sleevi
2017/02/14 19:26:16
s/root/anchor/
mattm
2017/02/16 22:06:07
Done.
| |
| 117 bool IsTrustSettingsTrustedForPolicy(CFArrayRef trust_settings, | |
| 118 bool is_self_signed, | |
| 119 const CFStringRef policy_oid) { | |
| 120 DVLOG(2) << "IsTrustSettingsTrustedForPolicy?"; | |
| 121 // An empty trust settings array (that is, the trustSettings parameter returns | |
|
Ryan Sleevi
2017/02/14 19:26:16
s/trustSettings/trust_settings/
mattm
2017/02/16 22:06:08
Done.
| |
| 122 // a valid but empty CFArray) means "always trust this certificateā with an | |
|
Ryan Sleevi
2017/02/14 19:26:16
note the trailing quote is a smart quote, not "
mattm
2017/02/16 22:06:08
Done.
| |
| 123 // overall trust setting for the certificate of | |
| 124 // kSecTrustSettingsResultTrustRoot. | |
| 125 if (CFArrayGetCount(trust_settings) == 0 && is_self_signed) { | |
| 126 DVLOG(2) << "Empty trust settings array and self-signed cert = trusted"; | |
| 127 return true; | |
| 128 } | |
| 129 | |
| 130 for (CFIndex i = 0, settings_count = CFArrayGetCount(trust_settings); | |
| 131 i < settings_count; ++i) { | |
| 132 DVLOG(2) << "IsTrustSettingsTrustedForPolicy i=" << i << " of " | |
| 133 << settings_count; | |
| 134 CFDictionaryRef trust_dict = reinterpret_cast<CFDictionaryRef>( | |
| 135 const_cast<void*>(CFArrayGetValueAtIndex(trust_settings, i))); | |
| 136 if (IsTrustDictionaryTrustedForPolicy(trust_dict, is_self_signed, | |
| 137 policy_oid)) | |
| 138 return true; | |
| 139 } | |
| 140 return false; | |
| 141 } | |
| 142 | |
| 143 // Returns true if the certificate |cert_handle| is trusted for the policy | |
| 144 // |policy_oid|. | |
| 145 bool IsSecCertificateTrustedForPolicy(SecCertificateRef cert_handle, | |
| 146 const CFStringRef policy_oid) { | |
| 147 const bool is_self_signed = X509Certificate::IsSelfSigned(cert_handle); | |
| 148 // Evaluate trust domains in user, admin, system order. Admin settings can | |
| 149 // override system ones, and user settings can override both admin and system. | |
| 150 for (const auto& trust_domain : | |
| 151 {kSecTrustSettingsDomainUser, kSecTrustSettingsDomainAdmin, | |
| 152 kSecTrustSettingsDomainSystem}) { | |
| 153 DVLOG(2) << "IsSecCertificateTrustedForPolicy domain = " << trust_domain; | |
| 154 CFArrayRef trust_settings; | |
| 155 OSStatus err = SecTrustSettingsCopyTrustSettings(cert_handle, trust_domain, | |
| 156 &trust_settings); | |
|
Ryan Sleevi
2017/02/14 19:26:16
You can instead use
base::ScopedCFTypeRef<CFArray
mattm
2017/02/16 22:06:08
Done.
| |
| 157 if (err == errSecItemNotFound) { | |
| 158 DVLOG(2) << "no trust settings for that domain"; | |
| 159 // No trust settings for that domain.. try the next. | |
| 160 continue; | |
| 161 } | |
| 162 if (err) { | |
| 163 OSSTATUS_LOG(ERROR, err) << "SecTrustSettingsCopyTrustSettings error"; | |
| 164 continue; | |
| 165 } | |
| 166 base::ScopedCFTypeRef<CFArrayRef> scoped_trust_settings(trust_settings); | |
| 167 if (IsTrustSettingsTrustedForPolicy(trust_settings, is_self_signed, | |
| 168 policy_oid)) | |
| 169 return true; | |
| 170 } | |
| 171 | |
| 172 // No trust settings, or none of the settings were for the correct policy, or | |
| 173 // had the correct trust result. | |
| 174 return false; | |
| 175 } | |
| 176 | |
| 177 } // namespace | |
| 178 | |
| 179 TrustStoreMac::TrustStoreMac(const void* policy_oid) | |
| 180 : policy_oid_(base::mac::CFCast<CFStringRef>(policy_oid)) { | |
|
Ryan Sleevi
2017/02/14 19:26:16
CFStrictCast ?
mattm
2017/02/16 22:06:08
Well, it does DCHECK(policy_oid_), but I guess a s
| |
| 181 DCHECK(policy_oid_); | |
| 182 } | |
| 183 | |
| 184 TrustStoreMac::~TrustStoreMac() = default; | |
| 185 | |
| 186 void TrustStoreMac::FindTrustAnchorsForCert( | |
| 187 const scoped_refptr<ParsedCertificate>& cert, | |
| 188 TrustAnchors* out_anchors) const { | |
| 189 DVLOG(2) << "FindTrustAnchorsForCert"; | |
| 190 | |
| 191 base::ScopedCFTypeRef<CFDataRef> name_data = GetMacNormalizedIssuer(cert); | |
| 192 | |
| 193 FindTrustAnchorsByMacNormalizedSubject(name_data, out_anchors); | |
| 194 } | |
| 195 | |
| 196 // static | |
| 197 base::ScopedCFTypeRef<CFArrayRef> | |
| 198 TrustStoreMac::FindMatchingCertificatesForMacNormalizedSubject( | |
| 199 CFDataRef name_data) { | |
| 200 base::ScopedCFTypeRef<CFArrayRef> matching_items; | |
| 201 base::ScopedCFTypeRef<CFMutableDictionaryRef> query( | |
| 202 CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks, | |
|
Ryan Sleevi
2017/02/14 19:26:15
nullptr
mattm
2017/02/16 22:06:08
Done.
| |
| 203 &kCFTypeDictionaryValueCallBacks)); | |
| 204 | |
| 205 CFDictionarySetValue(query, kSecClass, kSecClassCertificate); | |
| 206 CFDictionarySetValue(query, kSecReturnRef, kCFBooleanTrue); | |
| 207 CFDictionarySetValue(query, kSecMatchLimit, kSecMatchLimitAll); | |
| 208 CFDictionarySetValue(query, kSecAttrSubject, name_data); | |
| 209 | |
| 210 base::ScopedCFTypeRef<CFArrayRef> scoped_alternate_keychain_search_list; | |
| 211 if (TestKeychainSearchList::HasInstance()) { | |
| 212 CFArrayRef alternate_keychain_search_list; | |
| 213 OSStatus status = TestKeychainSearchList::GetInstance()->CopySearchList( | |
| 214 &alternate_keychain_search_list); | |
|
Ryan Sleevi
2017/02/14 19:26:15
Note .InitializeInto as well
(although perhaps ou
mattm
2017/02/16 22:06:08
Done.
| |
| 215 if (status) { | |
| 216 OSSTATUS_LOG(ERROR, status) | |
| 217 << "TestKeychainSearchList::CopySearchList error"; | |
| 218 return matching_items; | |
| 219 } | |
| 220 scoped_alternate_keychain_search_list.reset(alternate_keychain_search_list); | |
| 221 } | |
| 222 // If a TestKeychainSearchList is present, it will have already set | |
|
Ryan Sleevi
2017/02/14 19:26:16
newline between 221/222
mattm
2017/02/16 22:06:08
Done.
| |
| 223 // |scoped_alternate_keychain_search_list|, which will be used as the | |
| 224 // basis for reordering the keychain. Otherwise, get the current keychain | |
| 225 // search list and use that. | |
| 226 if (!scoped_alternate_keychain_search_list) { | |
| 227 CFArrayRef keychain_search_list; | |
| 228 OSStatus status = SecKeychainCopySearchList(&keychain_search_list); | |
|
Ryan Sleevi
2017/02/14 19:26:16
.InitializeInto as well
mattm
2017/02/16 22:06:08
Done.
| |
| 229 if (status) { | |
| 230 OSSTATUS_LOG(ERROR, status) << "SecKeychainCopySearchList error"; | |
| 231 return matching_items; | |
| 232 } | |
| 233 scoped_alternate_keychain_search_list.reset(keychain_search_list); | |
| 234 } | |
| 235 CFMutableArrayRef mutable_keychain_search_list = CFArrayCreateMutableCopy( | |
| 236 kCFAllocatorDefault, | |
| 237 CFArrayGetCount(scoped_alternate_keychain_search_list.get()) + 1, | |
| 238 scoped_alternate_keychain_search_list.get()); | |
| 239 if (!mutable_keychain_search_list) { | |
| 240 LOG(ERROR) << "CFArrayCreateMutableCopy"; | |
| 241 return matching_items; | |
| 242 } | |
| 243 scoped_alternate_keychain_search_list.reset(mutable_keychain_search_list); | |
| 244 SecKeychainRef roots_keychain; | |
| 245 // The System Roots keychain is not normally searched by SecItemCopyMatching. | |
| 246 // Get a reference to it and include in the keychain search list. | |
| 247 OSStatus status = SecKeychainOpen( | |
| 248 "/System/Library/Keychains/SystemRootCertificates.keychain", | |
| 249 &roots_keychain); | |
|
Ryan Sleevi
2017/02/14 19:26:16
.InitializeInto :)
mattm
2017/02/16 22:06:08
Done.
| |
| 250 if (status) { | |
| 251 OSSTATUS_LOG(ERROR, status) << "SecKeychainOpen error"; | |
| 252 return matching_items; | |
| 253 } | |
| 254 base::ScopedCFTypeRef<SecKeychainRef> scoped_roots_keychain(roots_keychain); | |
| 255 CFArrayAppendValue(mutable_keychain_search_list, roots_keychain); | |
| 256 CFDictionarySetValue(query, kSecMatchSearchList, | |
| 257 scoped_alternate_keychain_search_list.get()); | |
| 258 | |
| 259 OSStatus err; | |
| 260 { | |
| 261 base::AutoLock lock(crypto::GetMacSecurityServicesLock()); | |
| 262 err = SecItemCopyMatching( | |
| 263 query, reinterpret_cast<CFTypeRef*>(matching_items.InitializeInto())); | |
| 264 } | |
| 265 if (err == errSecItemNotFound) { | |
| 266 DVLOG(2) << "SecItemCopyMatching: no matches found"; | |
| 267 return matching_items; | |
| 268 } | |
| 269 if (err) { | |
| 270 crypto::LogCSSMError("SecItemCopyMatching", err); | |
|
Ryan Sleevi
2017/02/14 19:26:16
|err| isn't guaranteed to be in the CSSM error spa
mattm
2017/02/16 22:06:08
Done.
| |
| 271 OSSTATUS_LOG(ERROR, err) << "SecItemCopyMatching error"; | |
| 272 return matching_items; | |
| 273 } | |
| 274 return matching_items; | |
| 275 } | |
| 276 | |
| 277 // static | |
| 278 void TrustStoreMac::FilterTrustedCertificates(CFArrayRef matching_items, | |
| 279 const CFStringRef policy_oid, | |
| 280 TrustAnchors* out_anchors) { | |
| 281 for (CFIndex i = 0, item_count = CFArrayGetCount(matching_items); | |
| 282 i < item_count; ++i) { | |
| 283 DVLOG(2) << "i=" << i << " of " << item_count; | |
| 284 SecCertificateRef match_cert_handle = reinterpret_cast<SecCertificateRef>( | |
| 285 const_cast<void*>(CFArrayGetValueAtIndex(matching_items, i))); | |
| 286 | |
| 287 if (!IsSecCertificateTrustedForPolicy(match_cert_handle, policy_oid)) { | |
| 288 DVLOG(2) << "not trusted for policy"; | |
| 289 continue; | |
| 290 } else { | |
| 291 DVLOG(2) << "trusted!"; | |
| 292 } | |
| 293 | |
| 294 base::ScopedCFTypeRef<CFDataRef> der_data( | |
| 295 SecCertificateCopyData(match_cert_handle)); | |
| 296 if (!der_data) { | |
| 297 LOG(ERROR) << "SecCertificateCopyData error"; | |
| 298 continue; | |
| 299 } | |
| 300 | |
| 301 CertErrors errors; | |
| 302 ParseCertificateOptions options; | |
| 303 options.allow_invalid_serial_numbers = true; | |
|
Ryan Sleevi
2017/02/14 19:26:16
Document? What cert(s) are this? If you have the S
mattm
2017/02/16 22:06:08
done.
(The other cert that fails was a com.apple.
| |
| 304 scoped_refptr<ParsedCertificate> anchor_cert = ParsedCertificate::Create( | |
| 305 CFDataGetBytePtr(der_data.get()), CFDataGetLength(der_data.get()), | |
| 306 options, &errors); | |
| 307 if (!anchor_cert) { | |
| 308 // TODO(crbug.com/634443): return errors better. | |
| 309 LOG(ERROR) << "Error parsing issuer certificate:\n" | |
| 310 << errors.ToDebugString(); | |
| 311 continue; | |
| 312 } | |
| 313 | |
| 314 DVLOG(2) << "added cert to out_anchors"; | |
| 315 out_anchors->push_back(TrustAnchor::CreateFromCertificateNoConstraints( | |
| 316 std::move(anchor_cert))); | |
| 317 } | |
| 318 } | |
| 319 | |
| 320 // static | |
| 321 base::ScopedCFTypeRef<CFDataRef> TrustStoreMac::GetMacNormalizedIssuer( | |
| 322 const scoped_refptr<ParsedCertificate>& cert) { | |
| 323 base::ScopedCFTypeRef<CFDataRef> name_data; | |
| 324 // There does not appear to be any public API to get the normalized version | |
| 325 // of a Name without creating a SecCertificate. | |
| 326 X509Certificate::OSCertHandle cert_handle = | |
| 327 X509Certificate::CreateOSCertHandleFromBytes( | |
| 328 cert->der_cert().AsStringPiece().data(), cert->der_cert().Length()); | |
| 329 if (!cert_handle) { | |
| 330 LOG(ERROR) << "CreateOSCertHandleFromBytes"; | |
| 331 return name_data; | |
| 332 } | |
| 333 base::ScopedCFTypeRef<SecCertificateRef> scoped_cert_handle(cert_handle); | |
| 334 name_data.reset( | |
| 335 SecCertificateCopyNormalizedIssuerContent(cert_handle, nullptr)); | |
| 336 if (!name_data) | |
| 337 LOG(ERROR) << "SecCertificateCopyNormalizedIssuerContent"; | |
| 338 return name_data; | |
| 339 } | |
| 340 | |
| 341 void TrustStoreMac::FindTrustAnchorsByMacNormalizedSubject( | |
| 342 CFDataRef name_data, | |
| 343 TrustAnchors* out_anchors) const { | |
| 344 base::ScopedCFTypeRef<CFArrayRef> scoped_matching_items = | |
| 345 FindMatchingCertificatesForMacNormalizedSubject(name_data); | |
| 346 if (!scoped_matching_items) | |
| 347 return; | |
| 348 | |
| 349 FilterTrustedCertificates(scoped_matching_items.get(), policy_oid_, | |
| 350 out_anchors); | |
| 351 } | |
| 352 | |
| 353 } // namespace net | |
| OLD | NEW |