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