Index: net/base/x509_chain_mac.cc |
diff --git a/net/base/x509_chain_mac.cc b/net/base/x509_chain_mac.cc |
new file mode 100644 |
index 0000000000000000000000000000000000000000..b1503f9faff66d19bd56305366d4caa71c4aebe9 |
--- /dev/null |
+++ b/net/base/x509_chain_mac.cc |
@@ -0,0 +1,418 @@ |
+// Copyright (c) 2010 The Chromium Authors. All rights reserved. |
+// Use of this source code is governed by a BSD-style license that can be |
+// found in the LICENSE file. |
+ |
+#include "net/base/x509_chain.h" |
+ |
+#include <Security/Security.h> |
+ |
+#include "base/logging.h" |
+#include "base/scoped_cftyperef.h" |
+#include "net/base/cert_status_flags.h" |
+#include "net/base/cert_verify_result.h" |
+#include "net/base/net_errors.h" |
+#include "net/base/x509_certificate.h" |
+ |
+namespace net { |
+ |
+class MacTrustedCertificates { |
+ public: |
+ // Sets the trusted root certificate used by tests. Call with |cert| set |
+ // to NULL to clear the test certificate. |
+ void SetTestCertificate(X509Certificate* cert) { |
+ AutoLock lock(lock_); |
+ test_certificate_ = cert; |
+ } |
+ |
+ // Returns an array containing the trusted certificates for use with |
+ // SecTrustSetAnchorCertificates(). Returns NULL if the system-supplied |
+ // list of trust anchors is acceptable (that is, there is not test |
+ // certificate available). Ownership follows the Create Rule (caller |
+ // is responsible for calling CFRelease on the non-NULL result). |
+ CFArrayRef CopyTrustedCertificateArray() { |
+ AutoLock lock(lock_); |
+ |
+ if (!test_certificate_) |
+ return NULL; |
+ |
+ // Failure to copy the anchor certificates or add the test certificate |
+ // is non-fatal; SecTrustEvaluate() will use the system anchors instead. |
+ CFArrayRef anchor_array; |
+ OSStatus status = SecTrustCopyAnchorCertificates(&anchor_array); |
+ if (status) |
+ return NULL; |
+ scoped_cftyperef<CFArrayRef> scoped_anchor_array(anchor_array); |
+ CFMutableArrayRef merged_array = CFArrayCreateMutableCopy( |
+ kCFAllocatorDefault, 0, anchor_array); |
+ if (!merged_array) |
+ return NULL; |
+ CFArrayAppendValue(merged_array, test_certificate_->os_cert_handle()); |
+ |
+ return merged_array; |
+ } |
+ private: |
+ friend struct DefaultSingletonTraits<MacTrustedCertificates>; |
+ |
+ // Obtain an instance of MacTrustedCertificates via the singleton |
+ // interface. |
+ MacTrustedCertificates() : test_certificate_(NULL) { } |
+ |
+ // An X509Certificate object that may be appended to the list of |
+ // system trusted anchors. |
+ scoped_refptr<X509Certificate> test_certificate_; |
+ |
+ // The trusted cache may be accessed from multiple threads. |
+ mutable Lock lock_; |
+ |
+ DISALLOW_COPY_AND_ASSIGN(MacTrustedCertificates); |
+}; |
+ |
+void SetMacTestCertificate(X509Certificate* cert) { |
+ Singleton<MacTrustedCertificates>::get()->SetTestCertificate(cert); |
+} |
+ |
+namespace { |
+ |
+typedef OSStatus (*SecTrustCopyExtendedResultFuncPtr)(SecTrustRef, |
+ CFDictionaryRef*); |
+ |
+int NetErrorFromOSStatus(OSStatus status) { |
+ switch (status) { |
+ case noErr: |
+ return OK; |
+ case errSecNotAvailable: |
+ case errSecNoCertificateModule: |
+ case errSecNoPolicyModule: |
+ return ERR_NOT_IMPLEMENTED; |
+ case errSecAuthFailed: |
+ return ERR_ACCESS_DENIED; |
+ default: |
+ LOG(ERROR) << "Unknown error " << status << " mapped to net::ERR_FAILED"; |
+ return ERR_FAILED; |
+ } |
+} |
+ |
+int CertStatusFromOSStatus(OSStatus status) { |
+ switch (status) { |
+ case noErr: |
+ return 0; |
+ |
+ case CSSMERR_TP_INVALID_ANCHOR_CERT: |
+ case CSSMERR_TP_NOT_TRUSTED: |
+ case CSSMERR_TP_INVALID_CERT_AUTHORITY: |
+ return CERT_STATUS_AUTHORITY_INVALID; |
+ |
+ case CSSMERR_TP_CERT_EXPIRED: |
+ case CSSMERR_TP_CERT_NOT_VALID_YET: |
+ // "Expired" and "not yet valid" collapse into a single status. |
+ return CERT_STATUS_DATE_INVALID; |
+ |
+ case CSSMERR_TP_CERT_REVOKED: |
+ case CSSMERR_TP_CERT_SUSPENDED: |
+ return CERT_STATUS_REVOKED; |
+ |
+ case CSSMERR_APPLETP_HOSTNAME_MISMATCH: |
+ return CERT_STATUS_COMMON_NAME_INVALID; |
+ |
+ case CSSMERR_APPLETP_CRL_NOT_FOUND: |
+ case CSSMERR_APPLETP_INCOMPLETE_REVOCATION_CHECK: |
+ case CSSMERR_APPLETP_OCSP_UNAVAILABLE: |
+ return CERT_STATUS_NO_REVOCATION_MECHANISM; |
+ |
+ case CSSMERR_APPLETP_CRL_NOT_TRUSTED: |
+ case CSSMERR_APPLETP_CRL_SERVER_DOWN: |
+ case CSSMERR_APPLETP_CRL_NOT_VALID_YET: |
+ case CSSMERR_APPLETP_NETWORK_FAILURE: |
+ case CSSMERR_APPLETP_OCSP_BAD_RESPONSE: |
+ case CSSMERR_APPLETP_OCSP_NO_SIGNER: |
+ case CSSMERR_APPLETP_OCSP_RESP_UNAUTHORIZED: |
+ case CSSMERR_APPLETP_OCSP_RESP_SIG_REQUIRED: |
+ case CSSMERR_APPLETP_OCSP_RESP_MALFORMED_REQ: |
+ case CSSMERR_APPLETP_OCSP_RESP_INTERNAL_ERR: |
+ case CSSMERR_APPLETP_OCSP_RESP_TRY_LATER: |
+ // We asked for a revocation check, but didn't get it. |
+ return CERT_STATUS_UNABLE_TO_CHECK_REVOCATION; |
+ |
+ default: |
+ // Failure was due to something Chromium doesn't define a |
+ // specific status for (such as basic constraints violation, or |
+ // unknown critical extension) |
+ return CERT_STATUS_INVALID; |
+ } |
+} |
+ |
+bool OverrideHostnameMismatch(const std::string& hostname, |
+ std::vector<std::string>* dns_names) { |
+ // SecTrustEvaluate() does not check dotted IP addresses. If |
+ // hostname is provided as, say, 127.0.0.1, then the error |
+ // CSSMERR_APPLETP_HOSTNAME_MISMATCH will always be returned, |
+ // even if the certificate contains 127.0.0.1 as one of its names. |
+ // We, however, want to allow that behavior. SecTrustEvaluate() |
+ // only checks for digits and dots when considering whether a |
+ // hostname is an IP address, so IPv6 and hex addresses go through |
+ // its normal comparison. |
+ bool is_dotted_ip = true; |
+ bool override_hostname_mismatch = false; |
+ for (std::string::const_iterator c = hostname.begin(); |
+ c != hostname.end() && is_dotted_ip; ++c) |
+ is_dotted_ip = (*c >= '0' && *c <= '9') || *c == '.'; |
+ if (is_dotted_ip) { |
+ for (std::vector<std::string>::const_iterator name = dns_names->begin(); |
+ name != dns_names->end() && !override_hostname_mismatch; ++name) |
+ override_hostname_mismatch = (*name == hostname); |
+ } |
+ return override_hostname_mismatch; |
+} |
+ |
+// Creates a SecPolicyRef for the given OID, with optional value. |
+OSStatus CreatePolicy(const CSSM_OID* policy_OID, |
+ void* option_data, |
+ size_t option_length, |
+ SecPolicyRef* policy) { |
+ SecPolicySearchRef search; |
+ OSStatus err = SecPolicySearchCreate(CSSM_CERT_X_509v3, policy_OID, NULL, |
+ &search); |
+ if (err) |
+ return err; |
+ err = SecPolicySearchCopyNext(search, policy); |
+ CFRelease(search); |
+ if (err) |
+ return err; |
+ |
+ if (option_data) { |
+ CSSM_DATA options_data = { |
+ option_length, |
+ reinterpret_cast<uint8_t*>(option_data) |
+ }; |
+ err = SecPolicySetValue(*policy, &options_data); |
+ if (err) { |
+ CFRelease(*policy); |
+ return err; |
+ } |
+ } |
+ return noErr; |
+} |
+ |
+} // namespace |
+ |
+namespace x509_chain { |
+ |
+int VerifySSLServer(X509Certificate* certificate, const std::string& hostname, |
+ int flags, CertVerifyResult* verify_result) { |
+ verify_result->Reset(); |
+ if (!certificate || !certificate->os_cert_handle()) |
+ return ERR_UNEXPECTED; |
+ |
+ // Create an SSL SecPolicyRef, and configure it to perform hostname |
+ // validation. The hostname check does 99% of what we want, with the |
+ // exception of dotted IPv4 addreses, which we handle ourselves below. |
+ CSSM_APPLE_TP_SSL_OPTIONS tp_ssl_options = { |
+ CSSM_APPLE_TP_SSL_OPTS_VERSION, |
+ hostname.size(), |
+ hostname.data(), |
+ 0 |
+ }; |
+ SecPolicyRef ssl_policy; |
+ OSStatus status = CreatePolicy(&CSSMOID_APPLE_TP_SSL, |
+ &tp_ssl_options, |
+ sizeof(tp_ssl_options), |
+ &ssl_policy); |
+ if (status) |
+ return NetErrorFromOSStatus(status); |
+ scoped_cftyperef<SecPolicyRef> scoped_ssl_policy(ssl_policy); |
+ |
+ // Create and configure a SecTrustRef, which takes our certificate(s) |
+ // and our SSL SecPolicyRef. SecTrustCreateWithCertificates() takes an |
+ // array of certificates, the first of which is the certificate we're |
+ // verifying, and the subsequent (optional) certificates are used for |
+ // chain building. |
+ scoped_cftyperef<CFArrayRef> cert_list( |
+ certificate->CreateOSCertListHandle()); |
+ |
+ SecTrustRef trust_ref = NULL; |
+ status = SecTrustCreateWithCertificates(cert_list, ssl_policy, &trust_ref); |
+ if (status) |
+ return NetErrorFromOSStatus(status); |
+ scoped_cftyperef<SecTrustRef> scoped_trust_ref(trust_ref); |
+ |
+ // Set the trusted anchor certificates for the SecTrustRef by merging the |
+ // system trust anchors and the test root certificate. |
+ CFArrayRef anchor_array = |
+ Singleton<MacTrustedCertificates>::get()->CopyTrustedCertificateArray(); |
+ scoped_cftyperef<CFArrayRef> scoped_anchor_array(anchor_array); |
+ if (anchor_array) { |
+ status = SecTrustSetAnchorCertificates(trust_ref, anchor_array); |
+ if (status) |
+ return NetErrorFromOSStatus(status); |
+ } |
+ |
+ if (flags & VERIFY_REV_CHECKING_ENABLED) { |
+ // When called with VERIFY_REV_CHECKING_ENABLED, we ask SecTrustEvaluate() |
+ // to apply OCSP and CRL checking, but we're still subject to the global |
+ // settings, which are configured in the Keychain Access application (in |
+ // the Certificates tab of the Preferences dialog). If the user has |
+ // revocation disabled (which is the default), then we will get |
+ // kSecTrustResultRecoverableTrustFailure back from SecTrustEvaluate() |
+ // with one of a number of sub error codes indicating that revocation |
+ // checking did not occur. In that case, we'll set our own result to include |
+ // CERT_STATUS_UNABLE_TO_CHECK_REVOCATION. |
+ // |
+ // NOTE: This does not apply to EV certificates, which always get |
+ // revocation checks regardless of the global settings. |
+ verify_result->cert_status |= CERT_STATUS_REV_CHECKING_ENABLED; |
+ CSSM_APPLE_TP_ACTION_DATA tp_action_data = { CSSM_APPLE_TP_ACTION_VERSION }; |
+ tp_action_data.ActionFlags = CSSM_TP_ACTION_REQUIRE_REV_PER_CERT; |
+ CFDataRef action_data_ref = |
+ CFDataCreate(NULL, reinterpret_cast<UInt8*>(&tp_action_data), |
+ sizeof(tp_action_data)); |
+ if (!action_data_ref) |
+ return ERR_OUT_OF_MEMORY; |
+ scoped_cftyperef<CFDataRef> scoped_action_data_ref(action_data_ref); |
+ status = SecTrustSetParameters(trust_ref, CSSM_TP_ACTION_DEFAULT, |
+ action_data_ref); |
+ if (status) |
+ return NetErrorFromOSStatus(status); |
+ } |
+ |
+ // Verify the certificate. A non-zero result from SecTrustGetResult() |
+ // indicates that some fatal error occurred and the chain couldn't be |
+ // processed, not that the chain contains no errors. We need to examine the |
+ // output of SecTrustGetResult() to determine that. |
+ SecTrustResultType trust_result; |
+ status = SecTrustEvaluate(trust_ref, &trust_result); |
+ if (status) |
+ return NetErrorFromOSStatus(status); |
+ CFArrayRef completed_chain = NULL; |
+ CSSM_TP_APPLE_EVIDENCE_INFO* chain_info; |
+ status = SecTrustGetResult(trust_ref, &trust_result, &completed_chain, |
+ &chain_info); |
+ if (status) |
+ return NetErrorFromOSStatus(status); |
+ scoped_cftyperef<CFArrayRef> scoped_completed_chain(completed_chain); |
+ |
+ // Evaluate the results |
+ OSStatus cssm_result; |
+ bool got_certificate_error = false; |
+ switch (trust_result) { |
+ case kSecTrustResultUnspecified: |
+ case kSecTrustResultProceed: |
+ // Certificate chain is valid and trusted ("unspecified" indicates that |
+ // the user has not explicitly set a trust setting) |
+ break; |
+ |
+ case kSecTrustResultDeny: |
+ case kSecTrustResultConfirm: |
+ // Certificate chain is explicitly untrusted. For kSecTrustResultConfirm, |
+ // we're following what Secure Transport does and treating it as |
+ // "deny". |
+ verify_result->cert_status |= CERT_STATUS_AUTHORITY_INVALID; |
+ break; |
+ |
+ case kSecTrustResultRecoverableTrustFailure: |
+ // Certificate chain has a failure that can be overridden by the user. |
+ status = SecTrustGetCssmResultCode(trust_ref, &cssm_result); |
+ if (status) |
+ return NetErrorFromOSStatus(status); |
+ switch (cssm_result) { |
+ case CSSMERR_TP_NOT_TRUSTED: |
+ case CSSMERR_TP_INVALID_ANCHOR_CERT: |
+ verify_result->cert_status |= CERT_STATUS_AUTHORITY_INVALID; |
+ break; |
+ case CSSMERR_TP_CERT_EXPIRED: |
+ case CSSMERR_TP_CERT_NOT_VALID_YET: |
+ verify_result->cert_status |= CERT_STATUS_DATE_INVALID; |
+ break; |
+ case CSSMERR_TP_CERT_REVOKED: |
+ case CSSMERR_TP_CERT_SUSPENDED: |
+ verify_result->cert_status |= CERT_STATUS_REVOKED; |
+ break; |
+ default: |
+ // Look for specific per-certificate errors below. |
+ break; |
+ } |
+ // Walk the chain of error codes in the CSSM_TP_APPLE_EVIDENCE_INFO |
+ // structure which can catch multiple errors from each certificate. |
+ for (CFIndex index = 0, chain_count = CFArrayGetCount(completed_chain); |
+ index < chain_count; ++index) { |
+ if (chain_info[index].StatusBits & CSSM_CERT_STATUS_EXPIRED || |
+ chain_info[index].StatusBits & CSSM_CERT_STATUS_NOT_VALID_YET) |
+ verify_result->cert_status |= CERT_STATUS_DATE_INVALID; |
+ for (uint32 status_code_index = 0; |
+ status_code_index < chain_info[index].NumStatusCodes; |
+ ++status_code_index) { |
+ got_certificate_error = true; |
+ int cert_status = CertStatusFromOSStatus( |
+ chain_info[index].StatusCodes[status_code_index]); |
+ if (cert_status == CERT_STATUS_COMMON_NAME_INVALID) { |
+ std::vector<std::string> names; |
+ certificate->GetDNSNames(&names); |
+ if (OverrideHostnameMismatch(hostname, &names)) { |
+ cert_status = 0; |
+ } |
+ } |
+ verify_result->cert_status |= cert_status; |
+ } |
+ } |
+ // Be paranoid and ensure that we recorded at least one certificate |
+ // status on receiving kSecTrustResultRecoverableTrustFailure. The |
+ // call to SecTrustGetCssmResultCode() should pick up when the chain |
+ // is not trusted and the loop through CSSM_TP_APPLE_EVIDENCE_INFO |
+ // should pick up everything else, but let's be safe. |
+ if (!verify_result->cert_status && !got_certificate_error) { |
+ verify_result->cert_status |= CERT_STATUS_INVALID; |
+ NOTREACHED(); |
+ } |
+ break; |
+ |
+ default: |
+ status = SecTrustGetCssmResultCode(trust_ref, &cssm_result); |
+ if (status) |
+ return NetErrorFromOSStatus(status); |
+ verify_result->cert_status |= CertStatusFromOSStatus(cssm_result); |
+ if (!verify_result->cert_status) { |
+ verify_result->cert_status |= CERT_STATUS_INVALID; |
+ } |
+ break; |
+ } |
+ |
+ // TODO(wtc): Suppress CERT_STATUS_NO_REVOCATION_MECHANISM for now to be |
+ // compatible with Windows, which in turn implements this behavior to be |
+ // compatible with WinHTTP, which doesn't report this error (bug 3004). |
+ verify_result->cert_status &= ~CERT_STATUS_NO_REVOCATION_MECHANISM; |
+ |
+ if (IsCertStatusError(verify_result->cert_status)) |
+ return MapCertStatusToNetError(verify_result->cert_status); |
+ |
+ if (flags & VERIFY_EV_CERT) { |
+ // Determine the certificate's EV status using SecTrustCopyExtendedResult(), |
+ // which we need to look up because the function wasn't added until |
+ // Mac OS X 10.5.7. |
+ CFBundleRef bundle = |
+ CFBundleGetBundleWithIdentifier(CFSTR("com.apple.security")); |
+ if (bundle) { |
+ SecTrustCopyExtendedResultFuncPtr copy_extended_result = |
+ reinterpret_cast<SecTrustCopyExtendedResultFuncPtr>( |
+ CFBundleGetFunctionPointerForName(bundle, |
+ CFSTR("SecTrustCopyExtendedResult"))); |
+ if (copy_extended_result) { |
+ CFDictionaryRef ev_dict = NULL; |
+ status = copy_extended_result(trust_ref, &ev_dict); |
+ if (!status && ev_dict) { |
+ // The returned dictionary contains the EV organization name from the |
+ // server certificate, which we don't need at this point (and we |
+ // have other ways to access, anyway). All we care is that |
+ // SecTrustCopyExtendedResult() returned noErr and a non-NULL |
+ // dictionary. |
+ CFRelease(ev_dict); |
+ verify_result->cert_status |= CERT_STATUS_IS_EV; |
+ } |
+ } |
+ } |
+ } |
+ |
+ return OK; |
+} |
+ |
+} // namespace x509_chain |
+ |
+} // namespace net |