Repository navigation
Pass the server's intermediates to Windows certificate verification - #2602
Merged
Merged
Conversation
yhirose
force-pushed
the
windows-verify-intermediates
branch
from
October 2, 2026 13:30
1cdd4ed to
086a648
Compare
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
yhirose
force-pushed
the
windows-verify-intermediates
branch
from
October 2, 2026 19:29
086a648 to
75cc18b
Compare
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Refs #2596
This fixes a regression from v0.31.0 (#2346). Up to v0.30.2, the backend verified the chain against the Windows root store, and that was the only check. #2346 added a CryptoAPI check that runs after the backend succeeds, and gave it only the leaf. Since then, servers like the one below fail even when their chain is valid.
Problem
When Windows certificate verification is enabled (the default on Windows),
verify_cert_with_windows_schannel()gives CryptoAPI only the leaf certificate. CryptoAPI then follows the leaf's AIA URL to find an issuer instead of using the intermediates the server sent. This happens with every TLS backend.accounts.spotify.comshows the problem:Certainly Intermediate R1cross-signed byStarfield Root Certificate Authority - G2, which Windows trusts.Certainly Intermediate R1issued byCertainly Root R1, which Windows does not trust.So the connection fails with
ssl_backend_error=16777312(0x01000060=CERT_TRUST_IS_UNTRUSTED_ROOT | CERT_TRUST_REVOCATION_STATUS_UNKNOWN | CERT_TRUST_IS_OFFLINE_REVOCATION), even though the backend has already verified the chain.Fix
tls::get_peer_certs()for OpenSSL, Mbed TLS and wolfSSL. It returns the certificates the peer sent, leaf first, and the caller frees each one withfree_cert(), as withget_ca_certs().SESSION_CERTS. Without it, the function returns nothing and CryptoAPI gets the leaf alone, as before.CertGetCertificateChain()ashAdditionalStore. This part is shared by all backends.Verification
windows-latestGitHub Actions runner with OpenSSL 3.x and the stock root store,accounts.spotify.comused to fail with0x01000060. It now succeeds. Self-signed, expired, untrusted-root and hostname-mismatch sites on badssl.com are still rejected as before.get_peer_certs()was checked against a local server that sends a two-certificate chain. Each backend returned both certificates with the leaf first, on the client side and on the server side (client certificate). AddressSanitizer reported no errors.SSLClientTest.WindowsCertificateVerification_ServerIntermediates_Online. Windows CI skips*_Onlinetests, so it does not run there.This does not change which roots are trusted. A root that Windows has not fetched into its store yet (
ssl_backend_error=20in #2596) is still rejected by every backend.