blob: a088a32f4c51d924bf1e2e02ebcbaa105d9c32de [file] [log] [blame]
mattmea4ed8232017-02-28 23:13:231// Copyright 2017 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#include "net/cert/x509_util.h"
21
22namespace net {
23
24namespace {
25
26// The rules for interpreting trust settings are documented at:
27// https://developer.apple.com/reference/security/1400261-sectrustsettingscopytrustsetting?language=objc
28
29// Indicates the trust status of a certificate.
30enum class TrustStatus {
31 // Certificate inherits trust value from its issuer. If the certificate is the
32 // root of the chain, this implies distrust.
33 UNSPECIFIED,
34 // Certificate is a trust anchor.
35 TRUSTED,
36 // Certificate is blacklisted / explicitly distrusted.
37 DISTRUSTED
38};
39
40// Returns trust status of usage constraints dictionary |trust_dict| for a
41// certificate that |is_self_signed|.
42TrustStatus IsTrustDictionaryTrustedForPolicy(
43 CFDictionaryRef trust_dict,
44 bool is_self_signed,
45 const CFStringRef target_policy_oid) {
46 // An empty trust dict should be interpreted as
47 // kSecTrustSettingsResultTrustRoot. This is handled by falling through all
48 // the conditions below with the default value of |trust_settings_result|.
49
50 // Trust settings may be scoped to a single application, by checking that the
51 // code signing identity of the current application matches the serialized
52 // code signing identity in the kSecTrustSettingsApplication key.
53 // As this is not presently supported, skip any trust settings scoped to the
54 // application.
55 if (CFDictionaryContainsKey(trust_dict, kSecTrustSettingsApplication))
56 return TrustStatus::UNSPECIFIED;
57
58 // Trust settings may be scoped using policy-specific constraints. For
59 // example, SSL trust settings might be scoped to a single hostname, or EAP
60 // settings specific to a particular WiFi network.
61 // As this is not presently supported, skip any policy-specific trust
62 // settings.
63 if (CFDictionaryContainsKey(trust_dict, kSecTrustSettingsPolicyString))
64 return TrustStatus::UNSPECIFIED;
65
66 // Ignoring kSecTrustSettingsKeyUsage for now; it does not seem relevant to
67 // the TLS case.
68
69 // If the trust settings are scoped to a specific policy (via
70 // kSecTrustSettingsPolicy), ensure that the policy is the same policy as
71 // |target_policy_oid|. If there is no kSecTrustSettingsPolicy key, it's
72 // considered a match for all policies.
73 SecPolicyRef policy_ref = base::mac::GetValueFromDictionary<SecPolicyRef>(
74 trust_dict, kSecTrustSettingsPolicy);
75 if (policy_ref) {
76 base::ScopedCFTypeRef<CFDictionaryRef> policy_dict;
77 {
78 base::AutoLock lock(crypto::GetMacSecurityServicesLock());
79 policy_dict.reset(SecPolicyCopyProperties(policy_ref));
80 }
81
82 // kSecPolicyOid is guaranteed to be present in the policy dictionary.
83 //
84 // TODO(mattm): remove the CFCastStrict below once Chromium builds against
85 // the 10.11 SDK.
86 CFStringRef policy_oid = base::mac::GetValueFromDictionary<CFStringRef>(
87 policy_dict, base::mac::CFCastStrict<CFStringRef>(kSecPolicyOid));
88
89 if (!CFEqual(policy_oid, target_policy_oid))
90 return TrustStatus::UNSPECIFIED;
91 }
92
93 // If kSecTrustSettingsResult is not present in the trust dict,
94 // kSecTrustSettingsResultTrustRoot is assumed.
95 int trust_settings_result = kSecTrustSettingsResultTrustRoot;
96 CFNumberRef trust_settings_result_ref =
97 base::mac::GetValueFromDictionary<CFNumberRef>(trust_dict,
98 kSecTrustSettingsResult);
99 if (trust_settings_result_ref &&
100 !CFNumberGetValue(trust_settings_result_ref, kCFNumberIntType,
101 &trust_settings_result)) {
102 return TrustStatus::UNSPECIFIED;
103 }
104
105 if (trust_settings_result == kSecTrustSettingsResultDeny)
106 return TrustStatus::DISTRUSTED;
107
108 // kSecTrustSettingsResultTrustRoot can only be applied to root(self-signed)
109 // certs.
110 if (is_self_signed)
111 return (trust_settings_result == kSecTrustSettingsResultTrustRoot)
112 ? TrustStatus::TRUSTED
113 : TrustStatus::UNSPECIFIED;
114
115 // kSecTrustSettingsResultTrustAsRoot can only be applied to non-root certs.
116 return (trust_settings_result == kSecTrustSettingsResultTrustAsRoot)
117 ? TrustStatus::TRUSTED
118 : TrustStatus::UNSPECIFIED;
119}
120
121// Returns true if the trust settings array |trust_settings| for a certificate
122// that |is_self_signed| should be treated as a trust anchor.
123TrustStatus IsTrustSettingsTrustedForPolicy(CFArrayRef trust_settings,
124 bool is_self_signed,
125 const CFStringRef policy_oid) {
126 // An empty trust settings array (that is, the trust_settings parameter
127 // returns a valid but empty CFArray) means "always trust this certificate"
128 // with an overall trust setting for the certificate of
129 // kSecTrustSettingsResultTrustRoot.
130 if (CFArrayGetCount(trust_settings) == 0 && is_self_signed)
131 return TrustStatus::TRUSTED;
132
133 for (CFIndex i = 0, settings_count = CFArrayGetCount(trust_settings);
134 i < settings_count; ++i) {
135 CFDictionaryRef trust_dict = reinterpret_cast<CFDictionaryRef>(
136 const_cast<void*>(CFArrayGetValueAtIndex(trust_settings, i)));
137 TrustStatus trust = IsTrustDictionaryTrustedForPolicy(
138 trust_dict, is_self_signed, policy_oid);
139 if (trust != TrustStatus::UNSPECIFIED)
140 return trust;
141 }
142 return TrustStatus::UNSPECIFIED;
143}
144
145// Returns true if the certificate |cert_handle| is trusted for the policy
146// |policy_oid|.
147TrustStatus IsSecCertificateTrustedForPolicy(SecCertificateRef cert_handle,
148 const CFStringRef policy_oid) {
149 const bool is_self_signed = X509Certificate::IsSelfSigned(cert_handle);
150 // Evaluate trust domains in user, admin, system order. Admin settings can
151 // override system ones, and user settings can override both admin and system.
152 for (const auto& trust_domain :
153 {kSecTrustSettingsDomainUser, kSecTrustSettingsDomainAdmin,
154 kSecTrustSettingsDomainSystem}) {
155 base::ScopedCFTypeRef<CFArrayRef> trust_settings;
156 OSStatus err;
157 {
158 base::AutoLock lock(crypto::GetMacSecurityServicesLock());
159 err = SecTrustSettingsCopyTrustSettings(cert_handle, trust_domain,
160 trust_settings.InitializeInto());
161 }
162 if (err == errSecItemNotFound) {
163 // No trust settings for that domain.. try the next.
164 continue;
165 }
166 if (err) {
167 OSSTATUS_LOG(ERROR, err) << "SecTrustSettingsCopyTrustSettings error";
168 continue;
169 }
170 TrustStatus trust = IsTrustSettingsTrustedForPolicy(
171 trust_settings, is_self_signed, policy_oid);
172 if (trust != TrustStatus::UNSPECIFIED)
173 return trust;
174 }
175
176 // No trust settings, or none of the settings were for the correct policy, or
177 // had the correct trust result.
178 return TrustStatus::UNSPECIFIED;
179}
180
181// Filters an array of SecCertificateRef by trust for |policy_oid|, returning
182// the results as TrustAnchors in |out_anchors|.
183void FilterTrustedCertificates(CFArrayRef matching_items,
184 const CFStringRef policy_oid,
185 TrustAnchors* out_anchors) {
186 for (CFIndex i = 0, item_count = CFArrayGetCount(matching_items);
187 i < item_count; ++i) {
188 SecCertificateRef match_cert_handle = reinterpret_cast<SecCertificateRef>(
189 const_cast<void*>(CFArrayGetValueAtIndex(matching_items, i)));
190
191 if (IsSecCertificateTrustedForPolicy(match_cert_handle, policy_oid) !=
192 TrustStatus::TRUSTED)
193 continue;
194
195 base::ScopedCFTypeRef<CFDataRef> der_data(
196 SecCertificateCopyData(match_cert_handle));
197 if (!der_data) {
198 LOG(ERROR) << "SecCertificateCopyData error";
199 continue;
200 }
201
202 CertErrors errors;
203 ParseCertificateOptions options;
204 options.allow_invalid_serial_numbers = true;
205 scoped_refptr<ParsedCertificate> anchor_cert = ParsedCertificate::Create(
206 x509_util::CreateCryptoBuffer(CFDataGetBytePtr(der_data.get()),
207 CFDataGetLength(der_data.get())),
208 options, &errors);
209 if (!anchor_cert) {
210 // TODO(crbug.com/634443): return errors better.
211 LOG(ERROR) << "Error parsing issuer certificate:\n"
212 << errors.ToDebugString();
213 continue;
214 }
215
216 out_anchors->push_back(TrustAnchor::CreateFromCertificateNoConstraints(
217 std::move(anchor_cert)));
218 }
219}
220
221} // namespace
222
223TrustStoreMac::TrustStoreMac(CFTypeRef policy_oid)
224 : policy_oid_(base::mac::CFCastStrict<CFStringRef>(policy_oid)) {
225 DCHECK(policy_oid_);
226}
227
228TrustStoreMac::~TrustStoreMac() = default;
229
230void TrustStoreMac::FindTrustAnchorsForCert(
231 const scoped_refptr<ParsedCertificate>& cert,
232 TrustAnchors* out_anchors) const {
233 base::ScopedCFTypeRef<CFDataRef> name_data = GetMacNormalizedIssuer(cert);
234
235 FindTrustAnchorsByMacNormalizedSubject(name_data, out_anchors);
236}
237
238// static
239base::ScopedCFTypeRef<CFArrayRef>
240TrustStoreMac::FindMatchingCertificatesForMacNormalizedSubject(
241 CFDataRef name_data) {
242 base::ScopedCFTypeRef<CFArrayRef> matching_items;
243 base::ScopedCFTypeRef<CFMutableDictionaryRef> query(
244 CFDictionaryCreateMutable(nullptr, 0, &kCFTypeDictionaryKeyCallBacks,
245 &kCFTypeDictionaryValueCallBacks));
246
247 CFDictionarySetValue(query, kSecClass, kSecClassCertificate);
248 CFDictionarySetValue(query, kSecReturnRef, kCFBooleanTrue);
249 CFDictionarySetValue(query, kSecMatchLimit, kSecMatchLimitAll);
250 CFDictionarySetValue(query, kSecAttrSubject, name_data);
251
252 base::ScopedCFTypeRef<CFArrayRef> scoped_alternate_keychain_search_list;
253 if (TestKeychainSearchList::HasInstance()) {
254 OSStatus status = TestKeychainSearchList::GetInstance()->CopySearchList(
255 scoped_alternate_keychain_search_list.InitializeInto());
256 if (status) {
257 OSSTATUS_LOG(ERROR, status)
258 << "TestKeychainSearchList::CopySearchList error";
259 return matching_items;
260 }
261 }
262
263 base::AutoLock lock(crypto::GetMacSecurityServicesLock());
264
265 // If a TestKeychainSearchList is present, it will have already set
266 // |scoped_alternate_keychain_search_list|, which will be used as the
267 // basis for reordering the keychain. Otherwise, get the current keychain
268 // search list and use that.
269 if (!scoped_alternate_keychain_search_list) {
270 OSStatus status = SecKeychainCopySearchList(
271 scoped_alternate_keychain_search_list.InitializeInto());
272 if (status) {
273 OSSTATUS_LOG(ERROR, status) << "SecKeychainCopySearchList error";
274 return matching_items;
275 }
276 }
277
278 CFMutableArrayRef mutable_keychain_search_list = CFArrayCreateMutableCopy(
279 kCFAllocatorDefault,
280 CFArrayGetCount(scoped_alternate_keychain_search_list.get()) + 1,
281 scoped_alternate_keychain_search_list.get());
282 if (!mutable_keychain_search_list) {
283 LOG(ERROR) << "CFArrayCreateMutableCopy";
284 return matching_items;
285 }
286 scoped_alternate_keychain_search_list.reset(mutable_keychain_search_list);
287
288 base::ScopedCFTypeRef<SecKeychainRef> roots_keychain;
289 // The System Roots keychain is not normally searched by SecItemCopyMatching.
290 // Get a reference to it and include in the keychain search list.
291 OSStatus status = SecKeychainOpen(
292 "/System/Library/Keychains/SystemRootCertificates.keychain",
293 roots_keychain.InitializeInto());
294 if (status) {
295 OSSTATUS_LOG(ERROR, status) << "SecKeychainOpen error";
296 return matching_items;
297 }
298 CFArrayAppendValue(mutable_keychain_search_list, roots_keychain);
299
300 CFDictionarySetValue(query, kSecMatchSearchList,
301 scoped_alternate_keychain_search_list.get());
302
303 OSStatus err = SecItemCopyMatching(
304 query, reinterpret_cast<CFTypeRef*>(matching_items.InitializeInto()));
305 if (err == errSecItemNotFound) {
306 // No matches found.
307 return matching_items;
308 }
309 if (err) {
310 OSSTATUS_LOG(ERROR, err) << "SecItemCopyMatching error";
311 return matching_items;
312 }
313 return matching_items;
314}
315
316// static
317base::ScopedCFTypeRef<CFDataRef> TrustStoreMac::GetMacNormalizedIssuer(
318 const scoped_refptr<ParsedCertificate>& cert) {
319 base::ScopedCFTypeRef<CFDataRef> name_data;
320 // There does not appear to be any public API to get the normalized version
321 // of a Name without creating a SecCertificate.
322 base::ScopedCFTypeRef<SecCertificateRef> cert_handle(
323 X509Certificate::CreateOSCertHandleFromBytes(
324 cert->der_cert().AsStringPiece().data(), cert->der_cert().Length()));
325 if (!cert_handle) {
326 LOG(ERROR) << "CreateOSCertHandleFromBytes";
327 return name_data;
328 }
329 {
330 base::AutoLock lock(crypto::GetMacSecurityServicesLock());
331 name_data.reset(
332 SecCertificateCopyNormalizedIssuerContent(cert_handle, nullptr));
333 }
334 if (!name_data)
335 LOG(ERROR) << "SecCertificateCopyNormalizedIssuerContent";
336 return name_data;
337}
338
339void TrustStoreMac::FindTrustAnchorsByMacNormalizedSubject(
340 CFDataRef name_data,
341 TrustAnchors* out_anchors) const {
342 base::ScopedCFTypeRef<CFArrayRef> scoped_matching_items =
343 FindMatchingCertificatesForMacNormalizedSubject(name_data);
344 if (!scoped_matching_items)
345 return;
346
347 FilterTrustedCertificates(scoped_matching_items.get(), policy_oid_,
348 out_anchors);
349}
350
351} // namespace net