Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(16)

Side by Side Diff: net/cert/internal/trust_store_mac.cc

Issue 2585963003: PKI library Mac trust store integration (Closed)
Patch Set: test fixes Created 3 years, 10 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(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
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698