Andrus
Andrus

Reputation: 27955

How to use different certificates for HTTP requests?

HTTP GET request is done from ASP.NET Core 9 MVC controller using HttpClientFactory described in https://learn.microsoft.com/en-us/aspnet/core/fundamentals/http-requests?view=aspnetcore-9.0

Program.cs:

internal class Program {
    public const string LhvClient = "lhvclient";

    static void Main(string[] args) {
        WebApplicationBuilder builder = WebApplication.CreateBuilder(args);
        const string lhvprivaatvoti = @"-----BEGIN RSA PRIVATE KEY-----
    MIIEpAIBAAKCAQEAtlfiLYZ48IwU3cbJA69m5F9xCiamWzEvkzseP8aAuLHDVdUi
    AVIzvKZeILr/M9Sc+wowLYz0azBV64YiFI9qoder4BcfkfvznjRKwPUl6wQhOMUG
    hkM79VusdcUAiP31f0pVTtEKhmMqJik5asalB1TFdjylaW3VbrkHqdXVFw91Q3aK
    ifrc4Z6KDPt6wqDCXe108mG0QyaSGtc0lBuV0ZoWL3ZNHrbh3NRV4Ojb/O3mAog8
    rV0FbAufy0qZnaTse52x8KqZDE2rJjNWr23QLk2OAezt6t7a2ZCOLF3OS+tMqyeu
    M8N1lqQIORFo18iYxAXt5Rd9g6Ap6KpE8ahzuQIDAQABAoIBABLtFPJ+u6d+vV0L
    mKpY7AgWM/McHIuTPl88yqCkp5BZZcSkm6P2y/HvyWNXpXd+wPskN+idHHLSZd34
    DSovJ0qRyoA28OBD038p6acU4Duq27vlug342oysAumHv3d4ofZKrodra86Tv7iN
    nNe4CJcLNc0A8cul9ST0muwGNYOaup0wOqLOskfvXmhqYT8gu9h8sbsW40WupIip
    OFUwlGXBwtWCycJTD/UdsCKQZZ1oTX/oWpXDslzUyeUwssgaFdJwAw2Y1IQDODLk
    oV9pSjr2inWfIz0ILbtm131zSTY7BSMspbW1IKT1WJlrJz5gAYJjOddsoZ8RwUtX
    OHnPBFUCgYEA3InyQPWue5MU0j2PSNY3WQaMg/jD1zelmWZ1M65mxihfl/Ctf33I
    8mtEGoWzwaNgRcyTgd2SqOxHMUqeJ1b6mVSRgnC8GtSV32AG/k3qpZ6BB/rLZu5E
    hOcCoqysdF/ng4y1blVoZeJ4u06VI2tSjrp79M2aHh5WNEyEuZ4xSZsCgYEA06mw
    9AXg1LgwsBQJpNbMRwK7UVoJgjMiwKfsMkIDe2keeJo5eD68cseyOyvRjcssft8d
    Sxi3AAMa0ymHOi7/m013PHkfzVSeT0nmL2iGZ1s+w9EnVzwzkNXrFL9T9/QPTOTS
    75tPcbmWvMxwBxgdE5GouVpI+/PSAXYKoVQPxzsCgYEAyAnkECII2xQFPdISxplv
    6LN0/zvEv9E8xxDVXERj+neihdoMNWktvR2oz3nZG9oKOCWg4pnNLqXqyX4KSFqv
    wiagObXyGVAchVm/3ilknkdQeKF2n+2dfwNfM5j2cDSRdZRK+UJFCK1Mn3Fe+5qc
    btQeHWXk1k7kGFoTxZ4EzOMCgYApA3mZX0Y4kdRFWiygN0rv+5SGZ/bttrDmOeOn
    vWjlUfIolmHKbgWgDBf6JTx+yD6/+sW3VnunUfKxthtQ5+h5lGIlYqcJ53qhjIVA
    7HUWs/SOhwmjerPXPcxGgehoZG9OjAxfh222cKrHvKl8hmyj7RaPi/IWeCFaTSA0
    MJC6HwKBgQDVLOevx/XyYiIkOCNn90kyPBNOhMz7vo8vooE8sHCr7y/NerCZfDxG
    1c74EBD8aAyLDbQWh3aoi698TvVhbwtsLwh+KqkajSuGnVKVkhMFm+H6Pny/UF16
    jMQa/SSC4BBj5Y8IIPXoJC4HNlqocCBKHUgQHAurmXMR4ExHCq8ROg==
    -----END RSA PRIVATE KEY-----";

        const string lhvsert = @"-----BEGIN CERTIFICATE-----
    MIIE4TCCAsmgAwIBAgIGAZPGzbosMA0GCSqGSIb3DQEBCwUAMGgxCzAJBgNVBAYT
    AkVFMREwDwYDVQQIDAhIYXJqdW1hYTEUMBIGA1UECgwLQVMgTEhWIFBhbmsxDTAL
    BgNVBAsMBFBTRDIxITAfBgNVBAMMGExIViBQU0QyIEludGVybWVkaWF0ZSBDQTAe
    Fw0yNDEyMTQyMDEzMTFaFw0zNDEyMTIyMDEzMTFaMIGFMR0wGwYDVQRhExRQU0RF
    RS1MSFZURVNULThmMjBmZTEeMBwGA1UEAxMVUFNEMiB0ZXN0IGNlcnRpZmljYXRl
    MRIwEAYDVQQKEwlQU0QyIFRlc3QxEDAOBgNVBAcTB1RhbGxpbm4xETAPBgNVBAgT
    CEhhcmp1bWFhMQswCQYDVQQGEwJFRTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC
    AQoCggEBALZX4i2GePCMFN3GyQOvZuRfcQomplsxL5M7Hj/GgLixw1XVIgFSM7ym
    XiC6/zPUnPsKMC2M9GswVeuGIhSPaqHXq+AXH5H78540SsD1JesEITjFBoZDO/Vb
    rHXFAIj99X9KVU7RCoZjKiYpOWrGpQdUxXY8pWlt1W65B6nV1RcPdUN2ion63OGe
    igz7esKgwl3tdPJhtEMmkhrXNJQbldGaFi92TR624dzUVeDo2/zt5gKIPK1dBWwL
    n8tKmZ2k7HudsfCqmQxNqyYzVq9t0C5NjgHs7ere2tmQjixdzkvrTKsnrjPDdZak
    CDkRaNfImMQF7eUXfYOgKeiqRPGoc7kCAwEAAaNzMHEwbwYIKwYBBQUHAQMEYzBh
    BgYEAIGYJwIwVzA5MBEGBwQAgZgnAQIMBlBTUF9QSTARBgcEAIGYJwEDDAZQU1Bf
    QUkwEQYHBACBmCcBBAwGUFNQX0lDDBNGaW5hbnRzaW5zcGVrdHNpb29uDAVFRS1G
    STANBgkqhkiG9w0BAQsFAAOCAgEAhYm3S+mdyR837aTYk5ZGyXQ5d9ocSiHFDRnU
    j0AxDRCqJ70/jmj0vur4NfAeOC4yz3YXr2jBl0yPBDaXATUQClbMyYr4QIQJLa0Y
    FJ2iDQtN4j6jvi1GcWAyvlW8XSzi1y7mv6ggvkomhkkwd6Ua1EYqduxfrmbu7xOH
    bPd9QgJJkGCY5JmbNAAEL9W12ErZVR5zbJqieTxnetl6clApE4ZuSXWjhyNCbztk
    UJ/9UEZekMiLUCaxiFHQR81UxH7gMzjawTVL54xszddZZ8Tya5J3ayQ5XwpA6t0C
    u2UfhND2f/V8M5N0PaEaXJq+ekeE/eXx8cf/lFr3kesGWCN395NLAfG+W+Ov7d5b
    GBlnLBaZMcQ70Kv3GNHSFsILllJbv92NK28c00/Gyoo8bekCyMcVKv4rEy9vxLW4
    zUkDJOZJ5cM+w1jqgpfQG52rYHpRr1uY0rZx2R8DnXKp+rZ/m6u0uuEqwi5jTk2+
    zjXGocNBGdh5JsDlw31n/SmgzCn4KLH+RPHKCRtBB20oZBcy0ApVvMRCA45T5NKY
    K1Pavic5YCgg2jM2APN6xH6SCdmzJhipOXmYOwYVYW5QYIl8du52xIhEGSzzCiEJ
    zho4eB9Cgti+BJTdYOq0AV3Ad2EUckEkXhVG5u4N5MdhDHqk2gC3IsEpMAwb8J3b
    sT3qlI0=
    -----END CERTIFICATE-----";

        var lhvhandler = new HttpClientHandler {
            ClientCertificateOptions = ClientCertificateOption.Manual,
            ServerCertificateCustomValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator
        };

        var sert1 = X509Certificate2.CreateFromPem(lhvsert, lhvprivaatvoti);
        lhvhandler.ClientCertificates.Add(sert1);

        var lhv = builder.Services.AddHttpClient(LhvClient);
        lhv.ConfigurePrimaryHttpMessageHandler(() = >lhvhandler);
    }
}

