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