]> git.ipfire.org Git - thirdparty/curl.git/commitdiff
schannel: handle pkcs12 client certificates which contain CA certificates
authorJoel Depooter <joel.depooter@safe.com>
Mon, 24 Mar 2025 23:06:24 +0000 (16:06 -0700)
committerDaniel Stenberg <daniel@haxx.se>
Tue, 15 Apr 2025 21:27:40 +0000 (23:27 +0200)
The SChannel code uses the CertFindCertificateInStore function to
retrieve the client certificate from a pkcs12 certificate store.
However, when called with the CERT_FIND_ANY flag, this function does not
provide any guarantees on the order in which certificates are retrieved.
If a pkcs12 file contains an entire certificate chain instead of a
single client certificate, the CertFindCertificateInStore function may
return the CA or an intermediate certificate instead of the desired
client certificate. Since there is no associated private key for such a
certificate, the TLS handshake fails.

With this change, we now pass the CERT_FIND_HAS_PRIVATE_KEY flag. This
ensures that the CertFindCertificateInStore function will return a
certificate which has a corresponding private key. This will stop the CA
and intermediate certificates from being selected. I don't think there
would be much use in a client certificate which has no associated
private key, so this should ensure the client certificate is selected. I
suppose it may be possible for a pkcs12 file to contain multiple
certificates with private keys and the new behaviour may not guarantee
which is selected. However, this is no worse that the previous behaviour
in which any certificate may been selected.

The CERT_FIND_HAS_PRIVATE_KEY is only available in Windows 8 / Server
2012 (aka Windows NT6.2). For older versions, we will fall back to using
the CERT_FIND_ANY flag.

Closes #16825

lib/vtls/schannel.c

index f5589a30961c765b3c5781d2c7f567baec5ab85d..a5630687575ff685b2b44532eea822703955a530 100644 (file)
 #define PKCS12_NO_PERSIST_KEY 0x00008000
 #endif
 
+#ifndef CERT_FIND_HAS_PRIVATE_KEY
+#define CERT_FIND_HAS_PRIVATE_KEY (21 << CERT_COMPARE_SHIFT)
+#endif
+
 /* ALPN requires version 8.1 of the Windows SDK, which was
    shipped with Visual Studio 2013, aka _MSC_VER 1800:
      https://technet.microsoft.com/en-us/library/hh831771%28v=ws.11%29.aspx
@@ -611,6 +615,7 @@ schannel_acquire_credential_handle(struct Curl_cfilter *cf,
       WCHAR* pszPassword;
       size_t pwd_len = 0;
       int str_w_len = 0;
+      int cert_find_flags;
       const char *cert_showfilename_error = blob ?
         "(memory blob)" : data->set.ssl.primary.clientcert;
       curlx_unicodefree(cert_path);
@@ -682,9 +687,17 @@ schannel_acquire_credential_handle(struct Curl_cfilter *cf,
         return CURLE_SSL_CERTPROBLEM;
       }
 
+      /* CERT_FIND_HAS_PRIVATE_KEY is only available in Windows 8 / Server
+         2012, (NT v6.2). For earlier versions we use CURL_FIND_ANY. */
+      if(curlx_verify_windows_version(6, 2, 0, PLATFORM_WINNT,
+                                      VERSION_GREATER_THAN_EQUAL))
+        cert_find_flags = CERT_FIND_HAS_PRIVATE_KEY;
+      else
+        cert_find_flags = CERT_FIND_ANY;
+
       client_certs[0] = CertFindCertificateInStore(
         cert_store, X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, 0,
-        CERT_FIND_ANY, NULL, NULL);
+        cert_find_flags, NULL, NULL);
 
       if(!client_certs[0]) {
         failf(data, "schannel: Failed to get certificate from file %s"