// Copyright (c) 2012 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_certificate.h" #include #include #include #include #include #include "base/lazy_instance.h" #include "base/logging.h" #include "base/mac/mac_logging.h" #include "base/mac/scoped_cftyperef.h" #include "base/memory/singleton.h" #include "base/pickle.h" #include "base/sha1.h" #include "base/synchronization/lock.h" #include "base/sys_string_conversions.h" #include "crypto/cssm_init.h" #include "crypto/mac_security_services_lock.h" #include "crypto/nss_util.h" #include "crypto/rsa_private_key.h" #include "net/base/x509_util_mac.h" #include "third_party/nss/mozilla/security/nss/lib/certdb/cert.h" using base::mac::ScopedCFTypeRef; using base::Time; namespace net { namespace { void GetCertDistinguishedName( const x509_util::CSSMCachedCertificate& cached_cert, const CSSM_OID* oid, CertPrincipal* result) { x509_util::CSSMFieldValue distinguished_name; OSStatus status = cached_cert.GetField(oid, &distinguished_name); if (status || !distinguished_name.field()) return; result->ParseDistinguishedName(distinguished_name.field()->Data, distinguished_name.field()->Length); } void GetCertDateForOID(const x509_util::CSSMCachedCertificate& cached_cert, const CSSM_OID* oid, Time* result) { *result = Time::Time(); x509_util::CSSMFieldValue field; OSStatus status = cached_cert.GetField(oid, &field); if (status) return; const CSSM_X509_TIME* x509_time = field.GetAs(); if (x509_time->timeType != BER_TAG_UTC_TIME && x509_time->timeType != BER_TAG_GENERALIZED_TIME) { LOG(ERROR) << "Unsupported date/time format " << x509_time->timeType; return; } base::StringPiece time_string( reinterpret_cast(x509_time->time.Data), x509_time->time.Length); CertDateFormat format = x509_time->timeType == BER_TAG_UTC_TIME ? CERT_DATE_FORMAT_UTC_TIME : CERT_DATE_FORMAT_GENERALIZED_TIME; if (!ParseCertificateDate(time_string, format, result)) LOG(ERROR) << "Invalid certificate date/time " << time_string; } std::string GetCertSerialNumber( const x509_util::CSSMCachedCertificate& cached_cert) { x509_util::CSSMFieldValue serial_number; OSStatus status = cached_cert.GetField(&CSSMOID_X509V1SerialNumber, &serial_number); if (status || !serial_number.field()) return std::string(); return std::string( reinterpret_cast(serial_number.field()->Data), serial_number.field()->Length); } // Gets the issuer for a given cert, starting with the cert itself and // including the intermediate and finally root certificates (if any). // This function calls SecTrust but doesn't actually pay attention to the trust // result: it shouldn't be used to determine trust, just to traverse the chain. // Caller is responsible for releasing the value stored into *out_cert_chain. OSStatus CopyCertChain(SecCertificateRef cert_handle, CFArrayRef* out_cert_chain) { DCHECK(cert_handle); DCHECK(out_cert_chain); // Create an SSL policy ref configured for client cert evaluation. SecPolicyRef ssl_policy; OSStatus result = x509_util::CreateSSLClientPolicy(&ssl_policy); if (result) return result; ScopedCFTypeRef scoped_ssl_policy(ssl_policy); // Create a SecTrustRef. ScopedCFTypeRef input_certs(CFArrayCreate( NULL, const_cast(reinterpret_cast(&cert_handle)), 1, &kCFTypeArrayCallBacks)); SecTrustRef trust_ref = NULL; { base::AutoLock lock(crypto::GetMacSecurityServicesLock()); result = SecTrustCreateWithCertificates(input_certs, ssl_policy, &trust_ref); } if (result) return result; ScopedCFTypeRef trust(trust_ref); // Evaluate trust, which creates the cert chain. SecTrustResultType status; CSSM_TP_APPLE_EVIDENCE_INFO* status_chain; { base::AutoLock lock(crypto::GetMacSecurityServicesLock()); result = SecTrustEvaluate(trust, &status); } if (result) return result; { base::AutoLock lock(crypto::GetMacSecurityServicesLock()); result = SecTrustGetResult(trust, &status, out_cert_chain, &status_chain); } return result; } // Returns true if |purpose| is listed as allowed in |usage|. This // function also considers the "Any" purpose. If the attribute is // present and empty, we return false. bool ExtendedKeyUsageAllows(const CE_ExtendedKeyUsage* usage, const CSSM_OID* purpose) { for (unsigned p = 0; p < usage->numPurposes; ++p) { if (CSSMOIDEqual(&usage->purposes[p], purpose)) return true; if (CSSMOIDEqual(&usage->purposes[p], &CSSMOID_ExtendedKeyUsageAny)) return true; } return false; } // Test that a given |cert_handle| is actually a valid X.509 certificate, and // return true if it is. // // On OS X, SecCertificateCreateFromData() does not return any errors if // called with invalid data, as long as data is present. The actual decoding // of the certificate does not happen until an API that requires a CSSM // handle is called. While SecCertificateGetCLHandle is the most likely // candidate, as it performs the parsing, it does not check whether the // parsing was actually successful. Instead, SecCertificateGetSubject is // used (supported since 10.3), as a means to check that the certificate // parsed as a valid X.509 certificate. bool IsValidOSCertHandle(SecCertificateRef cert_handle) { const CSSM_X509_NAME* sanity_check = NULL; OSStatus status = SecCertificateGetSubject(cert_handle, &sanity_check); return status == noErr && sanity_check; } // Parses |data| of length |length|, attempting to decode it as the specified // |format|. If |data| is in the specified format, any certificates contained // within are stored into |output|. void AddCertificatesFromBytes(const char* data, size_t length, SecExternalFormat format, X509Certificate::OSCertHandles* output) { SecExternalFormat input_format = format; ScopedCFTypeRef local_data(CFDataCreateWithBytesNoCopy( kCFAllocatorDefault, reinterpret_cast(data), length, kCFAllocatorNull)); CFArrayRef items = NULL; OSStatus status; { base::AutoLock lock(crypto::GetMacSecurityServicesLock()); status = SecKeychainItemImport(local_data, NULL, &input_format, NULL, 0, NULL, NULL, &items); } if (status) { OSSTATUS_DLOG(WARNING, status) << "Unable to import items from data of length " << length; return; } ScopedCFTypeRef scoped_items(items); CFTypeID cert_type_id = SecCertificateGetTypeID(); for (CFIndex i = 0; i < CFArrayGetCount(items); ++i) { SecKeychainItemRef item = reinterpret_cast( const_cast(CFArrayGetValueAtIndex(items, i))); // While inputFormat implies only certificates will be imported, if/when // other formats (eg: PKCS#12) are supported, this may also include // private keys or other items types, so filter appropriately. if (CFGetTypeID(item) == cert_type_id) { SecCertificateRef cert = reinterpret_cast(item); // OS X ignores |input_format| if it detects that |local_data| is PEM // encoded, attempting to decode data based on internal rules for PEM // block headers. If a PKCS#7 blob is encoded with a PEM block of // CERTIFICATE, OS X 10.5 will return a single, invalid certificate // based on the decoded data. If this happens, the certificate should // not be included in |output|. Because |output| is empty, // CreateCertificateListfromBytes will use PEMTokenizer to decode the // data. When called again with the decoded data, OS X will honor // |input_format|, causing decode to succeed. On OS X 10.6, the data // is properly decoded as a PKCS#7, whether PEM or not, which avoids // the need to fallback to internal decoding. if (IsValidOSCertHandle(cert)) { CFRetain(cert); output->push_back(cert); } } } } struct CSSMOIDString { const CSSM_OID* oid_; std::string string_; }; typedef std::vector CSSMOIDStringVector; bool CERTNameToCSSMOIDVector(CERTName* name, CSSMOIDStringVector* out_values) { struct OIDCSSMMap { SECOidTag sec_OID_; const CSSM_OID* cssm_OID_; }; const OIDCSSMMap kOIDs[] = { { SEC_OID_AVA_COMMON_NAME, &CSSMOID_CommonName }, { SEC_OID_AVA_COUNTRY_NAME, &CSSMOID_CountryName }, { SEC_OID_AVA_LOCALITY, &CSSMOID_LocalityName }, { SEC_OID_AVA_STATE_OR_PROVINCE, &CSSMOID_StateProvinceName }, { SEC_OID_AVA_STREET_ADDRESS, &CSSMOID_StreetAddress }, { SEC_OID_AVA_ORGANIZATION_NAME, &CSSMOID_OrganizationName }, { SEC_OID_AVA_ORGANIZATIONAL_UNIT_NAME, &CSSMOID_OrganizationalUnitName }, { SEC_OID_AVA_DN_QUALIFIER, &CSSMOID_DNQualifier }, { SEC_OID_RFC1274_UID, &CSSMOID_UniqueIdentifier }, { SEC_OID_PKCS9_EMAIL_ADDRESS, &CSSMOID_EmailAddress }, }; CERTRDN** rdns = name->rdns; for (size_t rdn = 0; rdns[rdn]; ++rdn) { CERTAVA** avas = rdns[rdn]->avas; for (size_t pair = 0; avas[pair] != 0; ++pair) { SECOidTag tag = CERT_GetAVATag(avas[pair]); if (tag == SEC_OID_UNKNOWN) { return false; } CSSMOIDString oidString; bool found_oid = false; for (size_t oid = 0; oid < ARRAYSIZE_UNSAFE(kOIDs); ++oid) { if (kOIDs[oid].sec_OID_ == tag) { SECItem* decode_item = CERT_DecodeAVAValue(&avas[pair]->value); if (!decode_item) return false; // TODO(wtc): Pass decode_item to CERT_RFC1485_EscapeAndQuote. std::string value(reinterpret_cast(decode_item->data), decode_item->len); oidString.oid_ = kOIDs[oid].cssm_OID_; oidString.string_ = value; out_values->push_back(oidString); SECITEM_FreeItem(decode_item, PR_TRUE); found_oid = true; break; } } if (!found_oid) { DLOG(ERROR) << "Unrecognized OID: " << tag; } } } return true; } class ScopedCertName { public: explicit ScopedCertName(CERTName* name) : name_(name) { } ~ScopedCertName() { if (name_) CERT_DestroyName(name_); } operator CERTName*() { return name_; } private: CERTName* name_; }; class ScopedEncodedCertResults { public: explicit ScopedEncodedCertResults(CSSM_TP_RESULT_SET* results) : results_(results) { } ~ScopedEncodedCertResults() { if (results_) { CSSM_ENCODED_CERT* encCert = reinterpret_cast(results_->Results); for (uint32 i = 0; i < results_->NumberOfResults; i++) { crypto::CSSMFree(encCert[i].CertBlob.Data); } } crypto::CSSMFree(results_->Results); crypto::CSSMFree(results_); } private: CSSM_TP_RESULT_SET* results_; }; } // namespace void X509Certificate::Initialize() { x509_util::CSSMCachedCertificate cached_cert; if (cached_cert.Init(cert_handle_) == CSSM_OK) { GetCertDistinguishedName(cached_cert, &CSSMOID_X509V1SubjectNameStd, &subject_); GetCertDistinguishedName(cached_cert, &CSSMOID_X509V1IssuerNameStd, &issuer_); GetCertDateForOID(cached_cert, &CSSMOID_X509V1ValidityNotBefore, &valid_start_); GetCertDateForOID(cached_cert, &CSSMOID_X509V1ValidityNotAfter, &valid_expiry_); serial_number_ = GetCertSerialNumber(cached_cert); } fingerprint_ = CalculateFingerprint(cert_handle_); ca_fingerprint_ = CalculateCAFingerprint(intermediate_ca_certs_); } // static X509Certificate* X509Certificate::CreateSelfSigned( crypto::RSAPrivateKey* key, const std::string& subject, uint32 serial_number, base::TimeDelta valid_duration) { DCHECK(key); DCHECK(!subject.empty()); if (valid_duration.InSeconds() > kuint32max) { LOG(ERROR) << "valid_duration too big " << valid_duration.InSeconds(); valid_duration = base::TimeDelta::FromSeconds(kuint32max); } // There is a comment in // http://www.opensource.apple.com/source/security_certtool/security_certtool-31828/src/CertTool.cpp // that serial_numbers being passed into CSSM_TP_SubmitCredRequest can't have // their high bit set. We will continue though and mask it out below. if (serial_number & 0x80000000) LOG(ERROR) << "serial_number has high bit set " << serial_number; // NSS is used to parse the subject string into a set of // CSSM_OID/string pairs. There doesn't appear to be a system routine for // parsing Distinguished Name strings. crypto::EnsureNSSInit(); CSSMOIDStringVector subject_name_oids; ScopedCertName subject_name( CERT_AsciiToName(const_cast(subject.c_str()))); if (!CERTNameToCSSMOIDVector(subject_name, &subject_name_oids)) { DLOG(ERROR) << "Unable to generate CSSMOIDMap from " << subject; return NULL; } // Convert the map of oid/string pairs into an array of // CSSM_APPLE_TP_NAME_OIDs. std::vector cssm_subject_names; for (CSSMOIDStringVector::iterator iter = subject_name_oids.begin(); iter != subject_name_oids.end(); ++iter) { CSSM_APPLE_TP_NAME_OID cssm_subject_name; cssm_subject_name.oid = iter->oid_; cssm_subject_name.string = iter->string_.c_str(); cssm_subject_names.push_back(cssm_subject_name); } if (cssm_subject_names.empty()) { DLOG(ERROR) << "cssm_subject_names.size() == 0. Input: " << subject; return NULL; } // Set up a certificate request. CSSM_APPLE_TP_CERT_REQUEST certReq; memset(&certReq, 0, sizeof(certReq)); certReq.cspHand = crypto::GetSharedCSPHandle(); certReq.clHand = crypto::GetSharedCLHandle(); // See comment about serial numbers above. certReq.serialNumber = serial_number & 0x7fffffff; certReq.numSubjectNames = cssm_subject_names.size(); certReq.subjectNames = &cssm_subject_names[0]; certReq.numIssuerNames = 0; // Root. certReq.issuerNames = NULL; certReq.issuerNameX509 = NULL; certReq.certPublicKey = key->public_key(); certReq.issuerPrivateKey = key->key(); // These are the Apple defaults. certReq.signatureAlg = CSSM_ALGID_SHA1WithRSA; certReq.signatureOid = CSSMOID_SHA1WithRSA; certReq.notBefore = 0; certReq.notAfter = static_cast(valid_duration.InSeconds()); certReq.numExtensions = 0; certReq.extensions = NULL; certReq.challengeString = NULL; CSSM_TP_REQUEST_SET reqSet; reqSet.NumberOfRequests = 1; reqSet.Requests = &certReq; CSSM_FIELD policyId; memset(&policyId, 0, sizeof(policyId)); policyId.FieldOid = CSSMOID_APPLE_TP_LOCAL_CERT_GEN; CSSM_TP_CALLERAUTH_CONTEXT callerAuthContext; memset(&callerAuthContext, 0, sizeof(callerAuthContext)); callerAuthContext.Policy.NumberOfPolicyIds = 1; callerAuthContext.Policy.PolicyIds = &policyId; CSSM_TP_HANDLE tp_handle = crypto::GetSharedTPHandle(); CSSM_DATA refId; memset(&refId, 0, sizeof(refId)); sint32 estTime; CSSM_RETURN crtn = CSSM_TP_SubmitCredRequest(tp_handle, NULL, CSSM_TP_AUTHORITY_REQUEST_CERTISSUE, &reqSet, &callerAuthContext, &estTime, &refId); if (crtn) { DLOG(ERROR) << "CSSM_TP_SubmitCredRequest failed " << crtn; return NULL; } CSSM_BOOL confirmRequired; CSSM_TP_RESULT_SET* resultSet = NULL; crtn = CSSM_TP_RetrieveCredResult(tp_handle, &refId, NULL, &estTime, &confirmRequired, &resultSet); ScopedEncodedCertResults scopedResults(resultSet); crypto::CSSMFree(refId.Data); if (crtn) { DLOG(ERROR) << "CSSM_TP_RetrieveCredResult failed " << crtn; return NULL; } if (confirmRequired) { // Potential leak here of resultSet. |confirmRequired| should never be // true. DLOG(ERROR) << "CSSM_TP_RetrieveCredResult required confirmation"; return NULL; } if (resultSet->NumberOfResults != 1) { DLOG(ERROR) << "Unexpected number of results: " << resultSet->NumberOfResults; return NULL; } CSSM_ENCODED_CERT* encCert = reinterpret_cast(resultSet->Results); ScopedCFTypeRef scoped_cert; SecCertificateRef certificate_ref = NULL; OSStatus os_status = SecCertificateCreateFromData(&encCert->CertBlob, encCert->CertType, encCert->CertEncoding, &certificate_ref); if (os_status != 0) { OSSTATUS_DLOG(ERROR, os_status) << "SecCertificateCreateFromData failed"; return NULL; } scoped_cert.reset(certificate_ref); return CreateFromHandle(scoped_cert, X509Certificate::OSCertHandles()); } void X509Certificate::GetSubjectAltName( std::vector* dns_names, std::vector* ip_addrs) const { if (dns_names) dns_names->clear(); if (ip_addrs) ip_addrs->clear(); x509_util::CSSMCachedCertificate cached_cert; OSStatus status = cached_cert.Init(cert_handle_); if (status) return; x509_util::CSSMFieldValue subject_alt_name; status = cached_cert.GetField(&CSSMOID_SubjectAltName, &subject_alt_name); if (status || !subject_alt_name.field()) return; const CSSM_X509_EXTENSION* cssm_ext = subject_alt_name.GetAs(); if (!cssm_ext || !cssm_ext->value.parsedValue) return; const CE_GeneralNames* alt_name = reinterpret_cast(cssm_ext->value.parsedValue); for (size_t name = 0; name < alt_name->numNames; ++name) { const CE_GeneralName& name_struct = alt_name->generalName[name]; const CSSM_DATA& name_data = name_struct.name; // DNSName and IPAddress are encoded as IA5String and OCTET STRINGs // respectively, both of which can be byte copied from // CSSM_DATA::data into the appropriate output vector. if (dns_names && name_struct.nameType == GNT_DNSName) { dns_names->push_back(std::string( reinterpret_cast(name_data.Data), name_data.Length)); } else if (ip_addrs && name_struct.nameType == GNT_IPAddress) { ip_addrs->push_back(std::string( reinterpret_cast(name_data.Data), name_data.Length)); } } } // static bool X509Certificate::GetDEREncoded(X509Certificate::OSCertHandle cert_handle, std::string* encoded) { CSSM_DATA der_data; if (SecCertificateGetData(cert_handle, &der_data) != noErr) return false; encoded->assign(reinterpret_cast(der_data.Data), der_data.Length); return true; } // static bool X509Certificate::IsSameOSCert(X509Certificate::OSCertHandle a, X509Certificate::OSCertHandle b) { DCHECK(a && b); if (a == b) return true; if (CFEqual(a, b)) return true; CSSM_DATA a_data, b_data; return SecCertificateGetData(a, &a_data) == noErr && SecCertificateGetData(b, &b_data) == noErr && a_data.Length == b_data.Length && memcmp(a_data.Data, b_data.Data, a_data.Length) == 0; } // static X509Certificate::OSCertHandle X509Certificate::CreateOSCertHandleFromBytes( const char* data, int length) { CSSM_DATA cert_data; cert_data.Data = const_cast(reinterpret_cast(data)); cert_data.Length = length; OSCertHandle cert_handle = NULL; OSStatus status = SecCertificateCreateFromData(&cert_data, CSSM_CERT_X_509v3, CSSM_CERT_ENCODING_DER, &cert_handle); if (status != noErr) return NULL; if (!IsValidOSCertHandle(cert_handle)) { CFRelease(cert_handle); return NULL; } return cert_handle; } // static X509Certificate::OSCertHandles X509Certificate::CreateOSCertHandlesFromBytes( const char* data, int length, Format format) { OSCertHandles results; switch (format) { case FORMAT_SINGLE_CERTIFICATE: { OSCertHandle handle = CreateOSCertHandleFromBytes(data, length); if (handle) results.push_back(handle); break; } case FORMAT_PKCS7: AddCertificatesFromBytes(data, length, kSecFormatPKCS7, &results); break; default: NOTREACHED() << "Certificate format " << format << " unimplemented"; break; } return results; } // static X509Certificate::OSCertHandle X509Certificate::DupOSCertHandle( OSCertHandle handle) { if (!handle) return NULL; return reinterpret_cast(const_cast(CFRetain(handle))); } // static void X509Certificate::FreeOSCertHandle(OSCertHandle cert_handle) { CFRelease(cert_handle); } // static SHA1HashValue X509Certificate::CalculateFingerprint( OSCertHandle cert) { SHA1HashValue sha1; memset(sha1.data, 0, sizeof(sha1.data)); CSSM_DATA cert_data; OSStatus status = SecCertificateGetData(cert, &cert_data); if (status) return sha1; DCHECK(cert_data.Data); DCHECK_NE(cert_data.Length, 0U); CC_SHA1(cert_data.Data, cert_data.Length, sha1.data); return sha1; } // static SHA1HashValue X509Certificate::CalculateCAFingerprint( const OSCertHandles& intermediates) { SHA1HashValue sha1; memset(sha1.data, 0, sizeof(sha1.data)); // The CC_SHA(3cc) man page says all CC_SHA1_xxx routines return 1, so // we don't check their return values. CC_SHA1_CTX sha1_ctx; CC_SHA1_Init(&sha1_ctx); CSSM_DATA cert_data; for (size_t i = 0; i < intermediates.size(); ++i) { OSStatus status = SecCertificateGetData(intermediates[i], &cert_data); if (status) return sha1; CC_SHA1_Update(&sha1_ctx, cert_data.Data, cert_data.Length); } CC_SHA1_Final(sha1.data, &sha1_ctx); return sha1; } bool X509Certificate::SupportsSSLClientAuth() const { x509_util::CSSMCachedCertificate cached_cert; OSStatus status = cached_cert.Init(cert_handle_); if (status) return false; // RFC5280 says to take the intersection of the two extensions. // // Our underlying crypto libraries don't expose // ClientCertificateType, so for now we will not support fixed // Diffie-Hellman mechanisms. For rsa_sign, we need the // digitalSignature bit. // // In particular, if a key has the nonRepudiation bit and not the // digitalSignature one, we will not offer it to the user. x509_util::CSSMFieldValue key_usage; status = cached_cert.GetField(&CSSMOID_KeyUsage, &key_usage); if (status == CSSM_OK && key_usage.field()) { const CSSM_X509_EXTENSION* ext = key_usage.GetAs(); const CE_KeyUsage* key_usage_value = reinterpret_cast(ext->value.parsedValue); if (!((*key_usage_value) & CE_KU_DigitalSignature)) return false; } status = cached_cert.GetField(&CSSMOID_ExtendedKeyUsage, &key_usage); if (status == CSSM_OK && key_usage.field()) { const CSSM_X509_EXTENSION* ext = key_usage.GetAs(); const CE_ExtendedKeyUsage* ext_key_usage = reinterpret_cast(ext->value.parsedValue); if (!ExtendedKeyUsageAllows(ext_key_usage, &CSSMOID_ClientAuth)) return false; } return true; } bool X509Certificate::IsIssuedBy( const std::vector& valid_issuers) { // Get the cert's issuer chain. CFArrayRef cert_chain = NULL; OSStatus result = CopyCertChain(os_cert_handle(), &cert_chain); if (result) return false; ScopedCFTypeRef scoped_cert_chain(cert_chain); // Check all the certs in the chain for a match. int n = CFArrayGetCount(cert_chain); for (int i = 0; i < n; ++i) { SecCertificateRef cert_handle = reinterpret_cast( const_cast(CFArrayGetValueAtIndex(cert_chain, i))); scoped_refptr cert(X509Certificate::CreateFromHandle( cert_handle, X509Certificate::OSCertHandles())); for (unsigned j = 0; j < valid_issuers.size(); j++) { if (cert->issuer().Matches(valid_issuers[j])) return true; } } return false; } // static bool X509Certificate::GetSSLClientCertificates( const std::string& server_domain, const std::vector& valid_issuers, CertificateList* certs) { ScopedCFTypeRef preferred_identity; if (!server_domain.empty()) { // See if there's an identity preference for this domain: ScopedCFTypeRef domain_str( base::SysUTF8ToCFStringRef("https://" + server_domain)); SecIdentityRef identity = NULL; // While SecIdentityCopyPreferences appears to take a list of CA issuers // to restrict the identity search to, within Security.framework the // argument is ignored and filtering unimplemented. See // SecIdentity.cpp in libsecurity_keychain, specifically // _SecIdentityCopyPreferenceMatchingName(). { base::AutoLock lock(crypto::GetMacSecurityServicesLock()); if (SecIdentityCopyPreference(domain_str, 0, NULL, &identity) == noErr) preferred_identity.reset(identity); } } // Now enumerate the identities in the available keychains. SecIdentitySearchRef search = NULL; OSStatus err; { base::AutoLock lock(crypto::GetMacSecurityServicesLock()); err = SecIdentitySearchCreate(NULL, CSSM_KEYUSE_SIGN, &search); } if (err) return false; ScopedCFTypeRef scoped_search(search); while (!err) { SecIdentityRef identity = NULL; { base::AutoLock lock(crypto::GetMacSecurityServicesLock()); err = SecIdentitySearchCopyNext(search, &identity); } if (err) break; ScopedCFTypeRef scoped_identity(identity); SecCertificateRef cert_handle; err = SecIdentityCopyCertificate(identity, &cert_handle); if (err != noErr) continue; ScopedCFTypeRef scoped_cert_handle(cert_handle); scoped_refptr cert( CreateFromHandle(cert_handle, OSCertHandles())); if (cert->HasExpired() || !cert->SupportsSSLClientAuth()) continue; // Skip duplicates (a cert may be in multiple keychains). const SHA1HashValue& fingerprint = cert->fingerprint(); unsigned i; for (i = 0; i < certs->size(); ++i) { if ((*certs)[i]->fingerprint().Equals(fingerprint)) break; } if (i < certs->size()) continue; bool is_preferred = preferred_identity && CFEqual(preferred_identity, identity); // Make sure the issuer matches valid_issuers, if given. if (!valid_issuers.empty() && !cert->IsIssuedBy(valid_issuers)) continue; // The cert passes, so add it to the vector. // If it's the preferred identity, add it at the start (so it'll be // selected by default in the UI.) if (is_preferred) certs->insert(certs->begin(), cert); else certs->push_back(cert); } if (err != errSecItemNotFound) { OSSTATUS_LOG(ERROR, err) << "SecIdentitySearch error"; return false; } return true; } CFArrayRef X509Certificate::CreateClientCertificateChain() const { // Initialize the result array with just the IdentityRef of the receiver: SecIdentityRef identity; OSStatus result; { base::AutoLock lock(crypto::GetMacSecurityServicesLock()); result = SecIdentityCreateWithCertificate(NULL, cert_handle_, &identity); } if (result) { OSSTATUS_LOG(ERROR, result) << "SecIdentityCreateWithCertificate error"; return NULL; } ScopedCFTypeRef chain( CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks)); CFArrayAppendValue(chain, identity); CFArrayRef cert_chain = NULL; result = CopyCertChain(cert_handle_, &cert_chain); ScopedCFTypeRef scoped_cert_chain(cert_chain); if (result) { OSSTATUS_LOG(ERROR, result) << "CreateIdentityCertificateChain error"; return chain.release(); } // Append the intermediate certs from SecTrust to the result array: if (cert_chain) { int chain_count = CFArrayGetCount(cert_chain); if (chain_count > 1) { CFArrayAppendArray(chain, cert_chain, CFRangeMake(1, chain_count - 1)); } } return chain.release(); } CFArrayRef X509Certificate::CreateOSCertChainForCert() const { CFMutableArrayRef cert_list = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks); if (!cert_list) return NULL; CFArrayAppendValue(cert_list, os_cert_handle()); for (size_t i = 0; i < intermediate_ca_certs_.size(); ++i) CFArrayAppendValue(cert_list, intermediate_ca_certs_[i]); return cert_list; } // static X509Certificate::OSCertHandle X509Certificate::ReadOSCertHandleFromPickle(PickleIterator* pickle_iter) { const char* data; int length; if (!pickle_iter->ReadData(&data, &length)) return NULL; return CreateOSCertHandleFromBytes(data, length); } // static bool X509Certificate::WriteOSCertHandleToPickle(OSCertHandle cert_handle, Pickle* pickle) { CSSM_DATA cert_data; OSStatus status = SecCertificateGetData(cert_handle, &cert_data); if (status) return false; return pickle->WriteData(reinterpret_cast(cert_data.Data), cert_data.Length); } // static void X509Certificate::GetPublicKeyInfo(OSCertHandle cert_handle, size_t* size_bits, PublicKeyType* type) { // Since we might fail, set the output parameters to default values first. *type = kPublicKeyTypeUnknown; *size_bits = 0; SecKeyRef key; OSStatus status = SecCertificateCopyPublicKey(cert_handle, &key); if (status) { NOTREACHED() << "SecCertificateCopyPublicKey failed: " << status; return; } ScopedCFTypeRef scoped_key(key); const CSSM_KEY* cssm_key; status = SecKeyGetCSSMKey(key, &cssm_key); if (status) { NOTREACHED() << "SecKeyGetCSSMKey failed: " << status; return; } *size_bits = cssm_key->KeyHeader.LogicalKeySizeInBits; switch (cssm_key->KeyHeader.AlgorithmId) { case CSSM_ALGID_RSA: *type = kPublicKeyTypeRSA; break; case CSSM_ALGID_DSA: *type = kPublicKeyTypeDSA; break; case CSSM_ALGID_ECDSA: *type = kPublicKeyTypeECDSA; break; case CSSM_ALGID_DH: *type = kPublicKeyTypeDH; break; default: *type = kPublicKeyTypeUnknown; *size_bits = 0; break; } } } // namespace net