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

Unified 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. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
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

Powered by Google App Engine
This is Rietveld 408576698