summaryrefslogtreecommitdiffstats
path: root/net/base/x509_certificate_win.cc
diff options
context:
space:
mode:
authoravi@google.com <avi@google.com@0039d316-1c4b-4281-b951-d872f2087c98>2008-09-18 18:40:31 +0000
committeravi@google.com <avi@google.com@0039d316-1c4b-4281-b951-d872f2087c98>2008-09-18 18:40:31 +0000
commit453ff92faa1e8d7af2377e1f1fea1f8210ca36a6 (patch)
treedb140ed322b28649638039b9bd99dea1525ca881 /net/base/x509_certificate_win.cc
parentf1cf7487ce09e118695afd437fb5ae56e845c94c (diff)
downloadchromium_src-453ff92faa1e8d7af2377e1f1fea1f8210ca36a6.zip
chromium_src-453ff92faa1e8d7af2377e1f1fea1f8210ca36a6.tar.gz
chromium_src-453ff92faa1e8d7af2377e1f1fea1f8210ca36a6.tar.bz2
Bring X.509 cert handling (at least preliminarily) to the Mac.
Review URL: http://codereview.chromium.org/2963 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@2369 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'net/base/x509_certificate_win.cc')
-rw-r--r--net/base/x509_certificate_win.cc549
1 files changed, 549 insertions, 0 deletions
diff --git a/net/base/x509_certificate_win.cc b/net/base/x509_certificate_win.cc
new file mode 100644
index 0000000..ab3fe64
--- /dev/null
+++ b/net/base/x509_certificate_win.cc
@@ -0,0 +1,549 @@
+// Copyright (c) 2006-2008 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 <map>
+
+#include "base/histogram.h"
+#include "base/lock.h"
+#include "base/pickle.h"
+#include "base/singleton.h"
+#include "base/string_tokenizer.h"
+#include "base/string_util.h"
+#include "net/base/cert_status_flags.h"
+#include "net/base/ev_root_ca_metadata.h"
+
+#pragma comment(lib, "crypt32.lib")
+
+namespace net {
+
+namespace {
+
+// Returns true if this cert fingerprint is the null (all zero) fingerprint.
+// We use this as a bogus fingerprint value.
+bool IsNullFingerprint(const X509Certificate::Fingerprint& fingerprint) {
+ for (int i = 0; i < arraysize(fingerprint.data); ++i) {
+ if (fingerprint.data[i] != 0)
+ return false;
+ }
+ return true;
+}
+
+// Calculates the SHA-1 fingerprint of the certificate. Returns an empty
+// (all zero) fingerprint on failure.
+X509Certificate::Fingerprint CalculateFingerprint(PCCERT_CONTEXT cert) {
+ DCHECK(NULL != cert->pbCertEncoded);
+ DCHECK(0 != cert->cbCertEncoded);
+
+ BOOL rv;
+ X509Certificate::Fingerprint 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;
+}
+
+// Wrappers of malloc and free for CRYPT_DECODE_PARA, which requires the
+// WINAPI calling convention.
+void* WINAPI MyCryptAlloc(size_t size) {
+ return malloc(size);
+}
+
+void WINAPI MyCryptFree(void* p) {
+ free(p);
+}
+
+// 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 = MyCryptAlloc;
+ decode_para.pfnFree = MyCryptFree;
+ 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);
+}
+
+///////////////////////////////////////////////////////////////////////////
+//
+// Functions used by X509Certificate::IsEV
+//
+///////////////////////////////////////////////////////////////////////////
+
+// Constructs a certificate chain starting from the end certificate
+// 'cert_context', matching any of the certificate policies.
+//
+// Returns the certificate chain context on success, or NULL on failure.
+// The caller is responsible for freeing the certificate chain context with
+// CertFreeCertificateChain.
+PCCERT_CHAIN_CONTEXT ConstructCertChain(
+ PCCERT_CONTEXT cert_context,
+ const char* const* policies,
+ int num_policies) {
+ CERT_CHAIN_PARA chain_para;
+ memset(&chain_para, 0, sizeof(chain_para));
+ chain_para.cbSize = sizeof(chain_para);
+ chain_para.RequestedUsage.dwType = USAGE_MATCH_TYPE_AND;
+ chain_para.RequestedUsage.Usage.cUsageIdentifier = 0;
+ chain_para.RequestedUsage.Usage.rgpszUsageIdentifier = NULL; // LPSTR*
+ chain_para.RequestedIssuancePolicy.dwType = USAGE_MATCH_TYPE_OR;
+ chain_para.RequestedIssuancePolicy.Usage.cUsageIdentifier = num_policies;
+ chain_para.RequestedIssuancePolicy.Usage.rgpszUsageIdentifier =
+ const_cast<char**>(policies);
+ PCCERT_CHAIN_CONTEXT chain_context;
+ if (!CertGetCertificateChain(
+ NULL, // default chain engine, HCCE_CURRENT_USER
+ cert_context,
+ NULL, // current system time
+ cert_context->hCertStore, // search this store
+ &chain_para,
+ CERT_CHAIN_REVOCATION_CHECK_CHAIN_EXCLUDE_ROOT |
+ CERT_CHAIN_CACHE_END_CERT,
+ NULL, // reserved
+ &chain_context)) {
+ return NULL;
+ }
+ return chain_context;
+}
+
+// Decodes the cert's certificatePolicies extension into a CERT_POLICIES_INFO
+// structure and stores it in *output.
+void GetCertPoliciesInfo(PCCERT_CONTEXT cert,
+ scoped_ptr_malloc<CERT_POLICIES_INFO>* output) {
+ PCERT_EXTENSION extension = CertFindExtension(szOID_CERT_POLICIES,
+ cert->pCertInfo->cExtension,
+ cert->pCertInfo->rgExtension);
+ if (!extension)
+ return;
+
+ CRYPT_DECODE_PARA decode_para;
+ decode_para.cbSize = sizeof(decode_para);
+ decode_para.pfnAlloc = MyCryptAlloc;
+ decode_para.pfnFree = MyCryptFree;
+ CERT_POLICIES_INFO* policies_info = NULL;
+ DWORD policies_info_size = 0;
+ BOOL rv;
+ rv = CryptDecodeObjectEx(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING,
+ szOID_CERT_POLICIES,
+ extension->Value.pbData,
+ extension->Value.cbData,
+ CRYPT_DECODE_ALLOC_FLAG | CRYPT_DECODE_NOCOPY_FLAG,
+ &decode_para,
+ &policies_info,
+ &policies_info_size);
+ if (rv)
+ output->reset(policies_info);
+}
+
+// Returns true if the policy is in the array of CERT_POLICY_INFO in
+// the CERT_POLICIES_INFO structure.
+bool ContainsPolicy(const CERT_POLICIES_INFO* policies_info,
+ const char* policy) {
+ int num_policies = policies_info->cPolicyInfo;
+ for (int i = 0; i < num_policies; i++) {
+ if (!strcmp(policies_info->rgPolicyInfo[i].pszPolicyIdentifier, policy))
+ return true;
+ }
+ return false;
+}
+
+// This class wraps the CertFreeCertificateChain function in a class that can
+// be passed as a template argument to scoped_ptr_malloc.
+class ScopedPtrMallocFreeCertChain {
+ public:
+ void operator()(const CERT_CHAIN_CONTEXT* x) const {
+ CertFreeCertificateChain(x);
+ }
+};
+
+typedef scoped_ptr_malloc<const CERT_CHAIN_CONTEXT,
+ ScopedPtrMallocFreeCertChain> ScopedCertChainContext;
+
+} // namespace
+
+bool X509Certificate::FingerprintLessThan::operator()(
+ const Fingerprint& lhs,
+ const Fingerprint& rhs) const {
+ for (int i = 0; i < sizeof(lhs.data); ++i) {
+ if (lhs.data[i] < rhs.data[i])
+ return true;
+ if (lhs.data[i] > rhs.data[i])
+ return false;
+ }
+ return false;
+}
+
+bool X509Certificate::LessThan::operator()(X509Certificate* lhs,
+ X509Certificate* rhs) const {
+ if (lhs == rhs)
+ return false;
+
+ X509Certificate::FingerprintLessThan fingerprint_functor;
+ return fingerprint_functor(lhs->fingerprint_, rhs->fingerprint_);
+}
+
+// A thread-safe cache for X509Certificate objects.
+//
+// The cache does not hold a reference to the certificate objects. The objects
+// must |Remove| themselves from the cache upon destruction (or else the cache
+// will be holding dead pointers to the objects).
+class X509Certificate::Cache {
+ public:
+ // Get the singleton object for the cache.
+ static X509Certificate::Cache* GetInstance() {
+ return Singleton<X509Certificate::Cache>::get();
+ }
+
+ // Insert |cert| into the cache. The cache does NOT AddRef |cert|. The cache
+ // must not already contain a certificate with the same fingerprint.
+ void Insert(X509Certificate* cert) {
+ AutoLock lock(lock_);
+
+ DCHECK(!IsNullFingerprint(cert->fingerprint())) <<
+ "Only insert certs with real fingerprints.";
+ DCHECK(cache_.find(cert->fingerprint()) == cache_.end());
+ cache_[cert->fingerprint()] = cert;
+ };
+
+ // Remove |cert| from the cache. The cache does not assume that |cert| is
+ // already in the cache.
+ void Remove(X509Certificate* cert) {
+ AutoLock lock(lock_);
+
+ CertMap::iterator pos(cache_.find(cert->fingerprint()));
+ if (pos == cache_.end())
+ return; // It is not an error to remove a cert that is not in the cache.
+ cache_.erase(pos);
+ };
+
+ // Find a certificate in the cache with the given fingerprint. If one does
+ // not exist, this method returns NULL.
+ X509Certificate* Find(const Fingerprint& fingerprint) {
+ AutoLock lock(lock_);
+
+ CertMap::iterator pos(cache_.find(fingerprint));
+ if (pos == cache_.end())
+ return NULL;
+
+ return pos->second;
+ };
+
+ private:
+ typedef std::map<Fingerprint, X509Certificate*, FingerprintLessThan> CertMap;
+
+ // Obtain an instance of X509Certificate::Cache via GetInstance().
+ Cache() { }
+ friend DefaultSingletonTraits<X509Certificate::Cache>;
+
+ // You must acquire this lock before using any private data of this object.
+ // You must not block while holding this lock.
+ Lock lock_;
+
+ // The certificate cache. You must acquire |lock_| before using |cache_|.
+ CertMap cache_;
+
+ DISALLOW_EVIL_CONSTRUCTORS(X509Certificate::Cache);
+};
+
+void X509Certificate::Initialize() {
+ std::wstring subject_info;
+ std::wstring issuer_info;
+ DWORD name_size;
+ name_size = CertNameToStr(cert_handle_->dwCertEncodingType,
+ &cert_handle_->pCertInfo->Subject,
+ CERT_X500_NAME_STR | CERT_NAME_STR_CRLF_FLAG,
+ NULL, 0);
+ name_size = CertNameToStr(cert_handle_->dwCertEncodingType,
+ &cert_handle_->pCertInfo->Subject,
+ CERT_X500_NAME_STR | CERT_NAME_STR_CRLF_FLAG,
+ WriteInto(&subject_info, name_size), name_size);
+ name_size = CertNameToStr(cert_handle_->dwCertEncodingType,
+ &cert_handle_->pCertInfo->Issuer,
+ CERT_X500_NAME_STR | CERT_NAME_STR_CRLF_FLAG,
+ NULL, 0);
+ name_size = CertNameToStr(cert_handle_->dwCertEncodingType,
+ &cert_handle_->pCertInfo->Issuer,
+ CERT_X500_NAME_STR | CERT_NAME_STR_CRLF_FLAG,
+ WriteInto(&issuer_info, name_size), name_size);
+ ParsePrincipal(WideToUTF8(subject_info), &subject_);
+ ParsePrincipal(WideToUTF8(issuer_info), &issuer_);
+
+ valid_start_ = Time::FromFileTime(cert_handle_->pCertInfo->NotBefore);
+ valid_expiry_ = Time::FromFileTime(cert_handle_->pCertInfo->NotAfter);
+
+ fingerprint_ = CalculateFingerprint(cert_handle_);
+
+ // Store the certificate in the cache in case we need it later.
+ X509Certificate::Cache::GetInstance()->Insert(this);
+}
+
+// static
+X509Certificate* X509Certificate::CreateFromHandle(OSCertHandle cert_handle) {
+ DCHECK(cert_handle);
+
+ // Check if we already have this certificate in memory.
+ X509Certificate::Cache* cache = X509Certificate::Cache::GetInstance();
+ X509Certificate* cert = cache->Find(CalculateFingerprint(cert_handle));
+ if (cert) {
+ // We've found a certificate with the same fingerprint in our cache. We own
+ // the |cert_handle|, which makes it our job to free it.
+ CertFreeCertificateContext(cert_handle);
+ DHISTOGRAM_COUNTS(L"X509CertificateReuseCount", 1);
+ return cert;
+ }
+ // Otherwise, allocate a new object.
+ return new X509Certificate(cert_handle);
+}
+
+// static
+X509Certificate* X509Certificate::CreateFromPickle(const Pickle& pickle,
+ void** pickle_iter) {
+ const char* data;
+ int length;
+ if (!pickle.ReadData(pickle_iter, &data, &length))
+ return NULL;
+
+ OSCertHandle cert_handle = NULL;
+ if (!CertAddSerializedElementToStore(
+ NULL, // the cert won't be persisted in any cert store
+ reinterpret_cast<const BYTE*>(data), length,
+ CERT_STORE_ADD_USE_EXISTING, 0, CERT_STORE_CERTIFICATE_CONTEXT_FLAG,
+ NULL, reinterpret_cast<const void **>(&cert_handle)))
+ return NULL;
+
+ return CreateFromHandle(cert_handle);
+}
+
+X509Certificate::X509Certificate(OSCertHandle cert_handle)
+ : cert_handle_(cert_handle) {
+ Initialize();
+}
+
+X509Certificate::X509Certificate(std::string subject, std::string issuer,
+ Time start_date, Time expiration_date)
+ : subject_(subject),
+ issuer_(issuer),
+ valid_start_(start_date),
+ valid_expiry_(expiration_date),
+ cert_handle_(NULL) {
+ memset(fingerprint_.data, 0, sizeof(fingerprint_.data));
+}
+
+void X509Certificate::Persist(Pickle* pickle) {
+ DWORD length;
+ if (!CertSerializeCertificateStoreElement(cert_handle_, 0,
+ NULL, &length)) {
+ NOTREACHED();
+ return;
+ }
+ BYTE* data = reinterpret_cast<BYTE*>(pickle->BeginWriteData(length));
+ if (!CertSerializeCertificateStoreElement(cert_handle_, 0,
+ data, &length)) {
+ NOTREACHED();
+ length = 0;
+ }
+ pickle->TrimWriteData(length);
+}
+
+X509Certificate::~X509Certificate() {
+ // We might not be in the cache, but it is safe to remove ourselves anyway.
+ X509Certificate::Cache::GetInstance()->Remove(this);
+ if (cert_handle_)
+ CertFreeCertificateContext(cert_handle_);
+}
+
+void X509Certificate::GetDNSNames(std::vector<std::string>* dns_names) const {
+ dns_names->clear();
+ 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.
+ if (alt_name->rgAltEntry[i].dwAltNameChoice == CERT_ALT_NAME_DNS_NAME)
+ dns_names->push_back(WideToASCII(alt_name->rgAltEntry[i].pwszDNSName));
+ }
+ }
+ if (dns_names->empty())
+ dns_names->push_back(subject_.common_name);
+}
+
+bool X509Certificate::HasExpired() const {
+ return Time::Now() > valid_expiry();
+}
+
+// Returns true if the certificate is an extended-validation certificate.
+//
+// The certificate has already been verified by the HTTP library. cert_status
+// represents the result of that verification. This function performs
+// additional checks of the certificatePolicies extensions of the certificates
+// in the certificate chain according to Section 7 (pp. 11-12) of the EV
+// Certificate Guidelines Version 1.0 at
+// http://cabforum.org/EV_Certificate_Guidelines.pdf.
+bool X509Certificate::IsEV(int cert_status) const {
+ if (net::IsCertStatusError(cert_status) ||
+ (cert_status & net::CERT_STATUS_REV_CHECKING_ENABLED) == 0)
+ return false;
+
+ net::EVRootCAMetadata* metadata = net::EVRootCAMetadata::GetInstance();
+
+ PCCERT_CHAIN_CONTEXT chain_context = ConstructCertChain(cert_handle_,
+ metadata->GetPolicyOIDs(), metadata->NumPolicyOIDs());
+ if (!chain_context)
+ return false;
+ ScopedCertChainContext scoped_chain_context(chain_context);
+
+ DCHECK(chain_context->cChain != 0);
+ // If the cert doesn't match any of the policies, the
+ // CERT_TRUST_IS_NOT_VALID_FOR_USAGE bit (0x10) in
+ // chain_context->TrustStatus.dwErrorStatus is set.
+ DWORD error_status = chain_context->TrustStatus.dwErrorStatus;
+ DWORD info_status = chain_context->TrustStatus.dwInfoStatus;
+ if (!chain_context->cChain || error_status != CERT_TRUST_NO_ERROR)
+ return false;
+
+ // Check the end certificate simple chain (chain_context->rgpChain[0]).
+ // If the end certificate's certificatePolicies extension contains the
+ // EV policy OID of the root CA, return true.
+ PCERT_CHAIN_ELEMENT* element = chain_context->rgpChain[0]->rgpElement;
+ int num_elements = chain_context->rgpChain[0]->cElement;
+ if (num_elements < 2)
+ return false;
+
+ // Look up the EV policy OID of the root CA.
+ PCCERT_CONTEXT root_cert = element[num_elements - 1]->pCertContext;
+ X509Certificate::Fingerprint fingerprint = CalculateFingerprint(root_cert);
+ std::string ev_policy_oid;
+ if (!metadata->GetPolicyOID(fingerprint, &ev_policy_oid))
+ return false;
+ DCHECK(!ev_policy_oid.empty());
+
+ // Get the certificatePolicies extension of the end certificate.
+ PCCERT_CONTEXT end_cert = element[0]->pCertContext;
+ scoped_ptr_malloc<CERT_POLICIES_INFO> policies_info;
+ GetCertPoliciesInfo(end_cert, &policies_info);
+ if (!policies_info.get())
+ return false;
+
+ return ContainsPolicy(policies_info.get(), ev_policy_oid.c_str());
+}
+
+// static
+void X509Certificate::ParsePrincipal(const std::string& description,
+ Principal* principal) {
+ // The description of the principal is a string with each LDAP value on
+ // a separate line.
+ const std::string kDelimiters("\r\n");
+
+ std::vector<std::string> common_names, locality_names, state_names,
+ country_names, street_addresses;
+
+ // TODO(jcampan): add business_category and serial_number.
+ const std::string kPrefixes[8] = { std::string("CN="),
+ std::string("L="),
+ std::string("S="),
+ std::string("C="),
+ std::string("STREET="),
+ std::string("O="),
+ std::string("OU="),
+ std::string("DC=") };
+
+ std::vector<std::string>* values[8] = {
+ &common_names, &locality_names,
+ &state_names, &country_names,
+ &(principal->street_addresses),
+ &(principal->organization_names),
+ &(principal->organization_unit_names),
+ &(principal->domain_components) };
+ DCHECK(arraysize(kPrefixes) == arraysize(values));
+
+ StringTokenizer str_tok(description, kDelimiters);
+ while (str_tok.GetNext()) {
+ std::string entry = str_tok.token();
+ for (int i = 0; i < arraysize(kPrefixes); i++) {
+ if (!entry.compare(0, kPrefixes[i].length(), kPrefixes[i])) {
+ std::string value = entry.substr(kPrefixes[i].length());
+ // Remove enclosing double-quotes if any.
+ if (value.size() >= 2 &&
+ value[0] == '"' && value[value.size() - 1] == '"')
+ value = value.substr(1, value.size() - 2);
+ values[i]->push_back(value);
+ break;
+ }
+ }
+ }
+
+ // We don't expect to have more than one CN, L, S, and C.
+ std::vector<std::string>* single_value_lists[4] = {
+ &common_names, &locality_names, &state_names, &country_names };
+ std::string* single_values[4] = {
+ &principal->common_name, &principal->locality_name,
+ &principal->state_or_province_name, &principal->country_name };
+ for (int i = 0; i < arraysize(single_value_lists); ++i) {
+ int length = static_cast<int>(single_value_lists[i]->size());
+ DCHECK(single_value_lists[i]->size() <= 1);
+ if (single_value_lists[i]->size() > 0)
+ *(single_values[i]) = (*(single_value_lists[i]))[0];
+ }
+}
+
+X509Certificate::Policy::Judgment X509Certificate::Policy::Check(
+ X509Certificate* cert) const {
+ // It shouldn't matter which set we check first, but we check denied first
+ // in case something strange has happened.
+
+ if (denied_.find(cert->fingerprint()) != denied_.end()) {
+ // DCHECK that the order didn't matter.
+ DCHECK(allowed_.find(cert->fingerprint()) == allowed_.end());
+ return DENIED;
+ }
+
+ if (allowed_.find(cert->fingerprint()) != allowed_.end()) {
+ // DCHECK that the order didn't matter.
+ DCHECK(denied_.find(cert->fingerprint()) == denied_.end());
+ return ALLOWED;
+ }
+
+ // We don't have a policy for this cert.
+ return UNKNOWN;
+}
+
+void X509Certificate::Policy::Allow(X509Certificate* cert) {
+ // Put the cert in the allowed set and (maybe) remove it from the denied set.
+ denied_.erase(cert->fingerprint());
+ allowed_.insert(cert->fingerprint());
+}
+
+void X509Certificate::Policy::Deny(X509Certificate* cert) {
+ // Put the cert in the denied set and (maybe) remove it from the allowed set.
+ allowed_.erase(cert->fingerprint());
+ denied_.insert(cert->fingerprint());
+}
+
+} // namespace net
+