Controller:

[Authorize]
public class LhvController(IHttpClientFactory httpClientFactory) : Controller {
    public async Task < IActionResult > Index() {
        HttpClient client = httpClientFactory.CreateClient(Program.LhvClient);
        var tulem = await client.GetStringAsync("https://api.lhv.eu/psd2/v1/accounts-list?onlyActive=true");
        return new ContentResult() {
            Content = tulem,
            ContentType = "text/json"
        };
    }
}

In controller different certificate is required depending on production/sandbox environment and tenant in multi-tenant application. Certificates are stored in the database and fetched using EF Core.

HttpClient does not have method to set certificate. Tried also to create HttpRequestMessage in controller, but HttpRequestMessage also does not allow to set certificate.

How to use specify certificate in controller before making GET requests?

Upvotes: 1

Views: 86

Answers (1)

Kiryl
Kiryl

Reputation: 1526

Option#1

Create a pool of the preconfigured handlers with your custom certs, and then follow this guideline to create the client:

  • Create an instance of SocketsHttpHandler when the app starts and use it for the life of the app.
  • Configure PooledConnectionLifetime to an appropriate value based on DNS refresh times.
  • Create HttpClient instances using new HttpClient(handler, disposeHandler: false) as needed.
  • Finally, create a custom factory you'll use to create clients based on your logic

