// 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/cert/x509_certificate.h" #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/strings/string_piece.h" #include "base/strings/sys_string_conversions.h" #include "base/synchronization/lock.h" #include "crypto/cssm_init.h" #include "crypto/mac_security_services_lock.h" #include "net/cert/x509_util_mac.h" using base::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); } bool IsCertIssuerInEncodedList(X509Certificate::OSCertHandle cert_handle, const std::vector& issuers) { x509_util::CSSMCachedCertificate cached_cert; if (cached_cert.Init(cert_handle) != CSSM_OK) return false; x509_util::CSSMFieldValue distinguished_name; OSStatus status = cached_cert.GetField(&CSSMOID_X509V1IssuerNameStd, &distinguished_name); if (status || !distinguished_name.field()) return false; base::StringPiece name_piece( reinterpret_cast(distinguished_name.field()->Data), static_cast(distinguished_name.field()->Length)); for (std::vector::const_iterator it = issuers.begin(); it != issuers.end(); ++it) { base::StringPiece issuer_piece(*it); if (name_piece == issuer_piece) return true; } return false; } 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); } // 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), base::checked_cast(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); } } } } } // 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_); } bool X509Certificate::IsIssuedByEncoded( const std::vector& valid_issuers) { if (IsCertIssuerInEncodedList(cert_handle_, valid_issuers)) return true; for (OSCertHandles::iterator it = intermediate_ca_certs_.begin(); it != intermediate_ca_certs_.end(); ++it) { if (IsCertIssuerInEncodedList(*it, valid_issuers)) return true; } return false; } 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 (!cert_handle || 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, size_t 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, size_t 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) { if (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 SHA256HashValue X509Certificate::CalculateFingerprint256(OSCertHandle cert) { SHA256HashValue sha256; memset(sha256.data, 0, sizeof(sha256.data)); CSSM_DATA cert_data; OSStatus status = SecCertificateGetData(cert, &cert_data); if (status) return sha256; DCHECK(cert_data.Data); DCHECK_NE(cert_data.Length, 0U); CC_SHA256(cert_data.Data, cert_data.Length, sha256.data); return sha256; } // 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; } CFMutableArrayRef 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( base::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, base::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) { // SecCertificateCopyPublicKey may fail if the certificate has an invalid // key. See https://crbug.com/472291. LOG(WARNING) << "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; } } // static bool X509Certificate::IsSelfSigned(OSCertHandle cert_handle) { x509_util::CSSMCachedCertificate cached_cert; OSStatus status = cached_cert.Init(cert_handle); if (status != noErr) return false; x509_util::CSSMFieldValue subject; status = cached_cert.GetField(&CSSMOID_X509V1SubjectNameStd, &subject); if (status != CSSM_OK || !subject.field()) return false; x509_util::CSSMFieldValue issuer; status = cached_cert.GetField(&CSSMOID_X509V1IssuerNameStd, &issuer); if (status != CSSM_OK || !issuer.field()) return false; if (subject.field()->Length != issuer.field()->Length || memcmp(subject.field()->Data, issuer.field()->Data, issuer.field()->Length) != 0) { return false; } CSSM_CL_HANDLE cl_handle = CSSM_INVALID_HANDLE; status = SecCertificateGetCLHandle(cert_handle, &cl_handle); if (status) return false; CSSM_DATA cert_data; status = SecCertificateGetData(cert_handle, &cert_data); if (status) return false; if (CSSM_CL_CertVerify(cl_handle, 0, &cert_data, &cert_data, NULL, 0)) return false; return true; } } // namespace net