| // 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 <CommonCrypto/CommonDigest.h> |
| #include <CoreServices/CoreServices.h> |
| #include <Security/Security.h> |
| #include <time.h> |
| |
| #include <vector> |
| |
| #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<CSSM_X509_TIME>(); |
| 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<const char*>(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<const char*>(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<SecPolicyRef> scoped_ssl_policy(ssl_policy); |
| |
| // Create a SecTrustRef. |
| ScopedCFTypeRef<CFArrayRef> input_certs(CFArrayCreate( |
| NULL, const_cast<const void**>(reinterpret_cast<void**>(&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<SecTrustRef> 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<CFDataRef> local_data(CFDataCreateWithBytesNoCopy( |
| kCFAllocatorDefault, reinterpret_cast<const UInt8*>(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<CFArrayRef> scoped_items(items); |
| CFTypeID cert_type_id = SecCertificateGetTypeID(); |
| |
| for (CFIndex i = 0; i < CFArrayGetCount(items); ++i) { |
| SecKeychainItemRef item = reinterpret_cast<SecKeychainItemRef>( |
| const_cast<void*>(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<SecCertificateRef>(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<CSSMOIDString> 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<char*>(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<CSSM_ENCODED_CERT*>(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<char*>(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_APPLE_TP_NAME_OID> 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<uint32>(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<CSSM_ENCODED_CERT*>(resultSet->Results); |
| ScopedCFTypeRef<SecCertificateRef> 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<std::string>* dns_names, |
| std::vector<std::string>* 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<CSSM_X509_EXTENSION>(); |
| if (!cssm_ext || !cssm_ext->value.parsedValue) |
| return; |
| const CE_GeneralNames* alt_name = |
| reinterpret_cast<const CE_GeneralNames*>(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<const char*>(name_data.Data), |
| name_data.Length)); |
| } else if (ip_addrs && name_struct.nameType == GNT_IPAddress) { |
| ip_addrs->push_back(std::string( |
| reinterpret_cast<const char*>(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<char*>(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<uint8*>(reinterpret_cast<const uint8*>(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<OSCertHandle>(const_cast<void*>(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<CSSM_X509_EXTENSION>(); |
| const CE_KeyUsage* key_usage_value = |
| reinterpret_cast<const CE_KeyUsage*>(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<CSSM_X509_EXTENSION>(); |
| const CE_ExtendedKeyUsage* ext_key_usage = |
| reinterpret_cast<const CE_ExtendedKeyUsage*>(ext->value.parsedValue); |
| if (!ExtendedKeyUsageAllows(ext_key_usage, &CSSMOID_ClientAuth)) |
| return false; |
| } |
| return true; |
| } |
| |
| bool X509Certificate::IsIssuedBy( |
| const std::vector<CertPrincipal>& 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<CFArrayRef> 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<SecCertificateRef>( |
| const_cast<void*>(CFArrayGetValueAtIndex(cert_chain, i))); |
| scoped_refptr<X509Certificate> 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<CertPrincipal>& valid_issuers, |
| CertificateList* certs) { |
| ScopedCFTypeRef<SecIdentityRef> preferred_identity; |
| if (!server_domain.empty()) { |
| // See if there's an identity preference for this domain: |
| ScopedCFTypeRef<CFStringRef> 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<SecIdentitySearchRef> scoped_search(search); |
| while (!err) { |
| SecIdentityRef identity = NULL; |
| { |
| base::AutoLock lock(crypto::GetMacSecurityServicesLock()); |
| err = SecIdentitySearchCopyNext(search, &identity); |
| } |
| if (err) |
| break; |
| ScopedCFTypeRef<SecIdentityRef> scoped_identity(identity); |
| |
| SecCertificateRef cert_handle; |
| err = SecIdentityCopyCertificate(identity, &cert_handle); |
| if (err != noErr) |
| continue; |
| ScopedCFTypeRef<SecCertificateRef> scoped_cert_handle(cert_handle); |
| |
| scoped_refptr<X509Certificate> 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<CFMutableArrayRef> chain( |
| CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks)); |
| CFArrayAppendValue(chain, identity); |
| |
| CFArrayRef cert_chain = NULL; |
| result = CopyCertChain(cert_handle_, &cert_chain); |
| ScopedCFTypeRef<CFArrayRef> 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<char*>(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<SecKeyRef> 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 |