Compare commits

..
1 Commits
Author SHA1 Message Date
yhirose 086a648364 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
2026-10-02 09:25:55 -04:00
+14 -67
View File
@@ -5120,9 +5120,6 @@ bool is_peer_closed(session_t session, socket_t sock);
// Certificate verification
cert_t get_peer_cert(const_session_t session);
// The certificates the peer sent, leaf first. Free each with free_cert(), and
// do not use them after free_session(), as with get_peer_cert().
size_t get_peer_certs(const_session_t session, std::vector<cert_t> &certs);
void free_cert(cert_t cert);
bool verify_hostname(cert_t cert, const char *hostname);
uint64_t hostname_mismatch_code();
@@ -10721,25 +10718,23 @@ inline bool verify_cert_with_windows_schannel(
auto cert_guard =
scope_exit([&] { CertFreeCertificateContext(cert_context); });
// Give CryptoAPI the certificates the server sent. Without them it follows
// the leaf's AIA URL, which may lead to an issuer under an untrusted root.
std::vector<tls::cert_t> peer_certs;
tls::get_peer_certs(session, peer_certs);
#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([&] {
for (auto cert : peer_certs) {
tls::free_cert(cert);
}
if (store) { CertCloseStore(store, 0); }
});
for (auto cert : peer_certs) {
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;
if (store && tls::get_cert_der(cert, der)) {
CertAddEncodedCertificateToStore(store, X509_ASN_ENCODING, der.data(),
static_cast<DWORD>(der.size()),
CERT_STORE_ADD_USE_EXISTING, nullptr);
}
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 = {};
@@ -19545,24 +19540,6 @@ inline cert_t get_peer_cert(const_session_t session) {
static_cast<SSL *>(const_cast<void *>(session))));
}
inline size_t get_peer_certs(const_session_t session,
std::vector<cert_t> &certs) {
certs.clear();
if (!session) { return 0; }
auto ssl = static_cast<const SSL *>(session);
// On the server side, the chain leaves out the peer's own certificate
if (SSL_is_server(ssl)) {
if (auto leaf = get_peer_cert(session)) { certs.push_back(leaf); }
}
auto sk = SSL_get_peer_cert_chain(ssl);
for (int i = 0; sk && i < sk_X509_num(sk); i++) {
auto x509 = sk_X509_value(sk, i);
X509_up_ref(x509);
certs.push_back(static_cast<cert_t>(x509));
}
return certs.size();
}
inline void free_cert(cert_t cert) {
if (cert) { X509_free(static_cast<X509 *>(cert)); }
}
@@ -20907,18 +20884,6 @@ inline cert_t get_peer_cert(const_session_t session) {
return const_cast<mbedtls_x509_crt *>(cert);
}
inline size_t get_peer_certs(const_session_t session,
std::vector<cert_t> &certs) {
certs.clear();
// Mbed TLS parses the whole received chain into a list headed by the peer
// certificate, owned by the session like get_peer_cert()'s result
for (auto crt = static_cast<mbedtls_x509_crt *>(get_peer_cert(session));
crt && crt->raw.len > 0; crt = crt->next) {
certs.push_back(static_cast<cert_t>(crt));
}
return certs.size();
}
inline void free_cert(cert_t cert) {
// Mbed TLS: peer certificate is owned by the SSL context.
// No-op here, but callers should still call this for cross-backend
@@ -22076,24 +22041,6 @@ inline cert_t get_peer_cert(const_session_t session) {
return static_cast<cert_t>(cert);
}
inline size_t get_peer_certs(const_session_t session,
std::vector<cert_t> &certs) {
certs.clear();
if (!session) { return 0; }
// wolfSSL keeps the received chain only when built with SESSION_CERTS
#ifdef SESSION_CERTS
auto wsession =
static_cast<impl::WolfSSLSession *>(const_cast<void *>(session));
auto chain = wolfSSL_get_peer_chain(wsession->ssl);
auto count = chain ? wolfSSL_get_chain_count(chain) : 0;
for (int i = 0; i < count; i++) {
auto x509 = wolfSSL_get_chain_X509(chain, i);
if (x509) { certs.push_back(static_cast<cert_t>(x509)); }
}
#endif
return certs.size();
}
inline void free_cert(cert_t cert) {
if (cert) { wolfSSL_X509_free(static_cast<WOLFSSL_X509 *>(cert)); }
}