mirror of
https://github.com/yhirose/cpp-httplib.git
synced 2026-10-03 05:53:10 +07:00
Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9af2341702 | ||
|
|
086a648364 |
@@ -10698,10 +10698,9 @@ inline bool match_hostname(const std::string &pattern,
|
|||||||
// Verify certificate using Windows CertGetCertificateChain API.
|
// Verify certificate using Windows CertGetCertificateChain API.
|
||||||
// This provides real-time certificate validation with Windows Update
|
// This provides real-time certificate validation with Windows Update
|
||||||
// integration, independent of the TLS backend (OpenSSL or MbedTLS).
|
// integration, independent of the TLS backend (OpenSSL or MbedTLS).
|
||||||
inline bool
|
inline bool verify_cert_with_windows_schannel(
|
||||||
verify_cert_with_windows_schannel(const std::vector<unsigned char> &der_cert,
|
const std::vector<unsigned char> &der_cert, const std::string &hostname,
|
||||||
const std::string &hostname,
|
bool verify_hostname, uint64_t &out_error, tls::const_session_t session) {
|
||||||
bool verify_hostname, uint64_t &out_error) {
|
|
||||||
if (der_cert.empty()) { return false; }
|
if (der_cert.empty()) { return false; }
|
||||||
|
|
||||||
out_error = 0;
|
out_error = 0;
|
||||||
@@ -10719,14 +10718,35 @@ verify_cert_with_windows_schannel(const std::vector<unsigned char> &der_cert,
|
|||||||
auto cert_guard =
|
auto cert_guard =
|
||||||
scope_exit([&] { CertFreeCertificateContext(cert_context); });
|
scope_exit([&] { CertFreeCertificateContext(cert_context); });
|
||||||
|
|
||||||
// Setup chain parameters
|
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
||||||
|
// Add the intermediates the server sent. Without them CryptoAPI follows the
|
||||||
|
// leaf's AIA URL, which may lead to an issuer under an untrusted root.
|
||||||
|
auto store = CertOpenStore(CERT_STORE_PROV_MEMORY, 0, 0, 0, nullptr);
|
||||||
|
auto store_guard = scope_exit([&] { CertCloseStore(store, 0); });
|
||||||
|
auto sk = SSL_get_peer_cert_chain(static_cast<const SSL *>(session));
|
||||||
|
for (int i = 1; sk && i < sk_X509_num(sk); i++) {
|
||||||
|
std::vector<unsigned char> der;
|
||||||
|
tls::get_cert_der(sk_X509_value(sk, i), der);
|
||||||
|
CertAddEncodedCertificateToStore(store, X509_ASN_ENCODING, der.data(),
|
||||||
|
static_cast<DWORD>(der.size()),
|
||||||
|
CERT_STORE_ADD_USE_EXISTING, nullptr);
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
(void)session;
|
||||||
|
auto store = cert_context->hCertStore;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Setup chain parameters. The SSL policy does not check the key usage.
|
||||||
|
LPSTR server_auth = const_cast<LPSTR>(szOID_PKIX_KP_SERVER_AUTH);
|
||||||
CERT_CHAIN_PARA chain_para = {};
|
CERT_CHAIN_PARA chain_para = {};
|
||||||
chain_para.cbSize = sizeof(chain_para);
|
chain_para.cbSize = sizeof(chain_para);
|
||||||
|
chain_para.RequestedUsage.Usage.cUsageIdentifier = 1;
|
||||||
|
chain_para.RequestedUsage.Usage.rgpszUsageIdentifier = &server_auth;
|
||||||
|
|
||||||
// Build certificate chain with revocation checking
|
// Build certificate chain with revocation checking
|
||||||
PCCERT_CHAIN_CONTEXT chain_context = nullptr;
|
PCCERT_CHAIN_CONTEXT chain_context = nullptr;
|
||||||
auto chain_result = CertGetCertificateChain(
|
auto chain_result = CertGetCertificateChain(
|
||||||
nullptr, cert_context, nullptr, cert_context->hCertStore, &chain_para,
|
nullptr, cert_context, nullptr, store, &chain_para,
|
||||||
CERT_CHAIN_CACHE_END_CERT | CERT_CHAIN_REVOCATION_CHECK_END_CERT |
|
CERT_CHAIN_CACHE_END_CERT | CERT_CHAIN_REVOCATION_CHECK_END_CERT |
|
||||||
CERT_CHAIN_REVOCATION_ACCUMULATIVE_TIMEOUT,
|
CERT_CHAIN_REVOCATION_ACCUMULATIVE_TIMEOUT,
|
||||||
nullptr, &chain_context);
|
nullptr, &chain_context);
|
||||||
@@ -10919,6 +10939,15 @@ inline bool setup_client_tls_session(
|
|||||||
if (verification_status == SSLVerifierResponse::NoDecisionMade &&
|
if (verification_status == SSLVerifierResponse::NoDecisionMade &&
|
||||||
server_certificate_verification) {
|
server_certificate_verification) {
|
||||||
auto verify_result = get_verify_result(session);
|
auto verify_result = get_verify_result(session);
|
||||||
|
#if defined(CPPHTTPLIB_WINDOWS_AUTOMATIC_ROOT_CERTIFICATES_UPDATE) && \
|
||||||
|
defined(CPPHTTPLIB_OPENSSL_SUPPORT)
|
||||||
|
// Windows adds a root it trusts to its store only when CryptoAPI needs it,
|
||||||
|
// so leave a root missing from the store to the CryptoAPI check below
|
||||||
|
if (options.windows_cert_verification &&
|
||||||
|
verify_result == X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY) {
|
||||||
|
verify_result = X509_V_OK;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
if (verify_result != 0) {
|
if (verify_result != 0) {
|
||||||
return fail(Error::SSLServerVerification, 0,
|
return fail(Error::SSLServerVerification, 0,
|
||||||
static_cast<uint64_t>(verify_result));
|
static_cast<uint64_t>(verify_result));
|
||||||
@@ -10949,10 +10978,12 @@ inline bool setup_client_tls_session(
|
|||||||
if (get_cert_der(server_cert, der)) {
|
if (get_cert_der(server_cert, der)) {
|
||||||
uint64_t wincrypt_error = 0;
|
uint64_t wincrypt_error = 0;
|
||||||
if (!verify_cert_with_windows_schannel(
|
if (!verify_cert_with_windows_schannel(
|
||||||
der, host, options.server_hostname_verification,
|
der, host, options.server_hostname_verification, wincrypt_error,
|
||||||
wincrypt_error)) {
|
session)) {
|
||||||
return fail(Error::SSLServerVerification, 0, wincrypt_error);
|
return fail(Error::SSLServerVerification, 0, wincrypt_error);
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
return fail(Error::SSLServerVerification, 0, get_error());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -13958,6 +13958,43 @@ TEST(SSLClientTest, WindowsCertificateVerification_Disabled) {
|
|||||||
auto res = cli.Get("/");
|
auto res = cli.Get("/");
|
||||||
if (res) { EXPECT_NE(StatusCode::InternalServerError_500, res->status); }
|
if (res) { EXPECT_NE(StatusCode::InternalServerError_500, res->status); }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The server sends an intermediate cross-signed by a root Windows trusts,
|
||||||
|
// while the leaf's AIA URL leads to one under a root Windows does not trust.
|
||||||
|
TEST(SSLClientTest, WindowsCertificateVerification_ServerIntermediates_Online) {
|
||||||
|
SSLClient cli("accounts.spotify.com", 443);
|
||||||
|
auto res = cli.Get("/");
|
||||||
|
ASSERT_TRUE(res) << "Error: " << to_string(res.error())
|
||||||
|
<< " ssl_backend_error=" << res.ssl_backend_error();
|
||||||
|
}
|
||||||
|
|
||||||
|
// A root missing from the trust store is left to CryptoAPI, which must still
|
||||||
|
// reject a chain whose root Windows does not trust either.
|
||||||
|
TEST(SSLClientTest, WindowsCertificateVerification_UnknownIssuerRejected) {
|
||||||
|
// Issued by the test root CA, which is not in the Windows root store
|
||||||
|
SSLServer svr(CLIENT_CERT_FILE, CLIENT_PRIVATE_KEY_FILE);
|
||||||
|
ASSERT_TRUE(svr.is_valid());
|
||||||
|
svr.Get("/", [](const Request &, Response &res) {
|
||||||
|
res.set_content("ok", "text/plain");
|
||||||
|
});
|
||||||
|
|
||||||
|
thread t = thread([&]() { ASSERT_TRUE(svr.listen(HOST, PORT)); });
|
||||||
|
auto se = detail::scope_exit([&] {
|
||||||
|
svr.stop();
|
||||||
|
t.join();
|
||||||
|
ASSERT_FALSE(svr.is_running());
|
||||||
|
});
|
||||||
|
|
||||||
|
svr.wait_until_ready();
|
||||||
|
|
||||||
|
SSLClient cli(HOST, PORT);
|
||||||
|
// Keep the hostname check from failing first
|
||||||
|
cli.enable_server_hostname_verification(false);
|
||||||
|
|
||||||
|
auto res = cli.Get("/");
|
||||||
|
ASSERT_FALSE(res);
|
||||||
|
EXPECT_EQ(Error::SSLServerVerification, res.error());
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
TEST(SSLClientTest, ServerCertificateVerification1_Online) {
|
TEST(SSLClientTest, ServerCertificateVerification1_Online) {
|
||||||
|
|||||||
Reference in New Issue
Block a user