Option#2

Just a draft but the idea behind should be clear:

class MyCustomBuilder : HttpMessageHandlerBuilder
{
    public override string? Name { get; set; }

    public override HttpMessageHandler PrimaryHandler { get; set; }

    public MyCustomBuilder(HttpMessageHandler primaryHandler)
    {
        PrimaryHandler = primaryHandler;
    }

    public override IList<DelegatingHandler> AdditionalHandlers { get; } = new List<DelegatingHandler>();

    public override HttpMessageHandler Build()
    {
        //TODO:see DefaultHttpMessageHandlerBuilder impl
    }
}
...
services.AddTransient<HttpMessageHandlerBuilder, MyCustomBuilder>(sp =>
{
    //have a pool of builders, like one per your tenant or wharever
    return pool.GetOrAdd("tenantId", (n) => new MyCustomBuilder(
        new HttpClientHandler()));
});

Option#3

Use named clients, one per tenant -

services.AddHttpClient("tenant1")
    .ConfigurePrimaryHttpMessageHandler((sp) =>
    {
        return new HttpClientHandler();
    });             
services.AddHttpClient("tenant2")
    .ConfigurePrimaryHttpMessageHandler((sp) =>
    {
        return new HttpClientHandler();
    });
...
IServiceProvider sp = services.BuildServiceProvider();
IHttpClientFactory factory = sp.GetRequiredService<IHttpClientFactory>();

var client1 = factory.CreateClient("tenant1");
var client2 = factory.CreateClient("tenant2");

Upvotes: 0

Related Questions