I want to add mutual authentication to my client .NET application that is running under IIS server (it's a web service that calls another web service). Client app loads client certificate from file and it works fine with the following code on my development machine (I tried Windows 7 and Windows 10 with .NET 4.6.2):
var handler = new WebRequestHandler();
var certificate = new X509Certificate2(clientCertPath); -- PFX file with client cert and private key
handler.ClientCertificates.Add(certificate);
handler.AuthenticationLevel = AuthenticationLevel.MutualAuthRequired;
client = new HttpClient(handler);
But when this code is deployed to production Windows 2016 Server machine the application throws The request was aborted: Could not create SSL/TLS secure channel.
I enabled tracing for System.Net
this is what I see in logs
SecureChannel#66407304 - Certificate is of type X509Certificate2 and contains the private key.
AcquireCredentialsHandle(package = Microsoft Unified Security Protocol Provider, intent = Outbound, scc = System.Net.SecureCredential)
AcquireCredentialsHandle() failed with error 0X8009030D.
AcquireCredentialsHandle(package = Microsoft Unified Security Protocol Provider, intent = Outbound, scc = System.Net.SecureCredential)
AcquireCredentialsHandle() failed with error 0X8009030D.
Exception in HttpWebRequest#60537518:: - The request was aborted: Could not create SSL/TLS secure channel..
As it turned out this error is caused due to the lack of IIS user's permissions to read private key in PFX file. So when I import it into machine certificate store and add IIS_IUSRS
by clicking right-button on client certificate All Tasks -> Manage Private Keys...
everything works fine, but I want to avoid that.
Is there a way to work with client certificate loaded from file on Windows Server and don't import client certificate PFX file into certificate store?
First, try X509KeyStorageFlags.PersistKeySet. Next as I understand you can just use the key with importing it into key storage. As described here you should first of all import key into the store (whether persistent or not) and then use the key.
Since the example where things work involve using the machine store, you can try changing to a MachineKeySet load.
Replace
var certificate = new X509Certificate2(clientCertPath);
with
var certificate = new X509Certificate2(clientCertPath, null, X509KeyStorageFlags.MachineKeySet);
This shouldn't be necessary for client authentication certificates, but if you have user impersonation happening between load and use that can cause user keyset problems.
Try this, just add this in the startup code
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12 | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls;
ServicePointManager.ServerCertificateValidationCallback =
(a, b, c, d) => true;
User contributions licensed under CC BY-SA 3.0