mirror of
https://github.com/Icinga/icinga2.git
synced 2025-08-25 03:28:12 +02:00
181 lines
6.1 KiB
C++
181 lines
6.1 KiB
C++
//
|
|
// Copyright (c) 2021 Kasper Laudrup (laudrup at stacktrace dot dk)
|
|
//
|
|
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
|
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
|
//
|
|
|
|
#ifndef BOOST_WINTLS_DETAIL_CONTEXT_CERTIFICATES_HPP
|
|
#define BOOST_WINTLS_DETAIL_CONTEXT_CERTIFICATES_HPP
|
|
|
|
#include <boost/wintls/detail/config.hpp>
|
|
|
|
#include <boost/wintls/certificate.hpp>
|
|
#include <boost/wintls/error.hpp>
|
|
|
|
#include <cstdlib>
|
|
#include <memory>
|
|
#include <string>
|
|
#include <type_traits>
|
|
|
|
namespace boost {
|
|
namespace wintls {
|
|
namespace detail {
|
|
|
|
struct cert_store_deleter {
|
|
void operator()(HCERTSTORE store) {
|
|
CertCloseStore(store, 0);
|
|
}
|
|
};
|
|
|
|
using cert_store_ptr = std::unique_ptr<std::remove_pointer_t<HCERTSTORE>, cert_store_deleter>;
|
|
|
|
class context_certificates {
|
|
public:
|
|
void add_certificate_authority(const CERT_CONTEXT* cert) {
|
|
init_cert_store();
|
|
if(!CertAddCertificateContextToStore(cert_store_.get(),
|
|
cert,
|
|
CERT_STORE_ADD_ALWAYS,
|
|
nullptr)) {
|
|
throw_last_error("CertAddCertificateContextToStore");
|
|
}
|
|
}
|
|
|
|
void add_crl(const CRL_CONTEXT* crl_ctx) {
|
|
init_cert_store();
|
|
if (!CertAddCRLContextToStore(cert_store_.get(),
|
|
crl_ctx,
|
|
CERT_STORE_ADD_ALWAYS,
|
|
nullptr)) {
|
|
throw_last_error("CertAddCRLContextToStore");
|
|
}
|
|
}
|
|
|
|
HRESULT verify_certificate(const CERT_CONTEXT* cert, const std::string& server_hostname, bool check_revocation) {
|
|
HRESULT status = CERT_E_UNTRUSTEDROOT;
|
|
|
|
if (cert_store_) {
|
|
CERT_CHAIN_ENGINE_CONFIG chain_engine_config{};
|
|
chain_engine_config.cbSize = sizeof(chain_engine_config);
|
|
chain_engine_config.hExclusiveRoot = cert_store_.get();
|
|
|
|
struct cert_chain_engine {
|
|
~cert_chain_engine() {
|
|
CertFreeCertificateChainEngine(ptr);
|
|
}
|
|
HCERTCHAINENGINE ptr = nullptr;
|
|
} chain_engine;
|
|
|
|
if (!CertCreateCertificateChainEngine(&chain_engine_config, &chain_engine.ptr)) {
|
|
return static_cast<HRESULT>(GetLastError());
|
|
}
|
|
|
|
status = static_cast<HRESULT>(verify_certificate_chain(cert, chain_engine.ptr, server_hostname, check_revocation));
|
|
}
|
|
|
|
if (status != ERROR_SUCCESS && use_default_cert_store) {
|
|
// Calling CertGetCertificateChain with a NULL pointer engine uses
|
|
// the default system certificate store
|
|
status = static_cast<HRESULT>(verify_certificate_chain(cert, nullptr, server_hostname, check_revocation));
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
void use_certificate(const CERT_CONTEXT* cert) {
|
|
HCRYPTPROV_OR_NCRYPT_KEY_HANDLE unused_0;
|
|
DWORD unused_1;
|
|
BOOL unused_2;
|
|
if (!CryptAcquireCertificatePrivateKey(cert,
|
|
CRYPT_ACQUIRE_COMPARE_KEY_FLAG,
|
|
nullptr,
|
|
&unused_0,
|
|
&unused_1,
|
|
&unused_2)) {
|
|
detail::throw_last_error("CryptAcquireCertificatePrivateKey");
|
|
}
|
|
server_cert_ = cert_context_ptr{CertDuplicateCertificateContext(cert)};
|
|
}
|
|
|
|
const CERT_CONTEXT* server_cert() const {
|
|
return server_cert_.get();
|
|
}
|
|
|
|
bool use_default_cert_store = false;
|
|
|
|
private:
|
|
void init_cert_store() {
|
|
if (!cert_store_) {
|
|
cert_store_ = cert_store_ptr{CertOpenStore(CERT_STORE_PROV_MEMORY, 0, 0, 0, nullptr)};
|
|
if (!cert_store_) {
|
|
throw_last_error("CertOpenStore");
|
|
}
|
|
}
|
|
}
|
|
|
|
DWORD verify_certificate_chain(const CERT_CONTEXT* cert,
|
|
HCERTCHAINENGINE engine,
|
|
const std::string& server_hostname,
|
|
bool check_revocation) {
|
|
CERT_CHAIN_PARA chain_parameters{};
|
|
chain_parameters.cbSize = sizeof(chain_parameters);
|
|
|
|
const CERT_CHAIN_CONTEXT* chain_ctx_ptr;
|
|
DWORD chain_flags = check_revocation ? CERT_CHAIN_REVOCATION_CHECK_CHAIN_EXCLUDE_ROOT
|
|
: 0;
|
|
if(!CertGetCertificateChain(engine,
|
|
cert,
|
|
nullptr,
|
|
cert->hCertStore,
|
|
&chain_parameters,
|
|
chain_flags,
|
|
nullptr,
|
|
&chain_ctx_ptr)) {
|
|
return GetLastError();
|
|
}
|
|
|
|
std::unique_ptr<const CERT_CHAIN_CONTEXT, decltype(&CertFreeCertificateChain)>
|
|
scoped_chain_ctx{chain_ctx_ptr, &CertFreeCertificateChain};
|
|
// We could return here if (scoped_chain_ctx->TrustStatus.dwErrorStatus != CERT_TRUST_NO_ERROR),
|
|
// but we would have to map the error code to a system error ourselves,.
|
|
// Instead we rely on CertVerifyCertificateChainPolicy to obtain the appropriate system error.
|
|
|
|
HTTPSPolicyCallbackData https_policy{};
|
|
https_policy.cbStruct = sizeof(https_policy);
|
|
https_policy.dwAuthType = AUTHTYPE_SERVER;
|
|
// add parameter to verify the host name if it was actually set
|
|
std::wstring whostname;
|
|
if (!server_hostname.empty()) {
|
|
whostname.resize(server_hostname.size(), L' ');
|
|
whostname.resize(std::mbstowcs(&whostname[0], server_hostname.data(), server_hostname.size()));
|
|
https_policy.pwszServerName = &whostname[0];
|
|
}
|
|
|
|
CERT_CHAIN_POLICY_PARA policy_params{};
|
|
policy_params.cbSize = sizeof(policy_params);
|
|
policy_params.pvExtraPolicyPara = &https_policy;
|
|
|
|
CERT_CHAIN_POLICY_STATUS policy_status{};
|
|
policy_status.cbSize = sizeof(policy_status);
|
|
|
|
if(!CertVerifyCertificateChainPolicy(CERT_CHAIN_POLICY_SSL,
|
|
scoped_chain_ctx.get(),
|
|
&policy_params,
|
|
&policy_status)) {
|
|
return GetLastError();
|
|
}
|
|
|
|
return policy_status.dwError;
|
|
}
|
|
|
|
cert_store_ptr cert_store_{};
|
|
cert_context_ptr server_cert_{};
|
|
};
|
|
|
|
} // namespace detail
|
|
} // namespace wintls
|
|
} // namespace boost
|
|
|
|
#endif // BOOST_WINTLS_DETAIL_CONTEXT_CERTIFICATES_HPP
|