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 |