We are currently migrating our code from .NET 4.7.2 to NET 7 (.Net Core).
Everything has been moved forward except one project that has to consume another company’s WebApi rest web service.
When we were forced to interact with this company they were unable (or unwilling) to provide us sample code
on how to consume their service which required a Client Certificate.
The below scenario and code is not exactly how things are configured, but was simplified in order to post on this forum.
Setup:
We have a WebService and several WinForm apps that call the Vendor’s API.
We generate a self-signed certificate from any workstation/server that needs to call the vendor’s service.
The certificate is in the trusted root for the machine and current user, etc.
We export that certificate and physically give it to the vendor.
We then use the code at the bottom of this post on the box the certificate was generated from to call the vendor service.
Success Method 1:
If the Program is started with Admin permissions (Run as Administrator) on .Net4.7.2 it works.
Running without elevated permissions will cause it to fail.
Success Method 2:
The other successful path is to use the LoginUser "advapi32.dll" windows dll to impersonate an Admin account and run the request under the elevated permissions.
Both methods fail under .NET Core.
When we run the code in .Net Core we receive an “500 Internal Server” error and the below message (modified to remove company names):
An unexpected error occurred.\r\nSystem.Exception: Unable to GetCertificate
From: ggWeb.ggWebCrh ClientIP: xxx.xxx.xxx.xx\r\nSystem.Exception: X-ENV-SSL_CLIENT_CERTIFICATE
returned blank\r\n
at Vendor.ggWeb.ggWebCrh.GetCertificate(WebHeaderCollection phdr)\r\n
at Vendor.ggWeb.ggWebRsc.ExecuteBo(Int32 plngWebServiceKey, Boolean pblnTest, Stream pstmInput, ggMsgObj pmsgUrlParameters)\r\n
at Vendor.ggWeb.ggWebRsc.ExecuteBo(Int32 plngWebServiceKey, Boolean pblnTest, Stream pstmInput, ggMsgObj pmsgUrlParameters)"
As some articles suggested we exported to a password protected pfx. Both methods work on .NET 4.7.2.
In the ServerCertificateCustomValidationCallback event you can see the Vendor’s GlobalSign SSL certificate information.
We did find one interesting article shown below that we wondering could be connected:
https://learn.microsoft.com/en-us/dotnet/core/compatibility/aspnet-core/6.0/clientcertificate-doesnt-trigger-renegotiation
We have exhausted our searching and decided to reach out in the hopes that someone can help.
Thank you all in advance for any ideas, assistance, direction, etc.
Sample Code:
//********************************
//******* Using HttpClient *******
//********************************
System.Net.Http.HttpClient objClient = null;
System.Net.Http.HttpClientHandler objHandler = null;
objHandler = new System.Net.Http.HttpClientHandler();
objHandler.ClientCertificateOptions = System.Net.Http.ClientCertificateOption.Manual;
objHandler.SslProtocols = System.Security.Authentication.SslProtocols.Tls12;
objHandler.ClientCertificates.Add(new System.Security.Cryptography.X509Certificates.X509Certificate2(fncFileToByteArray("C:\\MyCert.cer"));
//objHandler.ClientCertificates.Add(new System.Security.Cryptography.X509Certificates.X509Certificate2(fncFileToByteArray("C:\\MYCert.pfx"), "MyPassword"));
objHandler.CheckCertificateRevocationList = false;
objHandler.ServerCertificateCustomValidationCallback +=
(sender2, certificate, chain, sslPolicyErrors) =>
{
return true;
};
objClient = new System.Net.Http.HttpClient(objHandler);
objClient.BaseAddress = new Uri("https://Vendor.com/Api/Endpoint");
objClient.DefaultRequestHeaders.Accept.Add(new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/json"));
objClient.Timeout = TimeSpan.FromSeconds(15);
System.Net.Http.HttpResponseMessage rmResponse = null;
rmResponse = objClient.GetAsync("?SomeQuery=12345").GetAwaiter().GetResult();
//if (!rmResponse.IsSuccessStatusCode)
System.String strResult = rmResponse.Content.ReadAsStringAsync().GetAwaiter().GetResult();
System.Net.HttpWebResponse wrResponse = null;
//******************************************
//******* Using Relegated WebRequest *******
//******************************************
System.String strUrl = txtUrl.Text;
System.Net.HttpWebRequest objWebRequest = (System.Net.HttpWebRequest)System.Net.WebRequest.Create(“https://Vendor.com/Api/Endpoint?SomeQuery=12345”);
objWebRequest.Method = "GET";
objWebRequest.Accept = "application/json";
objWebRequest.CachePolicy = new System.Net.Cache.HttpRequestCachePolicy(System.Net.Cache.HttpRequestCacheLevel.NoCacheNoStore);
objWebRequest.ClientCertificates.Add(new System.Security.Cryptography.X509Certificates.X509Certificate(fncFileToByteArray("C:\\MyCert.cer")));
objWebRequest.ServerCertificateValidationCallback = delegate { return true; };
wrResponse = (System.Net.HttpWebResponse)objWebRequest.GetResponse();
if (wrResponse.StatusCode != System.Net.HttpStatusCode.OK)
{
throw new System.Net.WebException("Unknown Status Code Returned");
}
String strResponseStream = null;
using (var reader = new System.IO.StreamReader(wrResponse.GetResponseStream()))
{
strResponseStream = reader.ReadToEnd();
}