瀏覽代碼

Pass the server's intermediates to Windows certificate verification

CryptoAPI got only the leaf, so it fetched an issuer from the leaf's AIA
URL instead of using the intermediates the server sent. For
accounts.spotify.com that issuer chains to Certainly Root R1, which
Windows does not trust, while the server's own chain ends at Starfield
Root G2. Add the server's intermediates to the store CryptoAPI builds
the chain from. This covers the OpenSSL backend.

Refs #2596
yhirose 6 天之前
父節點
當前提交
086a648364
共有 2 個文件被更改,包括 33 次插入 和 7 次删除
  1. 24 7
      httplib.h
  2. 9 0
      test/test.cc

+ 24 - 7
httplib.h

@@ -10698,10 +10698,9 @@ inline bool match_hostname(const std::string &pattern,
 // Verify certificate using Windows CertGetCertificateChain API.
 // This provides real-time certificate validation with Windows Update
 // integration, independent of the TLS backend (OpenSSL or MbedTLS).
-inline bool
-verify_cert_with_windows_schannel(const std::vector<unsigned char> &der_cert,
-                                  const std::string &hostname,
-                                  bool verify_hostname, uint64_t &out_error) {
+inline bool verify_cert_with_windows_schannel(
+    const std::vector<unsigned char> &der_cert, const std::string &hostname,
+    bool verify_hostname, uint64_t &out_error, tls::const_session_t session) {
   if (der_cert.empty()) { return false; }
 
   out_error = 0;
@@ -10719,6 +10718,24 @@ verify_cert_with_windows_schannel(const std::vector<unsigned char> &der_cert,
   auto cert_guard =
       scope_exit([&] { CertFreeCertificateContext(cert_context); });
 
+#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
   CERT_CHAIN_PARA chain_para = {};
   chain_para.cbSize = sizeof(chain_para);
@@ -10726,7 +10743,7 @@ verify_cert_with_windows_schannel(const std::vector<unsigned char> &der_cert,
   // Build certificate chain with revocation checking
   PCCERT_CHAIN_CONTEXT chain_context = nullptr;
   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_REVOCATION_ACCUMULATIVE_TIMEOUT,
       nullptr, &chain_context);
@@ -10949,8 +10966,8 @@ inline bool setup_client_tls_session(
       if (get_cert_der(server_cert, der)) {
         uint64_t wincrypt_error = 0;
         if (!verify_cert_with_windows_schannel(
-                der, host, options.server_hostname_verification,
-                wincrypt_error)) {
+                der, host, options.server_hostname_verification, wincrypt_error,
+                session)) {
           return fail(Error::SSLServerVerification, 0, wincrypt_error);
         }
       }

+ 9 - 0
test/test.cc

@@ -13958,6 +13958,15 @@ TEST(SSLClientTest, WindowsCertificateVerification_Disabled) {
   auto res = cli.Get("/");
   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();
+}
 #endif
 
 TEST(SSLClientTest, ServerCertificateVerification1_Online) {