// 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 <blapi.h> // Implement CalculateChainFingerprint() with NSS. #include "base/logging.h" #include "base/memory/scoped_ptr.h" #include "base/pickle.h" #include "base/sha1.h" #include "base/strings/string_util.h" #include "base/strings/utf_string_conversions.h" #include "crypto/capi_util.h" #include "crypto/scoped_capi_types.h" #include "net/base/net_errors.h" #pragma comment(lib, "crypt32.lib") using base::Time; namespace net { namespace { typedef crypto::ScopedCAPIHandle< HCERTSTORE, crypto::CAPIDestroyerWithFlags<HCERTSTORE, CertCloseStore, 0> > ScopedHCERTSTORE; void ExplodedTimeToSystemTime(const base::Time::Exploded& exploded, SYSTEMTIME* system_time) { system_time->wYear = exploded.year; system_time->wMonth = exploded.month; system_time->wDayOfWeek = exploded.day_of_week; system_time->wDay = exploded.day_of_month; system_time->wHour = exploded.hour; system_time->wMinute = exploded.minute; system_time->wSecond = exploded.second; system_time->wMilliseconds = exploded.millisecond; } //----------------------------------------------------------------------------- // Decodes the cert's subjectAltName extension into a CERT_ALT_NAME_INFO // structure and stores it in *output. void GetCertSubjectAltName(PCCERT_CONTEXT cert, scoped_ptr_malloc<CERT_ALT_NAME_INFO>* output) { PCERT_EXTENSION extension = CertFindExtension(szOID_SUBJECT_ALT_NAME2, cert->pCertInfo->cExtension, cert->pCertInfo->rgExtension); if (!extension) return; CRYPT_DECODE_PARA decode_para; decode_para.cbSize = sizeof(decode_para); decode_para.pfnAlloc = crypto::CryptAlloc; decode_para.pfnFree = crypto::CryptFree; CERT_ALT_NAME_INFO* alt_name_info = NULL; DWORD alt_name_info_size = 0; BOOL rv; rv = CryptDecodeObjectEx(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, szOID_SUBJECT_ALT_NAME2, extension->Value.pbData, extension->Value.cbData, CRYPT_DECODE_ALLOC_FLAG | CRYPT_DECODE_NOCOPY_FLAG, &decode_para, &alt_name_info, &alt_name_info_size); if (rv) output->reset(alt_name_info); } void AddCertsFromStore(HCERTSTORE store, X509Certificate::OSCertHandles* results) { PCCERT_CONTEXT cert = NULL; while ((cert = CertEnumCertificatesInStore(store, cert)) != NULL) { PCCERT_CONTEXT to_add = NULL; if (CertAddCertificateContextToStore( NULL, // The cert won't be persisted in any cert store. This breaks // any association the context currently has to |store|, which // allows us, the caller, to safely close |store| without // releasing the cert handles. cert, CERT_STORE_ADD_USE_EXISTING, &to_add) && to_add != NULL) { // When processing stores generated from PKCS#7/PKCS#12 files, it // appears that the order returned is the inverse of the order that it // appeared in the file. // TODO(rsleevi): Ensure this order is consistent across all Win // versions results->insert(results->begin(), to_add); } } } X509Certificate::OSCertHandles ParsePKCS7(const char* data, size_t length) { X509Certificate::OSCertHandles results; CERT_BLOB data_blob; data_blob.cbData = length; data_blob.pbData = reinterpret_cast<BYTE*>(const_cast<char*>(data)); HCERTSTORE out_store = NULL; DWORD expected_types = CERT_QUERY_CONTENT_FLAG_PKCS7_SIGNED | CERT_QUERY_CONTENT_FLAG_PKCS7_SIGNED_EMBED | CERT_QUERY_CONTENT_FLAG_PKCS7_UNSIGNED; if (!CryptQueryObject(CERT_QUERY_OBJECT_BLOB, &data_blob, expected_types, CERT_QUERY_FORMAT_FLAG_BINARY, 0, NULL, NULL, NULL, &out_store, NULL, NULL) || out_store == NULL) { return results; } AddCertsFromStore(out_store, &results); CertCloseStore(out_store, CERT_CLOSE_STORE_CHECK_FLAG); return results; } // Given a CERT_NAME_BLOB, returns true if it appears in a given list, // formatted as a vector of strings holding DER-encoded X.509 // DistinguishedName entries. bool IsCertNameBlobInIssuerList( CERT_NAME_BLOB* name_blob, const std::vector<std::string>& issuer_names) { for (std::vector<std::string>::const_iterator it = issuer_names.begin(); it != issuer_names.end(); ++it) { CERT_NAME_BLOB issuer_blob; issuer_blob.pbData = reinterpret_cast<BYTE*>(const_cast<char*>(it->data())); issuer_blob.cbData = static_cast<DWORD>(it->length()); BOOL rb = CertCompareCertificateName( X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, &issuer_blob, name_blob); if (rb) return true; } return false; } } // namespace void X509Certificate::Initialize() { DCHECK(cert_handle_); subject_.ParseDistinguishedName(cert_handle_->pCertInfo->Subject.pbData, cert_handle_->pCertInfo->Subject.cbData); issuer_.ParseDistinguishedName(cert_handle_->pCertInfo->Issuer.pbData, cert_handle_->pCertInfo->Issuer.cbData); valid_start_ = Time::FromFileTime(cert_handle_->pCertInfo->NotBefore); valid_expiry_ = Time::FromFileTime(cert_handle_->pCertInfo->NotAfter); fingerprint_ = CalculateFingerprint(cert_handle_); ca_fingerprint_ = CalculateCAFingerprint(intermediate_ca_certs_); const CRYPT_INTEGER_BLOB* serial = &cert_handle_->pCertInfo->SerialNumber; scoped_ptr<uint8[]> serial_bytes(new uint8[serial->cbData]); for (unsigned i = 0; i < serial->cbData; i++) serial_bytes[i] = serial->pbData[serial->cbData - i - 1]; serial_number_ = std::string( reinterpret_cast<char*>(serial_bytes.get()), serial->cbData); } 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(); if (!cert_handle_) return; scoped_ptr_malloc<CERT_ALT_NAME_INFO> alt_name_info; GetCertSubjectAltName(cert_handle_, &alt_name_info); CERT_ALT_NAME_INFO* alt_name = alt_name_info.get(); if (alt_name) { int num_entries = alt_name->cAltEntry; for (int i = 0; i < num_entries; i++) { // dNSName is an ASN.1 IA5String representing a string of ASCII // characters, so we can use WideToASCII here. const CERT_ALT_NAME_ENTRY& entry = alt_name->rgAltEntry[i]; if (dns_names && entry.dwAltNameChoice == CERT_ALT_NAME_DNS_NAME) { dns_names->push_back(WideToASCII(entry.pwszDNSName)); } else if (ip_addrs && entry.dwAltNameChoice == CERT_ALT_NAME_IP_ADDRESS) { ip_addrs->push_back(std::string( reinterpret_cast<const char*>(entry.IPAddress.pbData), entry.IPAddress.cbData)); } } } } PCCERT_CONTEXT X509Certificate::CreateOSCertChainForCert() const { // Create an in-memory certificate store to hold this certificate and // any intermediate certificates in |intermediate_ca_certs_|. The store // will be referenced in the returned PCCERT_CONTEXT, and will not be freed // until the PCCERT_CONTEXT is freed. ScopedHCERTSTORE store(CertOpenStore( CERT_STORE_PROV_MEMORY, 0, NULL, CERT_STORE_DEFER_CLOSE_UNTIL_LAST_FREE_FLAG, NULL)); if (!store.get()) return NULL; // NOTE: This preserves all of the properties of |os_cert_handle()| except // for CERT_KEY_PROV_HANDLE_PROP_ID and CERT_KEY_CONTEXT_PROP_ID - the two // properties that hold access to already-opened private keys. If a handle // has already been unlocked (eg: PIN prompt), then the first time that the // identity is used for client auth, it may prompt the user again. PCCERT_CONTEXT primary_cert; BOOL ok = CertAddCertificateContextToStore(store.get(), os_cert_handle(), CERT_STORE_ADD_ALWAYS, &primary_cert); if (!ok || !primary_cert) return NULL; for (size_t i = 0; i < intermediate_ca_certs_.size(); ++i) { CertAddCertificateContextToStore(store.get(), intermediate_ca_certs_[i], CERT_STORE_ADD_ALWAYS, NULL); } // Note: |store| is explicitly not released, as the call to CertCloseStore() // when |store| goes out of scope will not actually free the store. Instead, // the store will be freed when |primary_cert| is freed. return primary_cert; } // static bool X509Certificate::GetDEREncoded(X509Certificate::OSCertHandle cert_handle, std::string* encoded) { if (!cert_handle->pbCertEncoded || !cert_handle->cbCertEncoded) return false; encoded->assign(reinterpret_cast<char*>(cert_handle->pbCertEncoded), cert_handle->cbCertEncoded); return true; } // static bool X509Certificate::IsSameOSCert(X509Certificate::OSCertHandle a, X509Certificate::OSCertHandle b) { DCHECK(a && b); if (a == b) return true; return a->cbCertEncoded == b->cbCertEncoded && memcmp(a->pbCertEncoded, b->pbCertEncoded, a->cbCertEncoded) == 0; } // static X509Certificate::OSCertHandle X509Certificate::CreateOSCertHandleFromBytes( const char* data, int length) { OSCertHandle cert_handle = NULL; if (!CertAddEncodedCertificateToStore( NULL, X509_ASN_ENCODING, reinterpret_cast<const BYTE*>(data), length, CERT_STORE_ADD_USE_EXISTING, &cert_handle)) return NULL; return cert_handle; } 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 != NULL) results.push_back(handle); break; } case FORMAT_PKCS7: results = ParsePKCS7(data, length); break; default: NOTREACHED() << "Certificate format " << format << " unimplemented"; break; } return results; } // static X509Certificate::OSCertHandle X509Certificate::DupOSCertHandle( OSCertHandle cert_handle) { return CertDuplicateCertificateContext(cert_handle); } // static void X509Certificate::FreeOSCertHandle(OSCertHandle cert_handle) { CertFreeCertificateContext(cert_handle); } // static SHA1HashValue X509Certificate::CalculateFingerprint( OSCertHandle cert) { DCHECK(NULL != cert->pbCertEncoded); DCHECK_NE(static_cast<DWORD>(0), cert->cbCertEncoded); BOOL rv; SHA1HashValue sha1; DWORD sha1_size = sizeof(sha1.data); rv = CryptHashCertificate(NULL, CALG_SHA1, 0, cert->pbCertEncoded, cert->cbCertEncoded, sha1.data, &sha1_size); DCHECK(rv && sha1_size == sizeof(sha1.data)); if (!rv) memset(sha1.data, 0, sizeof(sha1.data)); return sha1; } // TODO(wtc): This function is implemented with NSS low-level hash // functions to ensure it is fast. Reimplement this function with // CryptoAPI. May need to cache the HCRYPTPROV to reduce the overhead. // static SHA1HashValue X509Certificate::CalculateCAFingerprint( const OSCertHandles& intermediates) { SHA1HashValue sha1; memset(sha1.data, 0, sizeof(sha1.data)); SHA1Context* sha1_ctx = SHA1_NewContext(); if (!sha1_ctx) return sha1; SHA1_Begin(sha1_ctx); for (size_t i = 0; i < intermediates.size(); ++i) { PCCERT_CONTEXT ca_cert = intermediates[i]; SHA1_Update(sha1_ctx, ca_cert->pbCertEncoded, ca_cert->cbCertEncoded); } unsigned int result_len; SHA1_End(sha1_ctx, sha1.data, &result_len, SHA1_LENGTH); SHA1_DestroyContext(sha1_ctx, PR_TRUE); return sha1; } // static X509Certificate::OSCertHandle X509Certificate::ReadOSCertHandleFromPickle(PickleIterator* pickle_iter) { const char* data; int length; if (!pickle_iter->ReadData(&data, &length)) return NULL; // Legacy serialized certificates were serialized with extended attributes, // rather than as DER only. As a result, these serialized certificates are // not portable across platforms and may have side-effects on Windows due // to extended attributes being serialized/deserialized - // http://crbug.com/118706. To avoid deserializing these attributes, write // the deserialized cert into a temporary cert store and then create a new // cert from the DER - that is, without attributes. ScopedHCERTSTORE store( CertOpenStore(CERT_STORE_PROV_MEMORY, 0, NULL, 0, NULL)); if (!store.get()) return NULL; OSCertHandle cert_handle = NULL; if (!CertAddSerializedElementToStore( store.get(), reinterpret_cast<const BYTE*>(data), length, CERT_STORE_ADD_NEW, 0, CERT_STORE_CERTIFICATE_CONTEXT_FLAG, NULL, reinterpret_cast<const void **>(&cert_handle))) { return NULL; } std::string encoded; bool ok = GetDEREncoded(cert_handle, &encoded); FreeOSCertHandle(cert_handle); cert_handle = NULL; if (ok) cert_handle = CreateOSCertHandleFromBytes(encoded.data(), encoded.size()); return cert_handle; } // static bool X509Certificate::WriteOSCertHandleToPickle(OSCertHandle cert_handle, Pickle* pickle) { return pickle->WriteData( reinterpret_cast<char*>(cert_handle->pbCertEncoded), cert_handle->cbCertEncoded); } // static void X509Certificate::GetPublicKeyInfo(OSCertHandle cert_handle, size_t* size_bits, PublicKeyType* type) { *type = kPublicKeyTypeUnknown; *size_bits = 0; PCCRYPT_OID_INFO oid_info = CryptFindOIDInfo( CRYPT_OID_INFO_OID_KEY, cert_handle->pCertInfo->SubjectPublicKeyInfo.Algorithm.pszObjId, CRYPT_PUBKEY_ALG_OID_GROUP_ID); if (!oid_info) return; CHECK_EQ(oid_info->dwGroupId, static_cast<DWORD>(CRYPT_PUBKEY_ALG_OID_GROUP_ID)); *size_bits = CertGetPublicKeyLength( X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, &cert_handle->pCertInfo->SubjectPublicKeyInfo); if (IS_SPECIAL_OID_INFO_ALGID(oid_info->Algid)) { // For an EC public key, oid_info->Algid is CALG_OID_INFO_PARAMETERS // (0xFFFFFFFE). Need to handle it as a special case. if (strcmp(oid_info->pszOID, szOID_ECC_PUBLIC_KEY) == 0) { *type = kPublicKeyTypeECDSA; } else { NOTREACHED(); } return; } switch (oid_info->Algid) { case CALG_RSA_SIGN: case CALG_RSA_KEYX: *type = kPublicKeyTypeRSA; break; case CALG_DSS_SIGN: *type = kPublicKeyTypeDSA; break; case CALG_ECDSA: *type = kPublicKeyTypeECDSA; break; case CALG_ECDH: *type = kPublicKeyTypeECDH; break; } } bool X509Certificate::IsIssuedByEncoded( const std::vector<std::string>& valid_issuers) { // If the certificate's issuer in the list? if (IsCertNameBlobInIssuerList(&cert_handle_->pCertInfo->Issuer, valid_issuers)) { return true; } // Otherwise, is any of the intermediate CA subjects in the list? for (OSCertHandles::iterator it = intermediate_ca_certs_.begin(); it != intermediate_ca_certs_.end(); ++it) { if (IsCertNameBlobInIssuerList(&(*it)->pCertInfo->Issuer, valid_issuers)) { return true; } } return false; } } // namespace net