schannel: allow partial chains for manual peer verification

- Align --cacert behaviour with OpenSSL and LibreSSL.

This changes the default behavior of Schannel manual certificate
verification, which is used when the user provides their own CA
certificates for verification, to accept partial chains. In other words,
the user may provide an intermediate certificate without having to
provide the root CA.

Win8/Server2012 widened the PKIX chain traversal API to allow
certificate traversal to terminate at an intermediate.

This behaviour (terminate at the fist matching intermediate) is the
default for LibreSSL and OpenSSL (with OpenSSL allowing control via
CURLSSLOPT_NO_PARTIALCHAIN).

This change uses the new API if it is available, and also allows the
behaviour to revert legacy if CURLSSLOPT_NO_PARTIALCHAIN is present.

Closes https://github.com/curl/curl/pull/17418
This commit is contained in:
Rod Widdowson 2025-05-21 20:10:36 +01:00 committed by Jay Satiro
parent 49a0c27bbc
commit df1ff17f88
3 changed files with 48 additions and 8 deletions

View File

@ -55,9 +55,13 @@ Untrusted Publishers block list which it seems cannot be bypassed. (Added in
## CURLSSLOPT_NO_PARTIALCHAIN
Tells libcurl to not accept "partial" certificate chains, which it otherwise
does by default. This option is only supported for OpenSSL and fails the
certificate verification if the chain ends with an intermediate certificate
and not with a root cert. (Added in 7.68.0)
does by default. This option fails the certificate verification if the chain
ends with an intermediate certificate and not with a root cert.
Works with OpenSSL and its forks (LibreSSL, BoringSSL, etc). (Added in 7.68.0)
Works with Schannel if the user specified certificates to verify the peer.
(Added in 8.15.0)
## CURLSSLOPT_REVOKE_BEST_EFFORT

View File

@ -53,9 +53,13 @@ Untrusted Publishers block list which it seems cannot be bypassed. (Added in
## CURLSSLOPT_NO_PARTIALCHAIN
Tells libcurl to not accept "partial" certificate chains, which it otherwise
does by default. This option is only supported for OpenSSL and fails the
certificate verification if the chain ends with an intermediate certificate
and not with a root cert. (Added in 7.68.0)
does by default. This option fails the certificate verification if the chain
ends with an intermediate certificate and not with a root cert.
Works with OpenSSL and its forks (LibreSSL, BoringSSL, etc). (Added in 7.68.0)
Works with Schannel if the user specified certificates to verify the peer.
(Added in 8.15.0)
## CURLSSLOPT_REVOKE_BEST_EFFORT

View File

@ -78,6 +78,28 @@
#define BEGIN_CERT "-----BEGIN CERTIFICATE-----"
#define END_CERT "\n-----END CERTIFICATE-----"
struct cert_chain_engine_config_win8 {
DWORD cbSize;
HCERTSTORE hRestrictedRoot;
HCERTSTORE hRestrictedTrust;
HCERTSTORE hRestrictedOther;
DWORD cAdditionalStore;
HCERTSTORE *rghAdditionalStore;
DWORD dwFlags;
DWORD dwUrlRetrievalTimeout;
DWORD MaximumCachedCertificates;
DWORD CycleDetectionModulus;
HCERTSTORE hExclusiveRoot;
HCERTSTORE hExclusiveTrustedPeople;
DWORD dwExclusiveFlags;
};
#ifndef CERT_CHAIN_EXCLUSIVE_ENABLE_CA_FLAG
/* Not defined for any MINGW build */
#define CERT_CHAIN_EXCLUSIVE_ENABLE_CA_FLAG 0x00000001
#endif
/* Legacy structure to supply size to Win7 clients */
struct cert_chain_engine_config_win7 {
DWORD cbSize;
HCERTSTORE hRestrictedRoot;
@ -93,6 +115,7 @@ struct cert_chain_engine_config_win7 {
HCERTSTORE hExclusiveTrustedPeople;
};
#ifndef UNDER_CE
static int is_cr_or_lf(char c)
{
@ -838,13 +861,22 @@ CURLcode Curl_verify_certificate(struct Curl_cfilter *cf,
}
if(result == CURLE_OK) {
struct cert_chain_engine_config_win7 engine_config;
struct cert_chain_engine_config_win8 engine_config;
BOOL create_engine_result;
memset(&engine_config, 0, sizeof(engine_config));
engine_config.cbSize = sizeof(engine_config);
engine_config.hExclusiveRoot = trust_store;
/* Win8/Server2012 allows us to match partial chains */
if(curlx_verify_windows_version(6, 2, 0, PLATFORM_WINNT,
VERSION_GREATER_THAN_EQUAL) &&
!ssl_config->no_partialchain) {
engine_config.cbSize = sizeof(engine_config);
engine_config.dwExclusiveFlags = CERT_CHAIN_EXCLUSIVE_ENABLE_CA_FLAG;
}
else
engine_config.cbSize = sizeof(struct cert_chain_engine_config_win7);
/* CertCreateCertificateChainEngine will check the expected size of the
* CERT_CHAIN_ENGINE_CONFIG structure and fail if the specified size
* does not match the expected size. When this occurs, it indicates that