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 |