diff options
author | rsleevi@chromium.org <rsleevi@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-12-16 00:01:37 +0000 |
---|---|---|
committer | rsleevi@chromium.org <rsleevi@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-12-16 00:01:37 +0000 |
commit | 32765f80889421c6161a7b9e73bc1ee722db6892 (patch) | |
tree | bba0f974c84f9859da5b62bc233d00955e63c032 /net/base/test_root_certs_win.cc | |
parent | 235478be87f59f3962eda9d8f3fba04e8a5096e4 (diff) | |
download | chromium_src-32765f80889421c6161a7b9e73bc1ee722db6892.zip chromium_src-32765f80889421c6161a7b9e73bc1ee722db6892.tar.gz chromium_src-32765f80889421c6161a7b9e73bc1ee722db6892.tar.bz2 |
Add support for temporarily trusting a certificate for the duration of unit tests on Windows, rather than requiring the machine to be pre-configured out-of-band.
Given the lack of a Microsoft-provided high-level API to supply application-level trusts to the verification routines, this implements a workaround that intercepts attempts to open the trusted system root store and injects the test certificates directly. This allows the unit tests to work without requiring that the Test CA be added to the machine's Trusted Certificates store.
While doing so, clean up the interface to adding/removing trusted test certificates, so as to support more than one trusted certificate if necessary.
BUG=8470
TEST=To follow
Review URL: http://codereview.chromium.org/4646001
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@69351 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'net/base/test_root_certs_win.cc')
-rw-r--r-- | net/base/test_root_certs_win.cc | 206 |
1 files changed, 206 insertions, 0 deletions
diff --git a/net/base/test_root_certs_win.cc b/net/base/test_root_certs_win.cc new file mode 100644 index 0000000..7862e40 --- /dev/null +++ b/net/base/test_root_certs_win.cc @@ -0,0 +1,206 @@ +// Copyright (c) 2010 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/test_root_certs.h" + +#include <windows.h> +#include <wincrypt.h> + +#include "base/basictypes.h" +#include "base/lazy_instance.h" +#include "base/logging.h" +#include "net/base/x509_certificate.h" + +namespace net { + +namespace { + +// Provides a CertDllOpenStoreProv callback provider function, to be called +// by CertOpenStore when the CERT_STORE_PROV_SYSTEM_W store is opened. See +// http://msdn.microsoft.com/en-us/library/aa376043(VS.85).aspx. +BOOL WINAPI InterceptedOpenStoreW(LPCSTR store_provider, + DWORD encoding, + HCRYPTPROV crypt_provider, + DWORD flags, + const void* extra, + HCERTSTORE memory_store, + PCERT_STORE_PROV_INFO store_info); + +// CryptoAPIInjector is used to inject a store provider function for system +// certificate stores before the one provided internally by Crypt32.dll. +// Once injected, there is no way to remove, so every call to open a system +// store will be redirected to the injected function. +struct CryptoAPIInjector { + // The previous default function for opening system stores. For most + // configurations, this should point to Crypt32's internal + // I_CertDllOpenSystemStoreProvW function. + PFN_CERT_DLL_OPEN_STORE_PROV_FUNC original_function; + + // The handle that CryptoAPI uses to ensure the DLL implementing + // |original_function| remains loaded in memory. + HCRYPTOIDFUNCADDR original_handle; + + private: + friend struct base::DefaultLazyInstanceTraits<CryptoAPIInjector>; + + CryptoAPIInjector() + : original_function(NULL), + original_handle(NULL) { + HCRYPTOIDFUNCSET registered_functions = + CryptInitOIDFunctionSet(CRYPT_OID_OPEN_STORE_PROV_FUNC, 0); + + // Preserve the original handler function in |original_function|. If other + // functions are overridden, they will also need to be preserved. + BOOL ok = CryptGetOIDFunctionAddress( + registered_functions, 0, CERT_STORE_PROV_SYSTEM_W, 0, + reinterpret_cast<void**>(&original_function), &original_handle); + DCHECK(ok); + + // For now, intercept only the numeric form of the system store + // function, CERT_STORE_PROV_SYSTEM_W (0x0A), which is what Crypt32 + // functionality uses exclusively. Depending on the machine that tests + // are being run on, it may prove necessary to also intercept + // sz_CERT_STORE_PROV_SYSTEM_[A/W] and CERT_STORE_PROV_SYSTEM_A, based + // on whether or not any third-party CryptoAPI modules have been + // installed. + const CRYPT_OID_FUNC_ENTRY kFunctionToIntercept = + { CERT_STORE_PROV_SYSTEM_W, &InterceptedOpenStoreW }; + + // Inject kFunctionToIntercept at the front of the linked list that + // crypt32 uses when CertOpenStore is called, replacing the existing + // registered function. + ok = CryptInstallOIDFunctionAddress(NULL, 0, + CRYPT_OID_OPEN_STORE_PROV_FUNC, 1, + &kFunctionToIntercept, + CRYPT_INSTALL_OID_FUNC_BEFORE_FLAG); + DCHECK(ok); + } + + // This is never called, because this object is intentionally leaked. + // Certificate verification happens on a non-joinable worker thread, which + // may still be running when ~AtExitManager is called, so the LazyInstance + // must be leaky. + ~CryptoAPIInjector() { + original_function = NULL; + CryptFreeOIDFunctionAddress(original_handle, NULL); + } +}; + +base::LazyInstance<CryptoAPIInjector, + base::LeakyLazyInstanceTraits<CryptoAPIInjector> > + g_capi_injector(base::LINKER_INITIALIZED); + +BOOL WINAPI InterceptedOpenStoreW(LPCSTR store_provider, + DWORD encoding, + HCRYPTPROV crypt_provider, + DWORD flags, + const void* store_name, + HCERTSTORE memory_store, + PCERT_STORE_PROV_INFO store_info) { + // If the high word is all zeroes, then |store_provider| is a numeric ID. + // Otherwise, it's a pointer to a null-terminated ASCII string. See the + // documentation for CryptGetOIDFunctionAddress for more information. + uint32 store_as_uint = reinterpret_cast<uint32>(store_provider); + if (store_as_uint > 0xFFFF || store_provider != CERT_STORE_PROV_SYSTEM_W || + !g_capi_injector.Get().original_function) + return FALSE; + + BOOL ok = g_capi_injector.Get().original_function(store_provider, encoding, + crypt_provider, flags, + store_name, memory_store, + store_info); + // Only the Root store should have certificates injected. If + // CERT_SYSTEM_STORE_RELOCATE_FLAG is set, then |store_name| points to a + // CERT_SYSTEM_STORE_RELOCATE_PARA structure, rather than a + // NULL-terminated wide string, so check before making a string + // comparison. + if (!ok || TestRootCerts::GetInstance()->IsEmpty() || + (flags & CERT_SYSTEM_STORE_RELOCATE_FLAG) || + lstrcmpiW(reinterpret_cast<LPCWSTR>(store_name), L"root")) + return ok; + + // The result of CertOpenStore with CERT_STORE_PROV_SYSTEM_W is documented + // to be a collection store, and that appears to hold for |memory_store|. + // Attempting to add an individual certificate to |memory_store| causes + // the request to be forwarded to the first physical store in the + // collection that accepts modifications, which will cause a secure + // confirmation dialog to be displayed, confirming the user wishes to + // trust the certificate. However, appending a store to the collection + // will merely modify the temporary collection store, and will not persist + // any changes to the underlying physical store. When the |memory_store| is + // searched to see if a certificate is in the Root store, all the + // underlying stores in the collection will be searched, and any certificate + // in temporary_roots() will be found and seen as trusted. + return CertAddStoreToCollection( + memory_store, TestRootCerts::GetInstance()->temporary_roots(), 0, 0); +} + +} // namespace + +bool TestRootCerts::Add(X509Certificate* certificate) { + // Ensure that the default CryptoAPI functionality has been intercepted. + // If a test certificate is never added, then no interception should + // happen. + g_capi_injector.Get(); + + BOOL ok = CertAddCertificateContextToStore( + temporary_roots_, certificate->os_cert_handle(), + CERT_STORE_ADD_NEW, NULL); + if (!ok) { + // If the certificate is already added, return successfully. + return GetLastError() == CRYPT_E_EXISTS; + } + + empty_ = false; + return true; +} + +void TestRootCerts::Clear() { + empty_ = true; + + PCCERT_CONTEXT prev_cert = NULL; + while (prev_cert = CertEnumCertificatesInStore(temporary_roots_, NULL)) + CertDeleteCertificateFromStore(prev_cert); +} + +bool TestRootCerts::IsEmpty() const { + return empty_; +} + +HCERTCHAINENGINE TestRootCerts::GetChainEngine() const { + if (IsEmpty()) + return NULL; // Default chain engine will suffice. + + // Each HCERTCHAINENGINE caches both the configured system stores and + // information about each chain that has been built. In order to ensure + // that changes to |temporary_roots_| are properly propagated and that the + // various caches are flushed, when at least one certificate is added, + // return a new chain engine for every call. Each chain engine creation + // should re-open the root store, ensuring the most recent changes are + // visible. + CERT_CHAIN_ENGINE_CONFIG engine_config = { + sizeof(engine_config) + }; + engine_config.dwFlags = + CERT_CHAIN_ENABLE_CACHE_AUTO_UPDATE | + CERT_CHAIN_ENABLE_SHARE_STORE; + HCERTCHAINENGINE chain_engine = NULL; + BOOL ok = CertCreateCertificateChainEngine(&engine_config, &chain_engine); + DCHECK(ok); + return chain_engine; +} + +TestRootCerts::~TestRootCerts() { + CertCloseStore(temporary_roots_, 0); +} + +void TestRootCerts::Init() { + empty_ = true; + temporary_roots_ = CertOpenStore( + CERT_STORE_PROV_MEMORY, 0, NULL, + CERT_STORE_DEFER_CLOSE_UNTIL_LAST_FREE_FLAG, NULL); + DCHECK(temporary_roots_); +} + +} // namespace net |