summaryrefslogtreecommitdiffstats
path: root/net/base
diff options
context:
space:
mode:
authorhawk@chromium.org <hawk@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2009-08-27 17:49:41 +0000
committerhawk@chromium.org <hawk@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2009-08-27 17:49:41 +0000
commit010e27ec98de24f68648b8c3ac68f3408f0578c0 (patch)
tree5d598c11cc366a4dd778f9d5aaed0ab62b082d05 /net/base
parent7cb43d53404c33f90398ba6217dc9645400e9c8e (diff)
downloadchromium_src-010e27ec98de24f68648b8c3ac68f3408f0578c0.zip
chromium_src-010e27ec98de24f68648b8c3ac68f3408f0578c0.tar.gz
chromium_src-010e27ec98de24f68648b8c3ac68f3408f0578c0.tar.bz2
Enable SSLClientSocketTest unit tests on Mac OS X by implementing our own certificate validation code. This gives us proper hostname matching, multiple error codes (e.g., before a certificate could be marked as expired or untrusted, but not both), revocation checking, and EV certificate checking.
BUG=19286,10910,14733 TEST=https://www.paypal.com should work without warning. https://paypal.com should get a warning about a hostname mismatch. https://test-ssev.verisign.com:1443/test-SSEV-expired-verisign.html should give a warning about an expired certificate. Review URL: http://codereview.chromium.org/174102 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@24625 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'net/base')
-rw-r--r--net/base/x509_certificate.cc13
-rw-r--r--net/base/x509_certificate.h12
-rw-r--r--net/base/x509_certificate_mac.cc408
3 files changed, 417 insertions, 16 deletions
diff --git a/net/base/x509_certificate.cc b/net/base/x509_certificate.cc
index 7a5a669..e8db7c7 100644
--- a/net/base/x509_certificate.cc
+++ b/net/base/x509_certificate.cc
@@ -169,7 +169,11 @@ X509Certificate* X509Certificate::CreateFromBytes(const char* data,
}
X509Certificate::X509Certificate(OSCertHandle cert_handle, Source source)
- : cert_handle_(cert_handle), source_(source) {
+ : cert_handle_(cert_handle),
+#if defined(OS_MACOSX)
+ intermediate_ca_certs_(NULL),
+#endif
+ source_(source) {
Initialize();
}
@@ -182,6 +186,9 @@ X509Certificate::X509Certificate(const std::string& subject,
valid_start_(start_date),
valid_expiry_(expiration_date),
cert_handle_(NULL),
+#if defined(OS_MACOSX)
+ intermediate_ca_certs_(NULL),
+#endif
source_(SOURCE_UNUSED) {
memset(fingerprint_.data, 0, sizeof(fingerprint_.data));
}
@@ -191,6 +198,10 @@ X509Certificate::~X509Certificate() {
X509Certificate::Cache::GetInstance()->Remove(this);
if (cert_handle_)
FreeOSCertHandle(cert_handle_);
+#if defined(OS_MACOSX)
+ if (intermediate_ca_certs_)
+ CFRelease(intermediate_ca_certs_);
+#endif
}
bool X509Certificate::HasExpired() const {
diff --git a/net/base/x509_certificate.h b/net/base/x509_certificate.h
index 1e2419e..4ae6554 100644
--- a/net/base/x509_certificate.h
+++ b/net/base/x509_certificate.h
@@ -209,6 +209,12 @@ class X509Certificate : public base::RefCountedThreadSafe<X509Certificate> {
// now.
bool HasExpired() const;
+#if defined(OS_MACOSX)
+ // Adds an untrusted intermediate certificate that may be needed for
+ // chain building.
+ void AddIntermediateCertificate(SecCertificateRef cert);
+#endif
+
// Verifies the certificate against the given hostname. Returns OK if
// successful or an error code upon failure.
//
@@ -299,6 +305,12 @@ class X509Certificate : public base::RefCountedThreadSafe<X509Certificate> {
// A handle to the certificate object in the underlying crypto library.
OSCertHandle cert_handle_;
+#if defined(OS_MACOSX)
+ // Untrusted intermediate certificates associated with this certificate
+ // that may be needed for chain building.
+ CFMutableArrayRef intermediate_ca_certs_;
+#endif
+
// Where the certificate comes from.
Source source_;
diff --git a/net/base/x509_certificate_mac.cc b/net/base/x509_certificate_mac.cc
index 2e75489..d55a770 100644
--- a/net/base/x509_certificate_mac.cc
+++ b/net/base/x509_certificate_mac.cc
@@ -7,23 +7,170 @@
#include <CommonCrypto/CommonDigest.h>
#include <time.h>
+#include "base/scoped_cftyperef.h"
#include "base/logging.h"
#include "base/pickle.h"
#include "net/base/cert_status_flags.h"
-#include "net/base/ev_root_ca_metadata.h"
+#include "net/base/cert_verify_result.h"
#include "net/base/net_errors.h"
using base::Time;
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*);
+
inline bool CSSMOIDEqual(const CSSM_OID* oid1, const CSSM_OID* oid2) {
return oid1->Length == oid2->Length &&
(memcmp(oid1->Data, oid2->Data, oid1->Length) == 0);
}
+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:
+ 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_UNAVAILABLE:
+ case CSSMERR_APPLETP_OCSP_BAD_RESPONSE:
+ 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;
+}
+
void ParsePrincipal(const CSSM_X509_NAME* name,
X509Certificate::Principal* principal) {
std::vector<std::string> common_names, locality_names, state_names,
@@ -262,26 +409,257 @@ void X509Certificate::GetDNSNames(std::vector<std::string>* dns_names) const {
dns_names->push_back(subject_.common_name);
}
-int X509Certificate::Verify(const std::string& hostname,
- int flags, CertVerifyResult* verify_result) const {
- NOTIMPLEMENTED();
- return ERR_NOT_IMPLEMENTED;
+int X509Certificate::Verify(const std::string& hostname, int flags,
+ CertVerifyResult* verify_result) const {
+ verify_result->Reset();
+
+ // 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.
+ SecPolicySearchRef ssl_policy_search_ref = NULL;
+ OSStatus status = SecPolicySearchCreate(CSSM_CERT_X_509v3,
+ &CSSMOID_APPLE_TP_SSL,
+ NULL,
+ &ssl_policy_search_ref);
+ if (status)
+ return NetErrorFromOSStatus(status);
+ scoped_cftyperef<SecPolicySearchRef>
+ scoped_ssl_policy_search_ref(ssl_policy_search_ref);
+ SecPolicyRef ssl_policy = NULL;
+ status = SecPolicySearchCopyNext(ssl_policy_search_ref, &ssl_policy);
+ if (status)
+ return NetErrorFromOSStatus(status);
+ scoped_cftyperef<SecPolicyRef> scoped_ssl_policy(ssl_policy);
+ CSSM_APPLE_TP_SSL_OPTIONS tp_ssl_options = { CSSM_APPLE_TP_SSL_OPTS_VERSION };
+ tp_ssl_options.ServerName = hostname.data();
+ tp_ssl_options.ServerNameLen = hostname.size();
+ CSSM_DATA tp_ssl_options_data_value;
+ tp_ssl_options_data_value.Data = reinterpret_cast<uint8*>(&tp_ssl_options);
+ tp_ssl_options_data_value.Length = sizeof(tp_ssl_options);
+ status = SecPolicySetValue(ssl_policy, &tp_ssl_options_data_value);
+ if (status)
+ return NetErrorFromOSStatus(status);
+
+ // 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.
+ CFMutableArrayRef cert_array = CFArrayCreateMutable(kCFAllocatorDefault, 0,
+ &kCFTypeArrayCallBacks);
+ if (!cert_array)
+ return ERR_OUT_OF_MEMORY;
+ scoped_cftyperef<CFArrayRef> scoped_cert_array(cert_array);
+ CFArrayAppendValue(cert_array, cert_handle_);
+ if (intermediate_ca_certs_) {
+ CFIndex intermediate_count = CFArrayGetCount(intermediate_ca_certs_);
+ for (CFIndex i = 0; i < intermediate_count; ++i) {
+ SecCertificateRef intermediate_cert = static_cast<SecCertificateRef>(
+ const_cast<void*>(CFArrayGetValueAtIndex(intermediate_ca_certs_, i)));
+ CFArrayAppendValue(cert_array, intermediate_cert);
+ }
+ }
+
+ SecTrustRef trust_ref = NULL;
+ status = SecTrustCreateWithCertificates(cert_array, 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 that 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);
+ } else {
+ // EV requires revocation checking.
+ flags &= ~VERIFY_EV_CERT;
+ }
+
+ // 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(cssm_result);
+ if (cert_status == CERT_STATUS_COMMON_NAME_INVALID) {
+ std::vector<std::string> names;
+ 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;
+ }
+
+ 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;
}
-// Returns true if the certificate is an extended-validation certificate.
-//
-// The certificate has already been verified by the HTTP library. cert_status
-// represents the result of that verification. This function performs
-// additional checks of the certificatePolicies extensions of the certificates
-// in the certificate chain according to Section 7 (pp. 11-12) of the EV
-// Certificate Guidelines Version 1.0 at
-// http://cabforum.org/EV_Certificate_Guidelines.pdf.
bool X509Certificate::VerifyEV() const {
- // TODO(avi): implement this
- NOTIMPLEMENTED();
+ // We don't call this private method, but we do need to implement it because
+ // it's defined in x509_certificate.h. We perform EV checking in the
+ // Verify() above.
+ NOTREACHED();
return false;
}
+void X509Certificate::AddIntermediateCertificate(SecCertificateRef cert) {
+ if (cert) {
+ if (!intermediate_ca_certs_) {
+ intermediate_ca_certs_ = CFArrayCreateMutable(kCFAllocatorDefault, 0,
+ &kCFTypeArrayCallBacks);
+ }
+ if (intermediate_ca_certs_) {
+ CFArrayAppendValue(intermediate_ca_certs_, cert);
+ }
+ }
+}
+
// static
X509Certificate::OSCertHandle X509Certificate::CreateOSCertHandleFromBytes(
const char* data, int length) {