// 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<CERT_ALT_NAME_INFO, base::FreeDeleter>* 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<CERT_ALT_NAME_INFO, base::FreeDeleter> 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 UTF16ToASCII 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(base::UTF16ToASCII(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