Compare commits

..
2 Commits
Author SHA1 Message Date
yhirose 39489ee376 Skip certificates that cannot be added to the CryptoAPI store
Also stop the Mbed TLS chain walk at an empty entry, as get_ca_certs()
does, and note that get_peer_certs() results share the session's
lifetime like get_peer_cert()'s.
2026-10-02 15:55:09 -04:00
yhirose 75cc18b966 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 tls::get_peer_certs(), which returns the certificates the peer sent
in the same way get_ca_certs() returns the CA certificates, for every
backend. The CryptoAPI check puts them into a memory store that it
passes to CertGetCertificateChain(). wolfSSL keeps the received chain
only when built with SESSION_CERTS; without it, CryptoAPI still gets the
leaf alone.

Refs #2596
2026-10-02 15:29:37 -04:00
+67 -14
View File
@@ -5120,6 +5120,9 @@ 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();
@@ -10718,23 +10721,25 @@ inline bool verify_cert_with_windows_schannel(
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.
// 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);
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++) {
auto store_guard = scope_exit([&] {
for (auto cert : peer_certs) {
tls::free_cert(cert);
}
if (store) { CertCloseStore(store, 0); }
});
for (auto cert : peer_certs) {
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);
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);
}
}
#else
(void)session;
auto store = cert_context->hCertStore;
#endif
// Setup chain parameters
CERT_CHAIN_PARA chain_para = {};
@@ -19540,6 +19545,24 @@ 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)); }
}
@@ -20884,6 +20907,18 @@ 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
@@ -22041,6 +22076,24 @@ 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)); }
}