10

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????

yogihosting
  • 5,494
  • 8
  • 47
  • 80

5 Answers5

3

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;
        }
    }
Rob
  • 3,556
  • 2
  • 34
  • 53
palcoder
  • 31
  • 2
1

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.

Sergey L
  • 1,402
  • 1
  • 9
  • 11
  • When I switch Project mode in VS 2019 then I get 'This site can’t be reached' & 'ERR_SPDY_INADEQUATE_TRANSPORT_SECURITY'. Please suggest? – yogihosting Mar 02 '20 at 12:12
  • 1
    Which url do you use to access the site? For Project mode, it typically localhost:5001 or similar. If you do not add local server certificate or other certificate you use, to the trusted root, you should enable to navigate to this site. Click on advanced link under the warning. Any screenshots might also help to understand your problem. – Sergey L Mar 02 '20 at 12:26
  • Url is https://localhost:5001/. I explain, first I created 2 certificates which are root certificate & client certificate in C# (using this tutorial https://damienbod.com/2019/06/27/using-chained-certificates-for-certificate-authentication-in-asp-net-core-3-0/). Then I added the root certificate to the 'Trusted Root Certification Authorities' from CertManager like you said. I also imported the 'Client certificate' to the chrome browser like you said. I get the error - 'ERR_SPDY_INADEQUATE_TRANSPORT_SECURITY'. What is wrong with it, thank you? – yogihosting Mar 02 '20 at 12:48
  • 1
    The problem might in a way, how you created these certificates. let's do it in steps. 1) remove your root certificate from the project if you used it. Run the project the the localhost certificate created by VS. it should ask if you want to add as a root certificate, you may agree. 2) If the rest is fine and you access you site it should either ask you to choose a client certificate or return an error (403) – Sergey L Mar 02 '20 at 13:21
  • 1
    3) If you good so far, create a client self signed cert, as I described, add it to chrome and root certificate storage as described. Access the site, choose this added certificate, you should be able to access the site. If something does not work as described, log the errors and do not move to the next step. if you managed to pass the step #3, it will mean it works with simple cert, you might start creating more complex certificates. – Sergey L Mar 02 '20 at 13:21
  • 1
    I believe this problem might related how you create the cert, chick on the lock icon and see what certificate you use. Also, it might be related: https://stackoverflow.com/questions/53086676/cowboy-webserver-using-http2-and-tls-getting-err-spdy-inadequate-transport-secur https://support.microsoft.com/en-us/help/187498/how-to-disable-pct-1-0-ssl-2-0-ssl-3-0-or-tls-1-0-in-internet-informat – Sergey L Mar 02 '20 at 13:35
0
options.RevocationMode = X509RevocationMode.NoCheck

setting this worked for me

0

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

joacar
  • 891
  • 11
  • 28
-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.

  • To the best of my knowledge, you're accepting anything as valid here, so there's no actual certification happening. – Eike Mar 17 '22 at 10:25