yogihosting
yogihosting

Reputation: 6292

Certificate Authentication Implementation in ASP.NET Core 3.1

I am building a small feature in ASP.NET Core Certificate authentication as given in official docs.

Note: I am not building APIs, I am just trying to secure some Action methods of some controllers so that these secured action methods are opened only when the client has the client certificate.

The below image shows that I am able to secure the Index action method which now requires client certificate. Other action method which is Privacy does not require client certificate. The result is that Index action does opens in browser (403 error is received) but Privacy action is opened up in browser

enter image description here

Full Codes

1. Program.cs

public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
    .ConfigureWebHostDefaults(webBuilder =>
    {
        webBuilder.UseStartup<Startup>();
        webBuilder.ConfigureKestrel(o =>
        {
            o.ConfigureHttpsDefaults(o =>
                o.ClientCertificateMode =
                    ClientCertificateMode.RequireCertificate);
        });
    });

2. Startup.cs

public Startup(IConfiguration configuration)
{
    Configuration = configuration;
}

public IConfiguration Configuration { get; }

// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
    services.AddControllersWithViews();
    services.AddAuthentication(
        CertificateAuthenticationDefaults.AuthenticationScheme)
        .AddCertificate(options =>
        {
            options.Events = new CertificateAuthenticationEvents
            {
                OnCertificateValidated = context =>
                {
                    var validationService = context.HttpContext.RequestServices.GetService<MyCertificateValidationService>();

                    if (validationService.ValidateCertificate(context.ClientCertificate))
                    {
                        context.Success();
                    }
                    else
                    {
                        context.Fail("invalid cert");
                    }

                    return Task.CompletedTask;
                },
                OnAuthenticationFailed = context =>
                {
                    context.Fail("invalid cert");
                    return Task.CompletedTask;
                }
            };
        });
}

// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    app.UseHttpsRedirection();
    app.UseStaticFiles();

    app.UseRouting();

    app.UseCertificateForwarding();
    app.UseAuthentication();

    app.UseAuthorization();

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapControllerRoute(
            name: "default",
            pattern: "{controller=Home}/{action=Index}/{id?}");
    });
}

3. MyCertificateValidationService.cs

public class MyCertificateValidationService
{
    public bool ValidateCertificate(X509Certificate2 clientCertificate)
    {
        var cert = new X509Certificate2(Path.Combine("localhost_root_l1.pfx"), "1234");
        if (clientCertificate.Thumbprint == cert.Thumbprint)
        {
            return true;
        }

        return false;
    }
}

4. Action methods that are secured and unsecured

[Authorize]
public IActionResult Index()
{
    return View();
}

public IActionResult Privacy()
{
    return View();
}

Note: Index action method requires client authentication while Privacy does not require client certificate.

The Problems: The problems which I am getting are:

  1. CertificateAuthenticationEvents & OnAuthenticationFailed located on ConfigureServices() method of startup.cs file I not called. I checked them by placing breakpoints but breakpoint is not reached.

  2. MyCertificateValidationService.cs class ValidateCertificate() method is also not called up. I have also checked it with breakpoint

Please help me to implement Certificate Authorization.

Update

I created 2 certificates in C# as explained in this tutorial. These are:

  1. Root certificate called root_localhost.pfx
  2. Client certificate called client.pfx

I did 2 things with these certificates:

a. I added root_localhost.pfx to the Trusted Root Certification Authorities (on Windows) for the Local Computer (using CertManager).

b. I imported the Client certificate on by chrome browser.

Next, I selected the project in VS 2019 (console) instead of 'IIS Express' and run my project. I opened the website url in incognito window, the URL happens to be - https://localhost:5001

Chrome asks to select the certificate, see image below: enter image description here

On selecting it i get This site can’t be reached - ERR_SPDY_INADEQUATE_TRANSPORT_SECURITY, see below image:

enter image description here

Why it is happening????

Upvotes: 11

Views: 9676

Answers (5)

palcoder
palcoder

Reputation: 41

