]> git.ipfire.org Git - thirdparty/curl.git/commitdiff
schannel: allow partial chains for manual peer verification
authorRod Widdowson <rdw@steadingsoftware.com>
Wed, 21 May 2025 19:10:36 +0000 (20:10 +0100)
committerJay Satiro <raysatiro@yahoo.com>
Sat, 14 Jun 2025 22:55:08 +0000 (18:55 -0400)
- 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

docs/libcurl/opts/CURLOPT_PROXY_SSL_OPTIONS.md
docs/libcurl/opts/CURLOPT_SSL_OPTIONS.md
lib/vtls/schannel_verify.c

index 62eafca48d512fa07112fdd2c190bbf529ef8437..68a34b2123fc066feed4a1e498ac9aecfe121875 100644 (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
 
index a997a43c3202cc263d66a42e38fa828daba6182f..38e002e8f46d3dc26f9524c2856470a6b8823249 100644 (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
 
index f843342b9ceab44931b6e9a9a75d3b2d97502520..6634cb7883518d66a261a8dcb419d327e5f8d2aa 100644 (file)
 #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