6

We are having a problem with a friend with loading a private certificate to httpHandler.
We are using .net core and need to host all aplication in the cloud.
Main goal is to get message from SQS and perform some specified API shots after with consumed data.
We have a problem with certificate with public / private key. We have tried I think all the possible ways of loading it.

    public async Task<HttpResponseMessage> VisitHttps()
    {
        // Proceed for an invalid cerficate
        ServicePointManager.ServerCertificateValidationCallback +=
        (sender, certificate, chain, sslPolicyErrors) => true;

        // Add the certificate
        var handler = new HttpClientHandler();
        var cert = GetMyCert();
        if (cert != null)
        {
            handler.ClientCertificates.Add(cert);
            handler.ClientCertificateOptions = ClientCertificateOption.Manual;
            handler.SslProtocols = System.Security.Authentication.SslProtocols.Tls12;
            //handler.PreAuthenticate = true;
        }
        ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12 | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls;


        HttpClient cclient = new HttpClient(handler)
        {
            //BaseAddress = new Uri("https://someurl.com")

        };
        cclient.DefaultRequestHeaders.Accept.Clear();
        cclient.DefaultRequestHeaders.Accept.Add(new 

MediaTypeWithQualityHeaderValue("application/json"));
            return await cclient.GetAsync("https://some-url.com/ping"); }

And the method GetMyCert() looks like below:

string currentLocation = $"{AppDomain.CurrentDomain.BaseDirectory}key-public.crt";
                //var xcert = new X509Certificate2(currentLocation, "password");

                ////var currentLocationPriv = $"{AppDomain.CurrentDomain.BaseDirectory}key-private.crt";
                ////var privcert = new X509Certificate2(currentLocationPriv, "password", X509KeyStorageFlags.EphemeralKeySet);
                //var certStore = new X509Store(StoreName.My, StoreLocation.CurrentUser);
                //certStore.Open(OpenFlags.ReadWrite);
                //certStore.Add(xcert);
                //certStore.Close();
            //return xcert;

            X509Store store = new X509Store("My", StoreLocation.CurrentUser);
            X509Certificate2 cert;
            cert = new X509Certificate2(File.ReadAllBytes(currentLocation), "password", X509KeyStorageFlags.MachineKeySet);
            bool result = cert.Verify();
            var r2 = result;
            return cert;

commented lines are variances of what we have tried to do.
We have no idea what else we should try to handle this issue.
Any guidelines would be more than welcome

EDIT:
I've tried registering this inside startup class but it seems not working anyway. I always got the private key field inside certificate empty. And hasPrivateKey marked as false.

 private void CreateCert(IServiceCollection services)
    {
        string currentLocation = $"{AppDomain.CurrentDomain.BaseDirectory}key-public.crt";
        var certificate = new X509Certificate2(currentLocation, "password");
        services.AddHttpClient("TestClient", client =>
        {
            client.BaseAddress = new Uri("https://someurl.com");
        })
        .ConfigurePrimaryHttpMessageHandler(() =>
            {
            var handler = new HttpClientHandler();
            handler.ClientCertificates.Add(certificate);
            return handler;
        });
    }  

My Test code:

        [Fact]
    public async Task ShouldPong()
    {
        var testClient = new TestClient()
        {
            BaseAddress = new Uri("https://someurl.com")
        };
        var result = await testClient.GetAsync("/ping");
        result.StatusCode.Should().Be(HttpStatusCode.OK);
    }

TestClient:

public class TestClient : HttpClient
{
    public TestClient()
        :base()
    {

    }

    public TestClient(HttpMessageHandler handler)
        : base(handler)
    {

    }
}  

EDIT:
The problem was solved when changing the .crt files into a .pfx file. Since the API we was hitting was hosted on nginx.

Kacper Werema
  • 448
  • 2
  • 5
  • 18
  • .Net Core provides a new way to deal with HttpClient, which is through Injecting the HttpClientFactory. And there are the Named Clients, pre-configured ones that are injected into your modules/classes. Possibly, you could use this question answer: https://stackoverflow.com/questions/56480160/c-net-core-private-key-authentication-httpclient – rogerdossantos Jun 07 '19 at 05:55
  • Sorry but you have linked my own question, I will check the httpClientFactory :) – Kacper Werema Jun 08 '19 at 08:01
  • I am sorry, my mistake haha. This is the correct link: https://stackoverflow.com/questions/52371768/asp-core-httpclientfactory-pattern-use-client-cert – rogerdossantos Jun 08 '19 at 15:07
  • Ok thanks :) It's not helping, by the way maybe the problem is that when I look at handle during debug it shows that private key is null and it's not set. Should I load it somehow with my public key or add it? – Kacper Werema Jun 08 '19 at 17:26

2 Answers2

4

The problem was resolved by creating a .PFX file. The server we were hitting was hosted on nginx that requires .pfx format. The .crt files are PEM certificate that was not valid for nginx.

Kacper Werema
  • 448
  • 2
  • 5
  • 18
  • Yes solution right but you should know that PEM format for nginx is also right it is using in linux environment – Hamit YILDIRIM Jun 12 '19 at 10:14
  • Well so I don't know why this was not working while using .crt files and worked when I've combined them into .pfx cert file. I thought that nginx just don't support PEM format :P. I was testing it on windows and mac. – Kacper Werema Jun 13 '19 at 12:48
  • if you try to working out with digitalocean then you will see it is using PEM on default. there is No relation with nginx within the certificate format. Nginx looks 2 things is there a right key for an ssl – Hamit YILDIRIM Jun 13 '19 at 13:45
0

I think you're not instantiating the client correctly, accordingly with the doc for Named Clients.

You will need to receive the IHttpClientFactory in run-time and ask for your named client like this:

 var client = _clientFactory.CreateClient("TestClient");

About testing with Dependency Injection, I believe this Microsoft tutorial can help: Integration Tests Asp.Net Core. The deal here is, as the Startup.cs file and the Core Dependency Injector are part of the framework, you need to setup simulate the web application in the test context. And Microsoft provides the WebApplicationFactory for that.

This example demonstrate a test with a httpClient given by the IHttpClientFactory in a simulated web application environment.

rogerdossantos
  • 161
  • 1
  • 10
  • I've done what you said. Registered as above. And tried to ping my action from postman to test it. I've injected the IHttpClientFactory in constructor and then created the client as you said. Tried to hit the endpoint with GetAsync method but still I get the same response. Unauthorized. Should I register the private key somewhere too? (I've got 2 .cert files 1 public.cert and 1 private.cert) – Kacper Werema Jun 10 '19 at 17:36