Chromium Code Reviews

Side by Side Diff: net/base/x509_chain_mac.cc

Issue 3112013: Move chain building/verification out of X509Certificate (Closed)
Patch Set: Rebase to trunk - Without OpenSSL fixes Created 10 years, 2 months ago
Use n/p to move between diff chunks; N/P to move between comments.
Jump to:
View unified diff |
OLDNEW
(Empty)
1 // Copyright (c) 2010 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/base/x509_chain.h"
6
7 #include <Security/Security.h>
8
9 #include "base/logging.h"
10 #include "base/scoped_cftyperef.h"
11 #include "net/base/cert_status_flags.h"
12 #include "net/base/cert_verify_result.h"
13 #include "net/base/net_errors.h"
14 #include "net/base/x509_certificate.h"
15
16 namespace net {
17
18 class MacTrustedCertificates {
19 public:
20 // Sets the trusted root certificate used by tests. Call with |cert| set
21 // to NULL to clear the test certificate.
22 void SetTestCertificate(X509Certificate* cert) {
23 AutoLock lock(lock_);
24 test_certificate_ = cert;
25 }
26
27 // Returns an array containing the trusted certificates for use with
28 // SecTrustSetAnchorCertificates(). Returns NULL if the system-supplied
29 // list of trust anchors is acceptable (that is, there is not test
30 // certificate available). Ownership follows the Create Rule (caller
31 // is responsible for calling CFRelease on the non-NULL result).
32 CFArrayRef CopyTrustedCertificateArray() {
33 AutoLock lock(lock_);
34
35 if (!test_certificate_)
36 return NULL;
37
38 // Failure to copy the anchor certificates or add the test certificate
39 // is non-fatal; SecTrustEvaluate() will use the system anchors instead.
40 CFArrayRef anchor_array;
41 OSStatus status = SecTrustCopyAnchorCertificates(&anchor_array);
42 if (status)
43 return NULL;
44 scoped_cftyperef<CFArrayRef> scoped_anchor_array(anchor_array);
45 CFMutableArrayRef merged_array = CFArrayCreateMutableCopy(
46 kCFAllocatorDefault, 0, anchor_array);
47 if (!merged_array)
48 return NULL;
49 CFArrayAppendValue(merged_array, test_certificate_->os_cert_handle());
50
51 return merged_array;
52 }
53 private:
54 friend struct DefaultSingletonTraits<MacTrustedCertificates>;
55
56 // Obtain an instance of MacTrustedCertificates via the singleton
57 // interface.
58 MacTrustedCertificates() : test_certificate_(NULL) { }
59
60 // An X509Certificate object that may be appended to the list of
61 // system trusted anchors.
62 scoped_refptr<X509Certificate> test_certificate_;
63
64 // The trusted cache may be accessed from multiple threads.
65 mutable Lock lock_;
66
67 DISALLOW_COPY_AND_ASSIGN(MacTrustedCertificates);
68 };
69
70 void SetMacTestCertificate(X509Certificate* cert) {
71 Singleton<MacTrustedCertificates>::get()->SetTestCertificate(cert);
72 }
73
74 namespace {
75
76 typedef OSStatus (*SecTrustCopyExtendedResultFuncPtr)(SecTrustRef,
77 CFDictionaryRef*);
78
79 int NetErrorFromOSStatus(OSStatus status) {
80 switch (status) {
81 case noErr:
82 return OK;
83 case errSecNotAvailable:
84 case errSecNoCertificateModule:
85 case errSecNoPolicyModule:
86 return ERR_NOT_IMPLEMENTED;
87 case errSecAuthFailed:
88 return ERR_ACCESS_DENIED;
89 default:
90 LOG(ERROR) << "Unknown error " << status << " mapped to net::ERR_FAILED";
91 return ERR_FAILED;
92 }
93 }
94
95 int CertStatusFromOSStatus(OSStatus status) {
96 switch (status) {
97 case noErr:
98 return 0;
99
100 case CSSMERR_TP_INVALID_ANCHOR_CERT:
101 case CSSMERR_TP_NOT_TRUSTED:
102 case CSSMERR_TP_INVALID_CERT_AUTHORITY:
103 return CERT_STATUS_AUTHORITY_INVALID;
104
105 case CSSMERR_TP_CERT_EXPIRED:
106 case CSSMERR_TP_CERT_NOT_VALID_YET:
107 // "Expired" and "not yet valid" collapse into a single status.
108 return CERT_STATUS_DATE_INVALID;
109
110 case CSSMERR_TP_CERT_REVOKED:
111 case CSSMERR_TP_CERT_SUSPENDED:
112 return CERT_STATUS_REVOKED;
113
114 case CSSMERR_APPLETP_HOSTNAME_MISMATCH:
115 return CERT_STATUS_COMMON_NAME_INVALID;
116
117 case CSSMERR_APPLETP_CRL_NOT_FOUND:
118 case CSSMERR_APPLETP_INCOMPLETE_REVOCATION_CHECK:
119 case CSSMERR_APPLETP_OCSP_UNAVAILABLE:
120 return CERT_STATUS_NO_REVOCATION_MECHANISM;
121
122 case CSSMERR_APPLETP_CRL_NOT_TRUSTED:
123 case CSSMERR_APPLETP_CRL_SERVER_DOWN:
124 case CSSMERR_APPLETP_CRL_NOT_VALID_YET:
125 case CSSMERR_APPLETP_NETWORK_FAILURE:
126 case CSSMERR_APPLETP_OCSP_BAD_RESPONSE:
127 case CSSMERR_APPLETP_OCSP_NO_SIGNER:
128 case CSSMERR_APPLETP_OCSP_RESP_UNAUTHORIZED:
129 case CSSMERR_APPLETP_OCSP_RESP_SIG_REQUIRED:
130 case CSSMERR_APPLETP_OCSP_RESP_MALFORMED_REQ:
131 case CSSMERR_APPLETP_OCSP_RESP_INTERNAL_ERR:
132 case CSSMERR_APPLETP_OCSP_RESP_TRY_LATER:
133 // We asked for a revocation check, but didn't get it.
134 return CERT_STATUS_UNABLE_TO_CHECK_REVOCATION;
135
136 default:
137 // Failure was due to something Chromium doesn't define a
138 // specific status for (such as basic constraints violation, or
139 // unknown critical extension)
140 return CERT_STATUS_INVALID;
141 }
142 }
143
144 bool OverrideHostnameMismatch(const std::string& hostname,
145 std::vector<std::string>* dns_names) {
146 // SecTrustEvaluate() does not check dotted IP addresses. If
147 // hostname is provided as, say, 127.0.0.1, then the error
148 // CSSMERR_APPLETP_HOSTNAME_MISMATCH will always be returned,
149 // even if the certificate contains 127.0.0.1 as one of its names.
150 // We, however, want to allow that behavior. SecTrustEvaluate()
151 // only checks for digits and dots when considering whether a
152 // hostname is an IP address, so IPv6 and hex addresses go through
153 // its normal comparison.
154 bool is_dotted_ip = true;
155 bool override_hostname_mismatch = false;
156 for (std::string::const_iterator c = hostname.begin();
157 c != hostname.end() && is_dotted_ip; ++c)
158 is_dotted_ip = (*c >= '0' && *c <= '9') || *c == '.';
159 if (is_dotted_ip) {
160 for (std::vector<std::string>::const_iterator name = dns_names->begin();
161 name != dns_names->end() && !override_hostname_mismatch; ++name)
162 override_hostname_mismatch = (*name == hostname);
163 }
164 return override_hostname_mismatch;
165 }
166
167 // Creates a SecPolicyRef for the given OID, with optional value.
168 OSStatus CreatePolicy(const CSSM_OID* policy_OID,
169 void* option_data,
170 size_t option_length,
171 SecPolicyRef* policy) {
172 SecPolicySearchRef search;
173 OSStatus err = SecPolicySearchCreate(CSSM_CERT_X_509v3, policy_OID, NULL,
174 &search);
175 if (err)
176 return err;
177 err = SecPolicySearchCopyNext(search, policy);
178 CFRelease(search);
179 if (err)
180 return err;
181
182 if (option_data) {
183 CSSM_DATA options_data = {
184 option_length,
185 reinterpret_cast<uint8_t*>(option_data)
186 };
187 err = SecPolicySetValue(*policy, &options_data);
188 if (err) {
189 CFRelease(*policy);
190 return err;
191 }
192 }
193 return noErr;
194 }
195
196 } // namespace
197
198 namespace x509_chain {
199
200 int VerifySSLServer(X509Certificate* certificate, const std::string& hostname,
201 int flags, CertVerifyResult* verify_result) {
202 verify_result->Reset();
203 if (!certificate || !certificate->os_cert_handle())
204 return ERR_UNEXPECTED;
205
206 // Create an SSL SecPolicyRef, and configure it to perform hostname
207 // validation. The hostname check does 99% of what we want, with the
208 // exception of dotted IPv4 addreses, which we handle ourselves below.
209 CSSM_APPLE_TP_SSL_OPTIONS tp_ssl_options = {
210 CSSM_APPLE_TP_SSL_OPTS_VERSION,
211 hostname.size(),
212 hostname.data(),
213 0
214 };
215 SecPolicyRef ssl_policy;
216 OSStatus status = CreatePolicy(&CSSMOID_APPLE_TP_SSL,
217 &tp_ssl_options,
218 sizeof(tp_ssl_options),
219 &ssl_policy);
220 if (status)
221 return NetErrorFromOSStatus(status);
222 scoped_cftyperef<SecPolicyRef> scoped_ssl_policy(ssl_policy);
223
224 // Create and configure a SecTrustRef, which takes our certificate(s)
225 // and our SSL SecPolicyRef. SecTrustCreateWithCertificates() takes an
226 // array of certificates, the first of which is the certificate we're
227 // verifying, and the subsequent (optional) certificates are used for
228 // chain building.
229 scoped_cftyperef<CFArrayRef> cert_list(
230 certificate->CreateOSCertListHandle());
231
232 SecTrustRef trust_ref = NULL;
233 status = SecTrustCreateWithCertificates(cert_list, ssl_policy, &trust_ref);
234 if (status)
235 return NetErrorFromOSStatus(status);
236 scoped_cftyperef<SecTrustRef> scoped_trust_ref(trust_ref);
237
238 // Set the trusted anchor certificates for the SecTrustRef by merging the
239 // system trust anchors and the test root certificate.
240 CFArrayRef anchor_array =
241 Singleton<MacTrustedCertificates>::get()->CopyTrustedCertificateArray();
242 scoped_cftyperef<CFArrayRef> scoped_anchor_array(anchor_array);
243 if (anchor_array) {
244 status = SecTrustSetAnchorCertificates(trust_ref, anchor_array);
245 if (status)
246 return NetErrorFromOSStatus(status);
247 }
248
249 if (flags & VERIFY_REV_CHECKING_ENABLED) {
250 // When called with VERIFY_REV_CHECKING_ENABLED, we ask SecTrustEvaluate()
251 // to apply OCSP and CRL checking, but we're still subject to the global
252 // settings, which are configured in the Keychain Access application (in
253 // the Certificates tab of the Preferences dialog). If the user has
254 // revocation disabled (which is the default), then we will get
255 // kSecTrustResultRecoverableTrustFailure back from SecTrustEvaluate()
256 // with one of a number of sub error codes indicating that revocation
257 // checking did not occur. In that case, we'll set our own result to include
258 // CERT_STATUS_UNABLE_TO_CHECK_REVOCATION.
259 //
260 // NOTE: This does not apply to EV certificates, which always get
261 // revocation checks regardless of the global settings.
262 verify_result->cert_status |= CERT_STATUS_REV_CHECKING_ENABLED;
263 CSSM_APPLE_TP_ACTION_DATA tp_action_data = { CSSM_APPLE_TP_ACTION_VERSION };
264 tp_action_data.ActionFlags = CSSM_TP_ACTION_REQUIRE_REV_PER_CERT;
265 CFDataRef action_data_ref =
266 CFDataCreate(NULL, reinterpret_cast<UInt8*>(&tp_action_data),
267 sizeof(tp_action_data));
268 if (!action_data_ref)
269 return ERR_OUT_OF_MEMORY;
270 scoped_cftyperef<CFDataRef> scoped_action_data_ref(action_data_ref);
271 status = SecTrustSetParameters(trust_ref, CSSM_TP_ACTION_DEFAULT,
272 action_data_ref);
273 if (status)
274 return NetErrorFromOSStatus(status);
275 }
276
277 // Verify the certificate. A non-zero result from SecTrustGetResult()
278 // indicates that some fatal error occurred and the chain couldn't be
279 // processed, not that the chain contains no errors. We need to examine the
280 // output of SecTrustGetResult() to determine that.
281 SecTrustResultType trust_result;
282 status = SecTrustEvaluate(trust_ref, &trust_result);
283 if (status)
284 return NetErrorFromOSStatus(status);
285 CFArrayRef completed_chain = NULL;
286 CSSM_TP_APPLE_EVIDENCE_INFO* chain_info;
287 status = SecTrustGetResult(trust_ref, &trust_result, &completed_chain,
288 &chain_info);
289 if (status)
290 return NetErrorFromOSStatus(status);
291 scoped_cftyperef<CFArrayRef> scoped_completed_chain(completed_chain);
292
293 // Evaluate the results
294 OSStatus cssm_result;
295 bool got_certificate_error = false;
296 switch (trust_result) {
297 case kSecTrustResultUnspecified:
298 case kSecTrustResultProceed:
299 // Certificate chain is valid and trusted ("unspecified" indicates that
300 // the user has not explicitly set a trust setting)
301 break;
302
303 case kSecTrustResultDeny:
304 case kSecTrustResultConfirm:
305 // Certificate chain is explicitly untrusted. For kSecTrustResultConfirm,
306 // we're following what Secure Transport does and treating it as
307 // "deny".
308 verify_result->cert_status |= CERT_STATUS_AUTHORITY_INVALID;
309 break;
310
311 case kSecTrustResultRecoverableTrustFailure:
312 // Certificate chain has a failure that can be overridden by the user.
313 status = SecTrustGetCssmResultCode(trust_ref, &cssm_result);
314 if (status)
315 return NetErrorFromOSStatus(status);
316 switch (cssm_result) {
317 case CSSMERR_TP_NOT_TRUSTED:
318 case CSSMERR_TP_INVALID_ANCHOR_CERT:
319 verify_result->cert_status |= CERT_STATUS_AUTHORITY_INVALID;
320 break;
321 case CSSMERR_TP_CERT_EXPIRED:
322 case CSSMERR_TP_CERT_NOT_VALID_YET:
323 verify_result->cert_status |= CERT_STATUS_DATE_INVALID;
324 break;
325 case CSSMERR_TP_CERT_REVOKED:
326 case CSSMERR_TP_CERT_SUSPENDED:
327 verify_result->cert_status |= CERT_STATUS_REVOKED;
328 break;
329 default:
330 // Look for specific per-certificate errors below.
331 break;
332 }
333 // Walk the chain of error codes in the CSSM_TP_APPLE_EVIDENCE_INFO
334 // structure which can catch multiple errors from each certificate.
335 for (CFIndex index = 0, chain_count = CFArrayGetCount(completed_chain);
336 index < chain_count; ++index) {
337 if (chain_info[index].StatusBits & CSSM_CERT_STATUS_EXPIRED ||
338 chain_info[index].StatusBits & CSSM_CERT_STATUS_NOT_VALID_YET)
339 verify_result->cert_status |= CERT_STATUS_DATE_INVALID;
340 for (uint32 status_code_index = 0;
341 status_code_index < chain_info[index].NumStatusCodes;
342 ++status_code_index) {
343 got_certificate_error = true;
344 int cert_status = CertStatusFromOSStatus(
345 chain_info[index].StatusCodes[status_code_index]);
346 if (cert_status == CERT_STATUS_COMMON_NAME_INVALID) {
347 std::vector<std::string> names;
348 certificate->GetDNSNames(&names);
349 if (OverrideHostnameMismatch(hostname, &names)) {
350 cert_status = 0;
351 }
352 }
353 verify_result->cert_status |= cert_status;
354 }
355 }
356 // Be paranoid and ensure that we recorded at least one certificate
357 // status on receiving kSecTrustResultRecoverableTrustFailure. The
358 // call to SecTrustGetCssmResultCode() should pick up when the chain
359 // is not trusted and the loop through CSSM_TP_APPLE_EVIDENCE_INFO
360 // should pick up everything else, but let's be safe.
361 if (!verify_result->cert_status && !got_certificate_error) {
362 verify_result->cert_status |= CERT_STATUS_INVALID;
363 NOTREACHED();
364 }
365 break;
366
367 default:
368 status = SecTrustGetCssmResultCode(trust_ref, &cssm_result);
369 if (status)
370 return NetErrorFromOSStatus(status);
371 verify_result->cert_status |= CertStatusFromOSStatus(cssm_result);
372 if (!verify_result->cert_status) {
373 verify_result->cert_status |= CERT_STATUS_INVALID;
374 }
375 break;
376 }
377
378 // TODO(wtc): Suppress CERT_STATUS_NO_REVOCATION_MECHANISM for now to be
379 // compatible with Windows, which in turn implements this behavior to be
380 // compatible with WinHTTP, which doesn't report this error (bug 3004).
381 verify_result->cert_status &= ~CERT_STATUS_NO_REVOCATION_MECHANISM;
382
383 if (IsCertStatusError(verify_result->cert_status))
384 return MapCertStatusToNetError(verify_result->cert_status);
385
386 if (flags & VERIFY_EV_CERT) {
387 // Determine the certificate's EV status using SecTrustCopyExtendedResult(),
388 // which we need to look up because the function wasn't added until
389 // Mac OS X 10.5.7.
390 CFBundleRef bundle =
391 CFBundleGetBundleWithIdentifier(CFSTR("com.apple.security"));
392 if (bundle) {
393 SecTrustCopyExtendedResultFuncPtr copy_extended_result =
394 reinterpret_cast<SecTrustCopyExtendedResultFuncPtr>(
395 CFBundleGetFunctionPointerForName(bundle,
396 CFSTR("SecTrustCopyExtendedResult")));
397 if (copy_extended_result) {
398 CFDictionaryRef ev_dict = NULL;
399 status = copy_extended_result(trust_ref, &ev_dict);
400 if (!status && ev_dict) {
401 // The returned dictionary contains the EV organization name from the
402 // server certificate, which we don't need at this point (and we
403 // have other ways to access, anyway). All we care is that
404 // SecTrustCopyExtendedResult() returned noErr and a non-NULL
405 // dictionary.
406 CFRelease(ev_dict);
407 verify_result->cert_status |= CERT_STATUS_IS_EV;
408 }
409 }
410 }
411 }
412
413 return OK;
414 }
415
416 } // namespace x509_chain
417
418 } // namespace net
OLDNEW

Powered by Google App Engine