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