Botan 3.8.1
Crypto and TLS for C&
certstor_windows.cpp
Go to the documentation of this file.
1/*
2* Certificate Store
3* (C) 1999-2021 Jack Lloyd
4* (C) 2018-2019 Patrik Fiedler, Tim Oesterreich
5* (C) 2021 René Meusel
6*
7* Botan is released under the Simplified BSD License (see license.txt)
8*/
9
10#include <botan/certstor_windows.h>
11
12#include <botan/assert.h>
13#include <botan/ber_dec.h>
14#include <botan/hash.h>
15#include <botan/pkix_types.h>
16#include <algorithm>
17#include <array>
18#include <functional>
19#include <vector>
20
21#define NOMINMAX 1
22#define _WINSOCKAPI_ // stop windows.h including winsock.h
23#include <windows.h>
24
25#include <wincrypt.h>
26
27#define WINCRYPT_UNUSED_PARAM \
28 0 // for avoiding warnings when passing NULL to unused params in win32 api that accept integer types
29
30namespace Botan {
31namespace {
32
33const std::array<const char*, 2> cert_store_names{"Root", "CA"};
34
35/**
36 * Abstract RAII wrapper for PCCERT_CONTEXT and HCERTSTORE
37 * The Windows API partly takes care of those pointers destructions itself.
38 * Especially, iteratively calling `CertFindCertificateInStore` with the previous PCCERT_CONTEXT
39 * will free the context and return a new one. In this case, this guard takes care of freeing the context
40 * in case of an exception and at the end of the iterative process.
41 */
42template <class T>
43class Handle_Guard {
44 public:
45 Handle_Guard(T context) : m_context(context) {}
46
47 Handle_Guard(const Handle_Guard<T>& rhs) = delete;
48
49 Handle_Guard(Handle_Guard<T>&& rhs) : m_context(std::move(rhs.m_context)) { rhs.m_context = nullptr; }
50
51 ~Handle_Guard() { close<T>(); }
52
53 operator bool() const { return m_context != nullptr; }
54
55 bool assign(T context) {
56 m_context = context;
57 return m_context != nullptr;
58 }
59
60 T& get() { return m_context; }
61
62 const T& get() const { return m_context; }
63
64 T operator->() { return m_context; }
65
66 private:
67 template <class T2 = T>
68 typename std::enable_if<std::is_same<T2, PCCERT_CONTEXT>::value>::type close() {
69 if(m_context) {
70 CertFreeCertificateContext(m_context);
71 }
72 }
73
74 template <class T2 = T>
75 typename std::enable_if<std::is_same<T2, HCERTSTORE>::value>::type close() {
76 if(m_context) {
77 // second parameter is a flag that tells the store how to deallocate memory
78 // using the default "0", this function works like decreasing the reference counter
79 // in a shared_ptr
80 CertCloseStore(m_context, 0);
81 }
82 }
83
84 T m_context;
85};
86
87HCERTSTORE open_cert_store(const char* cert_store_name) {
88 auto store = CertOpenSystemStoreA(WINCRYPT_UNUSED_PARAM, cert_store_name);
89 if(!store) {
90 throw Internal_Error("failed to open windows certificate store '" + std::string(cert_store_name) +
91 "' (Error Code: " + std::to_string(::GetLastError()) + ")");
92 }
93 return store;
94}
95
96std::vector<X509_Certificate> search_cert_stores(
97 const _CRYPTOAPI_BLOB& blob,
98 const DWORD& find_type,
99 std::function<bool(const std::vector<X509_Certificate>& certs, const X509_Certificate& cert)> filter,
100 bool return_on_first_found) {
101 std::vector<X509_Certificate> certs;
102 for(const auto store_name : cert_store_names) {
103 Handle_Guard<HCERTSTORE> windows_cert_store = open_cert_store(store_name);
104 Handle_Guard<PCCERT_CONTEXT> cert_context = nullptr;
105 while(cert_context.assign(CertFindCertificateInStore(windows_cert_store.get(),
106 PKCS_7_ASN_ENCODING | X509_ASN_ENCODING,
108 find_type,
109 &blob,
110 cert_context.get()))) {
111 X509_Certificate cert(cert_context->pbCertEncoded, cert_context->cbCertEncoded);
112 if(filter(certs, cert)) {
113 if(return_on_first_found) {
114 return {cert};
115 }
116 certs.push_back(cert);
117 }
118 }
119 }
120
121 return certs;
122}
123
124bool already_contains_certificate(const std::vector<X509_Certificate>& certs, X509_Certificate cert) {
125 return std::any_of(certs.begin(), certs.end(), [&](const X509_Certificate& c) { return c == cert; });
126}
127
128std::vector<X509_Certificate> find_cert_by_dn_and_key_id(const X509_DN& subject_dn,
129 const std::vector<uint8_t>& key_id,
130 bool return_on_first_found) {
131 _CRYPTOAPI_BLOB blob;
132 DWORD find_type;
133 std::vector<uint8_t> dn_data; // has to live until search completes
134
135 // if key_id is available, prefer searching that, as it should be "more unique" than the subject DN
136 if(key_id.empty()) {
137 find_type = CERT_FIND_SUBJECT_NAME;
138 dn_data = subject_dn.DER_encode();
139 blob.cbData = static_cast<DWORD>(dn_data.size());
140 blob.pbData = reinterpret_cast<BYTE*>(dn_data.data());
141 } else {
142 find_type = CERT_FIND_KEY_IDENTIFIER;
143 blob.cbData = static_cast<DWORD>(key_id.size());
144 blob.pbData = const_cast<BYTE*>(key_id.data());
145 }
146
147 auto filter = [&](const std::vector<X509_Certificate>& certs, const X509_Certificate& cert) {
148 return !already_contains_certificate(certs, cert) && (key_id.empty() || cert.subject_dn() == subject_dn);
149 };
150
151 return search_cert_stores(blob, find_type, filter, return_on_first_found);
152}
153
154} // namespace
155
157
158std::vector<X509_DN> Certificate_Store_Windows::all_subjects() const {
159 std::vector<X509_DN> subject_dns;
160 for(const auto store_name : cert_store_names) {
161 Handle_Guard<HCERTSTORE> windows_cert_store = open_cert_store(store_name);
162 Handle_Guard<PCCERT_CONTEXT> cert_context = nullptr;
163
164 // Handle_Guard::assign exchanges the underlying pointer. No RAII is needed here, because the Windows API takes care of
165 // freeing the previous context.
166 while(cert_context.assign(CertEnumCertificatesInStore(windows_cert_store.get(), cert_context.get()))) {
167 BER_Decoder dec(cert_context->pCertInfo->Subject.pbData, cert_context->pCertInfo->Subject.cbData);
168
169 X509_DN dn;
170 dn.decode_from(dec);
171 subject_dns.emplace_back(std::move(dn));
172 }
173 }
174
175 return subject_dns;
176}
177
178std::optional<X509_Certificate> Certificate_Store_Windows::find_cert(const X509_DN& subject_dn,
179 const std::vector<uint8_t>& key_id) const {
180 const auto certs = find_cert_by_dn_and_key_id(subject_dn, key_id, true);
181 if(certs.empty())
182 return std::nullopt;
183 else
184 return certs.front();
185}
186
187std::vector<X509_Certificate> Certificate_Store_Windows::find_all_certs(const X509_DN& subject_dn,
188 const std::vector<uint8_t>& key_id) const {
189 return find_cert_by_dn_and_key_id(subject_dn, key_id, false);
190}
191
193 const std::vector<uint8_t>& key_hash) const {
194 if(key_hash.size() != 20) {
195 throw Invalid_Argument("Certificate_Store_Windows::find_cert_by_pubkey_sha1 invalid hash");
196 }
197
198 CRYPT_HASH_BLOB blob;
199 blob.cbData = static_cast<DWORD>(key_hash.size());
200 blob.pbData = const_cast<BYTE*>(key_hash.data());
201
202 auto filter = [&](const std::vector<X509_Certificate>&, const X509_Certificate&) { return true; };
203
204 // This assumes that the to-be-found certificate adheres to RFC 3280 (Section 4.2.1.2), because
205 // the windows API does not allow to search for the SHA-1 of the certificate's public key directly.
206 // Most certificates adhere to the above mentioned convention...
207 const auto certs = search_cert_stores(blob, CERT_FIND_KEY_IDENTIFIER, filter, true);
208 if(!certs.empty())
209 return certs.front();
210
211 // ... for certificates that don't adhere to RFC 3280 (Section 4.2.1.2), we will need to use a
212 // fallback implementation that does an exhaustive search of all available certificates.
213 return find_cert_by_pubkey_sha1_via_exhaustive_search(key_hash);
214}
215
217 const std::vector<uint8_t>& subject_hash) const {
218 BOTAN_UNUSED(subject_hash);
219 throw Not_Implemented("Certificate_Store_Windows::find_cert_by_raw_subject_dn_sha256");
220}
221
222std::optional<X509_CRL> Certificate_Store_Windows::find_crl_for(const X509_Certificate& subject) const {
223 // TODO: this could be implemented by using the CertFindCRLInStore function
224 BOTAN_UNUSED(subject);
225 return std::nullopt;
226}
227
228std::optional<X509_Certificate> Certificate_Store_Windows::find_cert_by_pubkey_sha1_via_exhaustive_search(
229 const std::vector<uint8_t>& key_hash) const {
230 if(const auto cache_hit = m_non_rfc3289_certs.find(key_hash); cache_hit != m_non_rfc3289_certs.end()) {
231 return cache_hit->second;
232 }
233
234 auto sha1 = HashFunction::create_or_throw("SHA-1");
235 for(const auto store_name : cert_store_names) {
236 Handle_Guard<HCERTSTORE> windows_cert_store = open_cert_store(store_name);
237 Handle_Guard<PCCERT_CONTEXT> cert_context = nullptr;
238
239 // Handle_Guard::assign exchanges the underlying pointer. No RAII is needed here, because the Windows API takes care of
240 // freeing the previous context.
241 while(cert_context.assign(CertEnumCertificatesInStore(windows_cert_store.get(), cert_context.get()))) {
242 const auto pubkey_blob = cert_context->pCertInfo->SubjectPublicKeyInfo.PublicKey;
243 const auto key_hash_candidate = sha1->process(static_cast<uint8_t*>(pubkey_blob.pbData), pubkey_blob.cbData);
244
245 if(std::equal(key_hash.begin(), key_hash.end(), key_hash_candidate.begin())) {
246 X509_Certificate result(cert_context->pbCertEncoded, cert_context->cbCertEncoded);
247 m_non_rfc3289_certs[key_hash] = result;
248 return result;
249 }
250 }
251 }
252
253 // insert a negative query result into the cache
254 m_non_rfc3289_certs[key_hash] = std::nullopt;
255 return std::nullopt;
256}
257
258} // namespace Botan
#define BOTAN_UNUSED
Definition assert.h:120
#define WINCRYPT_UNUSED_PARAM
std::vector< X509_DN > all_subjects() const override
std::optional< X509_Certificate > find_cert_by_pubkey_sha1(const std::vector< uint8_t > &key_hash) const override
std::optional< X509_Certificate > find_cert(const X509_DN &subject_dn, const std::vector< uint8_t > &key_id) const override
std::optional< X509_Certificate > find_cert_by_raw_subject_dn_sha256(const std::vector< uint8_t > &subject_hash) const override
std::optional< X509_CRL > find_crl_for(const X509_Certificate &subject) const override
std::vector< X509_Certificate > find_all_certs(const X509_DN &subject_dn, const std::vector< uint8_t > &key_id) const override
static std::unique_ptr< HashFunction > create_or_throw(std::string_view algo_spec, std::string_view provider="")
Definition hash.cpp:298
void decode_from(BER_Decoder &) override
Definition x509_dn.cpp:347