From: Joel Depooter Date: Mon, 24 Mar 2025 23:06:24 +0000 (-0700) Subject: schannel: handle pkcs12 client certificates which contain CA certificates X-Git-Tag: curl-8_14_0~293 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=fe9898d26e54edc197570b971be86c15ccb67935;p=thirdparty%2Fcurl.git schannel: handle pkcs12 client certificates which contain CA certificates 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 --- diff --git a/lib/vtls/schannel.c b/lib/vtls/schannel.c index f5589a3096..a563068757 100644 --- a/lib/vtls/schannel.c +++ b/lib/vtls/schannel.c @@ -151,6 +151,10 @@ #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"