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

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

Issue 2585963003: PKI library Mac trust store integration (Closed)
Patch Set: review changes for comment #19 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/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
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698