I was stuck on the same issue recently and resolved it successfully. There are two issues with the test project you're running on.

  1. As mentioned by other Devs, revocation mode should be turned off using below snippet.
                {
                    options.RevocationMode = X509RevocationMode.NoCheck; 

  1. Register MyCertificateValidationService in the DI (ConfigureServices) using below snippet. services.AddTransient<MyCertificateValidationService>();

As soon as you make these changes, your breakpoint should hit MyCertificateValidationService.ValidateCertificate()

    public class MyCertificateValidationService
    {
        public bool ValidateCertificate(X509Certificate2 clientCertificate)
        {
            var cert = new X509Certificate2(Path.Combine("localhost_root_l1.pfx"), "1234");
            return clientCertificate.Thumbprint == cert.Thumbprint;
        }
    }

Upvotes: 4

Anusha Ganegoda
Anusha Ganegoda

Reputation: 1

Following is the only thing that was required, just to get around the certificate error when calling on localhost self signed. Note that, I am constructor injecting the HttpClient (as described in here) In StartUp class,

  public void ConfigureServices(IServiceCollection services)
    {           

        services.AddControllersWithViews();
        services.AddHttpClient().ConfigurePrimaryHttpMessageHandler(() =>
        {
            return new HttpClientHandler()
            {
                ServerCertificateCustomValidationCallback = (request, certificate, certificateChain, policy) => true
            };
        }); 

    }       

I got the above validation override from this article: Custom certificate validation in .NET.

Upvotes: -1

joacar
joacar

Reputation: 961

I had to deal with this as well recently and documented my steps here. They are quite lengthy so I recommend checking out the README and follow the instructions.

I'll post theme here anyway for those interested:


Certificates

  1. Run file certcrt.cmd and follow the instructions to create .cer, .pfx and .crl files.
  2. Import client certificate to Current User\Personal. This is usually done during creation.
    1. Windows+R
    2. Type certmgr.exe then enter
    3. Right click Personal
    4. All Tasks > Import
    5. Select the generated .cer client file
  3. Import server certificate to Local Computer\Trusted Root Certitificates Authorities
    1. Windows+R
    2. Type certlm.exe then Ctrl+Shift+Enter (start as admin)
    3. Right click Trusted Root Certitificates Authorities
    4. All Tasks > Import
    5. Select the generated .cer file
  4. Import certificate revocation list. Same as above but select .crl file

IIS/IIS Express

Run file iis.cmd to update relevant configuration sections. The section iisClientCertificateMappingAuthentication must be enabled and the section access should have sslFlags set to "Ssl, SslNegotiateCert, SslRequireCert"`.

Application

  1. Add Microsoft.AspNetCore.Authentication.Certificate nuget package

  2. Configure authentication protocol

    services
         .AddAuthentication(CertificateAuthenticationDefaults.AuthenticationScheme)
         .AddCertificate(options =>
         {
             options.AllowedCertificateTypes = CertificateTypes.All;
             options.Events = new CertificateAuthenticationEvents
             {
                 OnCertificateValidated = context =>
                 {
                     // Do validation on context.Certificate here
                     return Task.CompletedTask;
                 },
                 OnAuthenticationFailed = context =>
                 {
    
                     return Task.CompletedTask;
                 }
             };
         });
    
  3. Add app.UseAuthentication() before app.UseAuthorization() to hook into CertificateAuthenticationEvents.OnCertificateValidated. This is never called otherwise, leaving open for any certificate.

ISSUES

HTTP Error 403.16 - Forbidden Your client certificate is either not trusted or is invalid.

See certificates step 2.

Warning: Certificate validation failed, subject was CN=ancc_client. RevocationStatusUnknown The revocation function was unable to check revocation for the certificate.

See certificates step 3 or disable recovation check options.RevocationMode = X509RevocationMode.NoCheck

Upvotes: 0

Gunni Saggu
Gunni Saggu

Reputation: 39

options.RevocationMode = X509RevocationMode.NoCheck

setting this worked for me

Upvotes: 0

Sergey L
Sergey L

Reputation: 1492

At the moment your application is not configured to use client certificates. The reason is that you start (host) your application in IIS Express. There're 2 options:

1) The simplest one, switch to run in Project mode (the app will be run in the console window). You can run it manually in the console as well.

2) A little more complex method is to configure your IIS Express to work with client certificates. Following this steps: 2.1) edit \config\applicationhost.config file and change the section below (changes - Deny to Allow).

      <sectionGroup name="security">
        <section name="access" overrideModeDefault="**Allow**" />
        <section name="applicationDependencies" overrideModeDefault="Deny" />
        <sectionGroup name="authentication">
          <section name="anonymousAuthentication" overrideModeDefault="**Allow**" />

2.2) in you project add the following file web.config

<configuration>
    <system.webServer>
        <security>
            <access sslFlags="Ssl,SslNegotiateCert,SslRequireCert" />
          <authentication>
            <anonymousAuthentication enabled="true" />
          </authentication>
        </security>
    </system.webServer>
</configuration>

Next:

The prerequisites for the client authentication to work is to have a client certificate. You can create self signed one using the following commands or any other methods for generating client certificates:

#create key
openssl req -newkey rsa:4096 -keyout key.pem -out csr.pem -nodes -days 365 -subj "/CN=Your name"
#create certificate
openssl x509 -req -in csr.pem -signkey key.pem -out cert.pem -days 365
#self sign it
openssl pkcs12 -export -in cert.pem -inkey key.pem -out your_cert.p12

As this certificate is self signed, you have to add it to the Trusted Root Certification Authorities (on Windows) for the Local Computer (using CertManager).

After it you need to install it (import) to you Personal Certificates Storage using the same CertManager but only for current user. Alternative methods is to use Chrome settings ("Manage certificates"). This is required for Chrome to be able to send the certificate to the server.

Also in you application you might change this option that allows self signed certificates.

            services.AddAuthentication(
                    CertificateAuthenticationDefaults.AuthenticationScheme)
                    .AddCertificate(options => 
                    { 
                        **options.AllowedCertificateTypes = CertificateTypes.All**;

After all these changes it should ask to choose the certificate when you access your site.

Tip: You may not be asked to choose which certificated to use if you are visiting the same page again until you close all chrome instances. if you want it to ask to choose which certificate to use, open a new incognito window.

Upvotes: 2

Related Questions