Search code examples
c#asp.net-core.net-8.0

How to make a POST request using HttpClient with a PFX certificate


I'm using the latest .NET 8's HttpClient to call an API endpoint.

This is my code:

var cert = new X509Certificate("myCert.pfx", "mypass");

var handler = new HttpClientHandler
{
    ClientCertificateOptions = ClientCertificateOption.Manual
};

handler.ClientCertificates.Add(cert);

using var httpClient = new HttpClient(handler);

var response = await httpClient.PostAsJsonAsync("https://<my-url>");

var resultString = await response.Content.ReadAsStringAsync();

It generated the following exception:

System.Net.Http.HttpRequestException: An error occurred while sending the request.
 ---> System.IO.IOException: The decryption operation failed, see inner exception.
 ---> System.ComponentModel.Win32Exception (0x80090326): The message received was unexpected or badly formatted.
   --- End of inner exception stack trace ---
   at System.Net.Security.SslStream.ReadAsyncInternal[TIOAdapter](Memory`1 buffer, CancellationToken cancellationToken)
   at System.Runtime.CompilerServices.PoolingAsyncValueTaskMethodBuilder`1.StateMachineBox`1.System.Threading.Tasks.Sources.IValueTaskSource<TResult>.GetResult(Int16 token)
   at System.Net.Http.HttpConnection.InitialFillAsync(Boolean async)
   at System.Net.Http.HttpConnection.SendAsync(HttpRequestMessage request, Boolean async, CancellationToken cancellationToken)

This seems to suggest that the private key is not working as expected. I used the same certificate file and password on Postman and the request succeeded and returned meaningful results.

Here are some of the things I have tried:

  1. Double-checked my password
  2. Breaking up the pfx file into a certificate and private key
  3. Specify the request as TLS 1.2

None of the above worked.


Solution

  • Your code is mixing types meant for .NET Framework (4.x) and older versions of .NET Core; ostensibly code using them should work without modification on .NET 8 but the program won't run as well as it could - though in practice I find that's going to cause errors like "The message received was unexpected or badly formatted.".

    I suggest that you change your code to this and give it a spin:

    X509Certificate2 clientCert = new X509Certificate2(
        fileName: "myCert.pfx",
        password: "mypass"
    );
    
    if( !clientCert.HasPrivateKey ) throw new InvalidOperationException( "Private key not loaded." );
    
    SocketsHttpHandler socksHandler = new SocketsHttpHandler
    {
        SslOptions =
        {
    //      CipherSuitesPolicy = new CipherSuitesPolicy( ... ) // Only uncomment this if the underlying TLS error is due to a client/server disagreement over what TLS algos to use and this is the *only* way to resolve it.
            ClientCertificates = new X509CertificateCollection()
            {
                clientCert
            },
    //      SslProtocols = SslProtocols.Tls12 // Only uncomment this if you actually really need to specify exactly TLS 1.2. By default it will be negotiated.
        }
    };
    
    using HttpClient httpClient = new HttpClient( socksHandler );
    
    using HttpResponseMessage response = await httpClient.PostAsJsonAsync( "https://<my-url>", etc );
    
    String responseBody = await response.Content.ReadAsStringAsync();