Error calling a Web API with self-signed ClientCertificate using HttpClient in .NET Core (IIS and Winforms using C#)

Shane Vickery 0 Reputation points
2023-09-03T21:58:31.0133333+00:00
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://video2.skills-academy.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();
}
 
 

Windows Forms
Windows Forms
A set of .NET Framework managed libraries for developing graphical user interfaces.
1,869 questions
ASP.NET Core
ASP.NET Core
A set of technologies in the .NET Framework for building web applications and XML web services.
4,382 questions
C#
C#
An object-oriented and type-safe programming language that has its roots in the C family of languages and includes support for component-oriented programming.
10,624 questions
ASP.NET API
ASP.NET API
ASP.NET: A set of technologies in the .NET Framework for building web applications and XML web services.API: A software intermediary that allows two applications to interact with each other.
317 questions
0 comments No comments
{count} votes

1 answer

Sort by: Most helpful
  1. Muhammad Binyameen 395 Reputation points
    2023-09-05T17:22:34.4366667+00:00
    In order to consume the Vendor's WebApi rest web service in .NET Core, you can follow these steps:
    
    1. Install the required NuGet packages:
       - System.Net.Http: This package provides the necessary classes for interacting with HTTP services.
       - System.Security.Cryptography.X509Certificates: This package provides classes for working with X.509 certificates.
    
    2. Import the necessary namespaces at the top of your code file:
       ```csharp
       using System.Net.Http;
       using System.Security.Cryptography.X509Certificates;
    
    1. Create an HttpClient object and specify the required settings:
         HttpClientHandler handler = new HttpClientHandler();
         handler.ClientCertificateOptions = ClientCertificateOption.Manual;
         handler.SslProtocols = System.Security.Authentication.SslProtocols.Tls12;
         handler.ClientCertificates.Add(new X509Certificate2("C:\\MyCert.cer"));
      
         HttpClient client = new HttpClient(handler);
         client.BaseAddress = new Uri("https://Vendor.com/Api/Endpoint");
         client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
         client.Timeout = TimeSpan.FromSeconds(15);
      
    2. Send a GET request to the API endpoint:
         HttpResponseMessage response = await client.GetAsync("?SomeQuery=12345");
      
         if (response.IsSuccessStatusCode)
         {
             string result = await response.Content.ReadAsStringAsync();
             // Process the response data
         }
         else
         {
             // Handle the error scenario
         }
      

    Note: Make sure to replace "C:\MyCert.cer" with the correct path to your certificate file. If you have a password-protected certificate (PFX), you can use the X509Certificate2 constructor overload with the password parameter.

    By using the HttpClient class, you can send HTTP requests to the Vendor's API and receive the responses. Make sure to handle any exceptions and error scenarios appropriately as per your application's requirements.

    
    
    0 comments No comments