diff options
author | phajdan.jr@chromium.org <phajdan.jr@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-03-29 21:48:11 +0000 |
---|---|---|
committer | phajdan.jr@chromium.org <phajdan.jr@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-03-29 21:48:11 +0000 |
commit | 6e7845aed4759ab35d722ce0551b5a90d21e7640 (patch) | |
tree | f29a3f007f7ded842d2096446ff7ecaf186cb362 /net/cert/x509_certificate_win.cc | |
parent | a6b4f91d970aa2b71b0f3552dbc11e94f7650fd5 (diff) | |
download | chromium_src-6e7845aed4759ab35d722ce0551b5a90d21e7640.zip chromium_src-6e7845aed4759ab35d722ce0551b5a90d21e7640.tar.gz chromium_src-6e7845aed4759ab35d722ce0551b5a90d21e7640.tar.bz2 |
net: extract net/cert out of net/base
This introduces the following dependency of net/base on things outside:
net/base/openssl_client_key_store.cc:#include "net/cert/x509_certificate.h"
BUG=70818
Review URL: https://codereview.chromium.org/13006020
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@191450 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'net/cert/x509_certificate_win.cc')
-rw-r--r-- | net/cert/x509_certificate_win.cc | 505 |
1 files changed, 505 insertions, 0 deletions
diff --git a/net/cert/x509_certificate_win.cc b/net/cert/x509_certificate_win.cc new file mode 100644 index 0000000..6a72328 --- /dev/null +++ b/net/cert/x509_certificate_win.cc @@ -0,0 +1,505 @@ +// 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/string_util.h" +#include "base/utf_string_conversions.h" +#include "crypto/capi_util.h" +#include "crypto/rsa_private_key.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_array<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); +} + +// static +X509Certificate* X509Certificate::CreateSelfSigned( + crypto::RSAPrivateKey* key, + const std::string& subject, + uint32 serial_number, + base::TimeDelta valid_duration) { + // Get the ASN.1 encoding of the certificate subject. + std::wstring w_subject = ASCIIToWide(subject); + DWORD encoded_subject_length = 0; + if (!CertStrToName( + X509_ASN_ENCODING, + w_subject.c_str(), + CERT_X500_NAME_STR, NULL, NULL, &encoded_subject_length, NULL)) { + return NULL; + } + + scoped_array<BYTE> encoded_subject(new BYTE[encoded_subject_length]); + if (!CertStrToName( + X509_ASN_ENCODING, + w_subject.c_str(), + CERT_X500_NAME_STR, NULL, + encoded_subject.get(), + &encoded_subject_length, NULL)) { + return NULL; + } + + CERT_NAME_BLOB subject_name; + memset(&subject_name, 0, sizeof(subject_name)); + subject_name.cbData = encoded_subject_length; + subject_name.pbData = encoded_subject.get(); + + CRYPT_ALGORITHM_IDENTIFIER sign_algo; + memset(&sign_algo, 0, sizeof(sign_algo)); + sign_algo.pszObjId = szOID_RSA_SHA1RSA; + + base::Time not_before = base::Time::Now(); + base::Time not_after = not_before + valid_duration; + base::Time::Exploded exploded; + + // Create the system time structs representing our exploded times. + not_before.UTCExplode(&exploded); + SYSTEMTIME start_time; + ExplodedTimeToSystemTime(exploded, &start_time); + not_after.UTCExplode(&exploded); + SYSTEMTIME end_time; + ExplodedTimeToSystemTime(exploded, &end_time); + + PCCERT_CONTEXT cert_handle = + CertCreateSelfSignCertificate(key->provider(), &subject_name, + CERT_CREATE_SELFSIGN_NO_KEY_INFO, NULL, + &sign_algo, &start_time, &end_time, NULL); + DCHECK(cert_handle) << "Failed to create self-signed certificate: " + << GetLastError(); + if (!cert_handle) + return NULL; + + X509Certificate* cert = CreateFromHandle(cert_handle, OSCertHandles()); + FreeOSCertHandle(cert_handle); + return cert; +} + +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); + + 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 |