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

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

Issue 2585963003: PKI library Mac trust store integration (Closed)
Patch Set: review changes & cleanups 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 <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
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698