| 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
|
|
|