We have investigated the issue and this is our conclusion:
When a certificate is imported to the Certificate Store on Windows the associated private key is stored in a Key Storage. However, there is more than one Key Storage on Windows. Every Cryptography Provider has its own Key Storage. More details are available at Understanding Cryptographic Providers.
On Windows, there are two major Cryptographic Providers: legacy
Cryptography API (CryptoApi or CSP) and modern
Cryptography API: Next Generation (CNG).
The .pfx holds information, which Cryptographic Provider is intended to be used when working with the private key. When importing certificate into Certificate Store the private key is stored in the Key Storage of the intended Cryptographic Provider and the certificate is associated with this provider.
Now, to your particular case:
- In your case, the certificate was associated with the legacy CryptoApi.
- TLS 1.3 needs to perform a cryptography operation, which is only available through CNG provider (namely PSS padding).
- Since the private key is stored in legacy CryptoApi (which does not support PSS) the TLS Proxy tries to export the private key from CryptoApi and import it to CNG in order to perform PSS padding operation.
- This fails when the private key is not marked as exportable (which is your case).
- Either use one of the previously suggested workarounds.
- Or associate your certificate with CNG provider.
It can be done either programmatically in C# using Rebex.Common:
var cert = Certificate.LoadPfx("cert-CSP.pfx", "xxx", KeySetOptions.Exportable | KeySetOptions.AlwaysCng);
cert.Save("cert-CNG.pfx", CertificateFormat.Pfx, "xxx");
Or using OpenSSL:
openssl pkcs12 -in cert-CSP.pfx -passin pass:xxx -out cert-CSP.pem -passout pass:xxx
openssl pkcs12 -export -in cert-CSP.pem -passin pass:xxx -out cert-CNG.pfx -passout pass:xxx -CSP "Microsoft Software Key Storage Provider"
Then, you can import the converted
cert-CNG.pfx into Certificate Store again (without need to mark the private key as exportable). Since the private key is associated with CNG provider, TLS Proxy will not attempt to export it anymore.
When you try to use TLS 1.1 or previous, you will face the opposite issue. The CNG provider does not support required cryptography operation (namely legacy
SHAMD5). The TLS Proxy will try to export the private key from CNG to import it into CryptoApi, which will fail (if not exportable).
However, if you are not going to support TLS 1.1, you are fine.