diff options
author | Jack Lloyd <[email protected]> | 2019-05-14 04:30:00 -0400 |
---|---|---|
committer | Jack Lloyd <[email protected]> | 2019-05-14 04:30:00 -0400 |
commit | d5495db3d5d459ae400a7c253774091865ca53af (patch) | |
tree | b94164646398d20cf53f42cb34e723a42acaf33b | |
parent | 249edbf41a75285527869c13068aa49d689c716b (diff) | |
parent | 9eb4954843b387c82f6d6126d84af06709ae3ac3 (diff) |
Merge GH #1931 Add Windows certificate store
-rw-r--r-- | doc/authors.txt | 1 | ||||
-rw-r--r-- | doc/todo.rst | 1 | ||||
-rw-r--r-- | src/build-data/os/windows.txt | 2 | ||||
-rw-r--r-- | src/lib/x509/certstor_system/certstor_system.cpp | 4 | ||||
-rw-r--r-- | src/lib/x509/certstor_system_windows/certstor_windows.cpp | 255 | ||||
-rw-r--r-- | src/lib/x509/certstor_system_windows/certstor_windows.h | 70 | ||||
-rw-r--r-- | src/lib/x509/certstor_system_windows/info.txt | 15 | ||||
-rw-r--r-- | src/tests/test_certstor_system.cpp | 69 | ||||
-rw-r--r-- | src/tests/test_certstor_utils.cpp | 2 |
9 files changed, 417 insertions, 2 deletions
diff --git a/doc/authors.txt b/doc/authors.txt index 954081b55..7f5f90d0b 100644 --- a/doc/authors.txt +++ b/doc/authors.txt @@ -89,6 +89,7 @@ souch t0b3 tcely Technische Universitat Darmstadt +Tim Oesterreich Tobias @neverhub Tomasz Frydrych Uri Blumenthal diff --git a/doc/todo.rst b/doc/todo.rst index 07119f2ad..ca524c944 100644 --- a/doc/todo.rst +++ b/doc/todo.rst @@ -69,7 +69,6 @@ Multiparty Protocols External Providers, Hardware Support ---------------------------------------- -* Access to system certificate stores (Windows, OS X) * Extend OpenSSL provider (DH, HMAC, CMAC, GCM) * Support using BoringSSL instead of OpenSSL or LibreSSL * /dev/crypto provider (ciphers, hashes) diff --git a/src/build-data/os/windows.txt b/src/build-data/os/windows.txt index 8eab164df..d11bacc37 100644 --- a/src/build-data/os/windows.txt +++ b/src/build-data/os/windows.txt @@ -32,6 +32,8 @@ virtual_lock threads filesystem + +certificate_store </target_features> <aliases> diff --git a/src/lib/x509/certstor_system/certstor_system.cpp b/src/lib/x509/certstor_system/certstor_system.cpp index 1650a1784..8b469dec9 100644 --- a/src/lib/x509/certstor_system/certstor_system.cpp +++ b/src/lib/x509/certstor_system/certstor_system.cpp @@ -9,6 +9,8 @@ #if defined(BOTAN_HAS_CERTSTOR_MACOS) #include <botan/certstor_macos.h> +#elif defined(BOTAN_HAS_CERTSTOR_WINDOWS) + #include <botan/certstor_windows.h> #elif defined(BOTAN_HAS_CERTSTOR_FLATFILE) && defined(BOTAN_SYSTEM_CERT_BUNDLE) #include <botan/certstor_flatfile.h> #endif @@ -19,6 +21,8 @@ System_Certificate_Store::System_Certificate_Store() { #if defined(BOTAN_HAS_CERTSTOR_MACOS) m_system_store = std::make_shared<Certificate_Store_MacOS>(); +#elif defined(BOTAN_HAS_CERTSTOR_WINDOWS) + m_system_store = std::make_shared<Certificate_Store_Windows>(); #elif defined(BOTAN_HAS_CERTSTOR_FLATFILE) && defined(BOTAN_SYSTEM_CERT_BUNDLE) m_system_store = std::make_shared<Flatfile_Certificate_Store>(BOTAN_SYSTEM_CERT_BUNDLE, true); #else diff --git a/src/lib/x509/certstor_system_windows/certstor_windows.cpp b/src/lib/x509/certstor_system_windows/certstor_windows.cpp new file mode 100644 index 000000000..58ef40fd7 --- /dev/null +++ b/src/lib/x509/certstor_system_windows/certstor_windows.cpp @@ -0,0 +1,255 @@ +/* +* Certificate Store +* (C) 1999-2019 Jack Lloyd +* (C) 2018-2019 Patrik Fiedler, Tim Oesterreich +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#include <botan/certstor_windows.h> +#include <botan/der_enc.h> + +#include <array> +#include <vector> + +#define NOMINMAX 1 +#define _WINSOCKAPI_ // stop windows.h including winsock.h +#include <windows.h> +#include <wincrypt.h> + +namespace Botan { +namespace { + +using Cert_Pointer = std::shared_ptr<const Botan::X509_Certificate>; +using Cert_Vector = std::vector<Cert_Pointer>; +const std::array<const char*, 2> cert_store_names{"Root", "CA"}; + +/** + * Abstract RAII wrapper for PCCERT_CONTEXT and HCERTSTORE + * The Windows API partly takes care of those pointers destructions itself. + * Especially, iteratively calling `CertFindCertificateInStore` with the previous PCCERT_CONTEXT + * will free the context and return a new one. In this case, this guard takes care of freeing the context + * in case of an exception and at the end of the iterative process. + */ +template<class T> +class Handle_Guard + { + public: + Handle_Guard(T context) + : m_context(context) + { + } + + Handle_Guard(const Handle_Guard<T>& rhs) = delete; + Handle_Guard(Handle_Guard<T>&& rhs) : + m_context(std::move(rhs.m_context)) + { + rhs.m_context = nullptr; + } + + ~Handle_Guard() + { + close<T>(); + } + + operator bool() const + { + return m_context != nullptr; + } + + bool assign(T context) + { + m_context = context; + return m_context != nullptr; + } + + T& get() + { + return m_context; + } + + const T& get() const + { + return m_context; + } + + T operator->() + { + return m_context; + } + + private: + template<class T2 = T> + typename std::enable_if<std::is_same<T2, PCCERT_CONTEXT>::value>::type close() + { + if(m_context) + { + CertFreeCertificateContext(m_context); + } + } + + template<class T2 = T> + typename std::enable_if<std::is_same<T2, HCERTSTORE>::value>::type close() + { + if(m_context) + { + // second parameter is a flag that tells the store how to deallocate memory + // using the default "0", this function works like decreasing the reference counter + // in a shared_ptr + CertCloseStore(m_context, 0); + } + } + + T m_context; + }; + +HCERTSTORE open_cert_store(const char* cert_store_name) + { + auto store = CertOpenSystemStore(NULL, cert_store_name); + if(!store) + { + throw Botan::Internal_Error( + "failed to open windows certificate store '" + std::string(cert_store_name) + + "' (Error Code: " + + std::to_string(::GetLastError()) + ")"); + } + return store; + } + +Cert_Vector search_cert_stores(const _CRYPTOAPI_BLOB& blob, const DWORD& find_type, + std::function<bool(const Cert_Vector& certs, Cert_Pointer cert)> filter, + bool return_on_first_found) + { + Cert_Vector certs; + for(const auto store_name : cert_store_names) + { + Handle_Guard<HCERTSTORE> windows_cert_store = open_cert_store(store_name); + Handle_Guard<PCCERT_CONTEXT> cert_context = nullptr; + while(cert_context.assign(CertFindCertificateInStore( + windows_cert_store.get(), PKCS_7_ASN_ENCODING | X509_ASN_ENCODING, + NULL, find_type, + &blob, cert_context.get()))) + { + auto cert = std::make_shared<X509_Certificate>(cert_context->pbCertEncoded, cert_context->cbCertEncoded); + if(filter(certs, cert)) + { + if(return_on_first_found) + { + return {cert}; + } + certs.push_back(cert); + } + } + } + + return certs; + } + +bool already_contains_certificate(const Cert_Vector& certs, Cert_Pointer cert) + { + return std::any_of(certs.begin(), certs.end(), [&](std::shared_ptr<const Botan::X509_Certificate> c) + { + return *c == *cert; + }); + } + +Cert_Vector find_cert_by_dn_and_key_id(const Botan::X509_DN& subject_dn, + const std::vector<uint8_t>& key_id, + bool return_on_first_found) + { + _CRYPTOAPI_BLOB blob; + DWORD find_type; + std::vector<uint8_t> dn_data; + + // if key_id is available, prefer searching that, as it should be "more unique" than the subject DN + if(key_id.empty()) + { + find_type = CERT_FIND_SUBJECT_NAME; + DER_Encoder encoder(dn_data); + subject_dn.encode_into(encoder); + blob.cbData = static_cast<DWORD>(dn_data.size()); + blob.pbData = reinterpret_cast<BYTE*>(dn_data.data()); + } + else + { + find_type = CERT_FIND_KEY_IDENTIFIER; + blob.cbData = static_cast<DWORD>(key_id.size()); + blob.pbData = const_cast<BYTE*>(key_id.data()); + } + + auto filter = [&](const Cert_Vector& certs, Cert_Pointer cert) + { + return !already_contains_certificate(certs, cert) && (key_id.empty() || cert->subject_dn() == subject_dn); + }; + + return search_cert_stores(blob, find_type, filter, return_on_first_found); + } +} // namespace + +Certificate_Store_Windows::Certificate_Store_Windows() {} + +std::vector<X509_DN> Certificate_Store_Windows::all_subjects() const + { + std::vector<X509_DN> subject_dns; + for(const auto store_name : cert_store_names) + { + Handle_Guard<HCERTSTORE> windows_cert_store = open_cert_store(store_name); + Handle_Guard<PCCERT_CONTEXT> cert_context = nullptr; + + // Handle_Guard::assign exchanges the underlying pointer. No RAII is needed here, because the Windows API takes care of + // freeing the previous context. + while(cert_context.assign(CertEnumCertificatesInStore(windows_cert_store.get(), cert_context.get()))) + { + X509_Certificate cert(cert_context->pbCertEncoded, cert_context->cbCertEncoded); + subject_dns.push_back(cert.subject_dn()); + } + } + + return subject_dns; + } + +Cert_Pointer Certificate_Store_Windows::find_cert(const Botan::X509_DN& subject_dn, + const std::vector<uint8_t>& key_id) const + { + const auto certs = find_cert_by_dn_and_key_id(subject_dn, key_id, true); + return certs.empty() ? nullptr : certs.front(); + } + +Cert_Vector Certificate_Store_Windows::find_all_certs( + const X509_DN& subject_dn, + const std::vector<uint8_t>& key_id) const + { + return find_cert_by_dn_and_key_id(subject_dn, key_id, false); + } + +Cert_Pointer Certificate_Store_Windows::find_cert_by_pubkey_sha1(const std::vector<uint8_t>& key_hash) const + { + if(key_hash.size() != 20) + { + throw Invalid_Argument("Certificate_Store_Windows::find_cert_by_pubkey_sha1 invalid hash"); + } + + CRYPT_HASH_BLOB blob; + blob.cbData = static_cast<DWORD>(key_hash.size()); + blob.pbData = const_cast<BYTE*>(key_hash.data()); + + auto filter = [](const Cert_Vector&, Cert_Pointer) { return true; }; + + const auto certs = search_cert_stores(blob, CERT_FIND_KEY_IDENTIFIER, filter, true); + return certs.empty() ? nullptr : certs.front(); + } + +Cert_Pointer Certificate_Store_Windows::find_cert_by_raw_subject_dn_sha256( + const std::vector<uint8_t>& subject_hash) const + { + BOTAN_UNUSED(subject_hash); + throw Not_Implemented("Certificate_Store_Windows::find_cert_by_raw_subject_dn_sha256"); + } + +std::shared_ptr<const X509_CRL> Certificate_Store_Windows::find_crl_for(const X509_Certificate& subject) const + { + // TODO: this could be implemented by using the CertFindCRLInStore function + BOTAN_UNUSED(subject); + return {}; + } +} diff --git a/src/lib/x509/certstor_system_windows/certstor_windows.h b/src/lib/x509/certstor_system_windows/certstor_windows.h new file mode 100644 index 000000000..f47e718c8 --- /dev/null +++ b/src/lib/x509/certstor_system_windows/certstor_windows.h @@ -0,0 +1,70 @@ +/* +* Certificate Store +* (C) 1999-2019 Jack Lloyd +* (C) 2019 Patrick Schmidt +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#ifndef BOTAN_CERT_STORE_SYSTEM_WINDOWS_H_ +#define BOTAN_CERT_STORE_SYSTEM_WINDOWS_H_ + +#include <botan/certstor.h> + +namespace Botan { +/** +* Certificate Store that is backed by the system trust store on Windows. +*/ +class BOTAN_PUBLIC_API(2, 11) Certificate_Store_Windows final : public Certificate_Store + { + public: + Certificate_Store_Windows(); + + Certificate_Store_Windows(const Certificate_Store_Windows&) = default; + Certificate_Store_Windows(Certificate_Store_Windows&&) = default; + Certificate_Store_Windows& operator=(const Certificate_Store_Windows&) = default; + Certificate_Store_Windows& operator=(Certificate_Store_Windows&&) = default; + + /** + * @return DNs for all certificates managed by the store + */ + std::vector<X509_DN> all_subjects() const override; + + /** + * Find a certificate by Subject DN and (optionally) key identifier + * @return the first certificate that matches + */ + std::shared_ptr<const X509_Certificate> find_cert( + const X509_DN& subject_dn, + const std::vector<uint8_t>& key_id) const override; + + /** + * Find all certificates with a given Subject DN. + * Subject DN and even the key identifier might not be unique. + */ + std::vector<std::shared_ptr<const X509_Certificate>> find_all_certs( + const X509_DN& subject_dn, const std::vector<uint8_t>& key_id) const override; + + /** + * Find a certificate by searching for one with a matching SHA-1 hash of + * public key. + * @return a matching certificate or nullptr otherwise + */ + std::shared_ptr<const X509_Certificate> + find_cert_by_pubkey_sha1(const std::vector<uint8_t>& key_hash) const override; + + /** + * @throws Botan::Not_Implemented + */ + std::shared_ptr<const X509_Certificate> + find_cert_by_raw_subject_dn_sha256(const std::vector<uint8_t>& subject_hash) const override; + + /** + * Not Yet Implemented + * @return nullptr; + */ + std::shared_ptr<const X509_CRL> find_crl_for(const X509_Certificate& subject) const override; + }; +} + +#endif diff --git a/src/lib/x509/certstor_system_windows/info.txt b/src/lib/x509/certstor_system_windows/info.txt new file mode 100644 index 000000000..8d6dce6c6 --- /dev/null +++ b/src/lib/x509/certstor_system_windows/info.txt @@ -0,0 +1,15 @@ +<defines> +CERTSTOR_WINDOWS -> 20190430 +</defines> + +<os_features> +win32,certificate_store +</os_features> + +<header:public> +certstor_windows.h +</header:public> + +<libs> +windows -> crypt32.lib +</libs>
\ No newline at end of file diff --git a/src/tests/test_certstor_system.cpp b/src/tests/test_certstor_system.cpp index 854bf4bae..48bcc7246 100644 --- a/src/tests/test_certstor_system.cpp +++ b/src/tests/test_certstor_system.cpp @@ -76,6 +76,33 @@ Test::Result find_cert_by_subject_dn(Botan::Certificate_Store& certstore) return result; } +Test::Result find_cert_by_utf8_subject_dn(Botan::Certificate_Store& certstore) + { + Test::Result result("System Certificate Store - Find Certificate by UTF8 subject DN"); + + try + { + auto dn = get_utf8_dn(); + + result.start_timer(); + auto cert = certstore.find_cert(dn, std::vector<uint8_t>()); + result.end_timer(); + + if(result.test_not_null("found certificate", cert.get())) + { + auto cns = cert->subject_dn().get_attribute("CN"); + result.test_is_eq("exactly one CN", cns.size(), size_t(1)); + result.test_eq("CN", cns.front(), "D-TRUST Root Class 3 CA 2 EV 2009"); + } + } + catch(std::exception& e) + { + result.test_failure(e.what()); + } + + return result; + } + Test::Result find_cert_by_subject_dn_and_key_id(Botan::Certificate_Store& certstore) { Test::Result result("System Certificate Store - Find Certificate by subject DN and key ID"); @@ -131,6 +158,43 @@ Test::Result find_certs_by_subject_dn_and_key_id(Botan::Certificate_Store& certs return result; } +Test::Result find_all_certs_by_subject_dn(Botan::Certificate_Store& certstore) + { + Test::Result result("System Certificate Store - Find all Certificates by subject DN"); + + try + { + auto dn = get_dn(); + + result.start_timer(); + auto certs = certstore.find_all_certs(dn, std::vector<uint8_t>()); + result.end_timer(); + + // check for duplications + sort(certs.begin(), certs.end()); + for(int i = 1; i < certs.size(); ++i) + { + if(certs[i=1] == certs[i]) + { + result.test_failure("find_all_certs produced duplicated result"); + } + } + + if(result.confirm("result not empty", !certs.empty())) + { + auto cns = certs.front()->subject_dn().get_attribute("CN"); + result.test_gte("at least one CN", cns.size(), size_t(1)); + result.test_eq("CN", cns.front(), "DST Root CA X3"); + } + } + catch(std::exception& e) + { + result.test_failure(e.what()); + } + + return result; + } + Test::Result find_all_subjects(Botan::Certificate_Store& certstore) { Test::Result result("System Certificate Store - Find all Certificate Subjects"); @@ -241,6 +305,7 @@ class Certstor_System_Tests final : public Test } catch(Botan::Not_Implemented& e) { + BOTAN_UNUSED(e); open_result.test_note("Skipping due to not available in current build"); return {open_result}; } @@ -258,9 +323,13 @@ class Certstor_System_Tests final : public Test results.push_back(find_certificate_by_pubkey_sha1(*system)); results.push_back(find_cert_by_subject_dn(*system)); results.push_back(find_cert_by_subject_dn_and_key_id(*system)); + results.push_back(find_all_certs_by_subject_dn(*system)); results.push_back(find_certs_by_subject_dn_and_key_id(*system)); results.push_back(find_all_subjects(*system)); results.push_back(no_certificate_matches(*system)); +#if !defined(BOTAN_HAS_CERTSTOR_WINDOWS) + results.push_back(find_cert_by_utf8_subject_dn(*system)); +#endif #if defined(BOTAN_HAS_CERTSTOR_MACOS) results.push_back(certificate_matching_with_dn_normalization(*system)); #endif diff --git a/src/tests/test_certstor_utils.cpp b/src/tests/test_certstor_utils.cpp index 007271cc8..68b0f8010 100644 --- a/src/tests/test_certstor_utils.cpp +++ b/src/tests/test_certstor_utils.cpp @@ -24,7 +24,7 @@ Botan::X509_DN read_dn(const std::string hex) Botan::X509_DN get_dn() { - // Public key fingerprint of "DST Root CA X3" + // ASN.1 encoded subject DN of "DST Root CA X3" // This certificate is in the standard "System Roots" of any macOS setup, // serves as the trust root of botan.randombit.net and expires on // Thursday, 30. September 2021 at 16:01:15 Central European Summer Time |