Using mkcert for ASP.NET Core Development

Scott Brady
Scott Brady
ASP.NET

While playing around with IdentityServer4 and mTLS client authentication, I was recommended mkcert for generating trusted development certificates. I found this tool to be super simple to use and it saved me from having to use OpenSSL or the PowerShell replacement for MakeCert (New-SelfSignedCertificate).

So, I thought I would document how to use mkcert on Windows and how to use it for some ASP.NET Core development tasks such as client authentication and pfx generation.

Installation and mkcert CA

mkcert is written in Go, and you can run it with a Go run command, but if you’re like me, you’ll want to install it. I did this on Windows using Chocolatey, but you can find alternatives listed in the documentation.

Once you have mkcert installed, you will need to run the following command to install a new Certificate Authority (CA) on your machine.

mkcert -install
The mkcert Certificate Authority that was installed after running mkcert -install. This shows that the CA is issued to mkcert plus followed by my machine name and is valid for 10 years
mkcert Certificate Authority installed on my dev machine

IIS TLS Certificates

With mkcert ready to go, let’s use the example of wanting a certificate for Transport Layer Security (TLS) for the test site on our machine. The cool thing about this approach is that you can issue certificates for reserved top-level domains, including localhost. So, let’s generate a certificate for scottbrady91.test. I’ll leave it to you to set up a hostname record for your chosen domain.

By default, mkcert will two .pem files, one for the certificate, the other for the private key. Windows & IIS like their .p12 and .pfx files, so you will need to use the pkcs12 command to generate that instead:

mkcert -pkcs12 scottbrady91.test

This command should give you the file scottbrady91.test.p12 with a default password of “changeit”. It is a 2048-bit RSA key, valid for 10 years, suitable for Server Authentication, and Digital Signature & Key Encipherment. In my case has a subject of:

CN = scottbrady91.test
OU = DESKTOP-ITA7HLB\Scott@DESKTOP-ITA7HLB (Scott Brady)
O = mkcert development certificate

You can now import this into your Windows certificate store (local machine), and bind it to your IIS site:

IIS binding setup for scottbrady91.test using an installed cert generated using mkcert

Which should then let you navigate to that URL and see a valid certificate (I’m using Chrome, which uses my machine’s CAs).

Chrome window showing scottbrad91.test with a valid TLS certificate

ECDSA IIS TLS

You can even generate a certificate with an ECDSA key using:

mkcert -ecdsa -pkcs12 scottbrady91.test

This certificate has a 256-bit ECC public key and public key parameters of ECDH_P256. This also works nicely with the Windows certificate store and IIS.

Client Certificate Authentication (mTLS)

You can also use certificates generated with mkcert for client authentication with mutual TLS (mTLS). mTLS is where the client uses an X.509 certificate to authenticate themselves with the server that they are calling. This way, both sides of the connection are verified using TLS. mTLS is a popular approach for machine-to-machine authentication with microservices, gRPC, and even for basic proof-of-possession in OAuth.

To generate a client certificate for your server, you can use the client flag in combination with pkcs12:

mkcert -client -pkcs12 scottbrady91.test

This should give you the file scottbrady91.test-client.p12, and a certificate that is suitable for both Client Authentication and Server Authentication.

mTLS in Kestrel

Let’s take a quick look at how to use this client certificate to authenticate access to an ASP.NET Core 3 website. We’re going to run this using Kestrel as this lets us run the site from Visual Studio. If you want to use IIS, you would have to publish the site; as far as I know, you can’t use client certificate authentication in IIS Express.

First, spin up an ASP.NET Core application:

dotnet new mvc

You can then configure Kestrel to require a client certificate to connect to the server. This is done in the KestrelServerOptions, accessible in your web host builder. This enforces client authentication applies to your whole website. If you want to apply it only to a specific path, consider using IIS instead.

public class Program
{
    public static void Main(string[] args)
    {
        CreateHostBuilder(args).Build().Run();
    }

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

                webBuilder.UseStartup<Startup>();
            });
}

We now require a client certificate, but we’re not yet validating it within our application. To do so, you’ll to bring in the following nuget package:

install-package Microsoft.AspNetCore.Authentication.Certificate

Which you can then use to add client certificate authentication to your Startup.cs:

public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddControllersWithViews();

		services.AddAuthentication("mTLS")
			.AddCertificate("mTLS", options
				=> options.RevocationMode = X509RevocationMode.NoCheck);
    }

    public void Configure(IApplicationBuilder app)
    {
        app.UseDeveloperExceptionPage();

        app.UseHttpsRedirection();
        app.UseStaticFiles();

        app.UseRouting();

        app.UseAuthentication();
        app.UseAuthorization();

        app.UseEndpoints(endpoints => endpoints.MapDefaultControllerRoute());
    }
}

I was unable to get mkcert to work with the authentication handler’s revocation check, so I had to disable it. Don’t let that one slip into production.

You can then trigger the authentication handler using an AuthorizeAttribute on one of your endpoints.

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

You can test this using chrome and get challenged for a certificate. The UX isn’t great, but we don’t typically use client certificate authentication via a browser.

Chrome window showing https://localhost:5001 challenging the user to choose a certificate to authenticate with.

You should now get a user loaded in (or, if it went wrong, a 403). Mine had the following claims:

  • issuer: CN=mkcert DESKTOP-ITA7HLB\Scott@DESKTOP-ITA7HLB, OU=DESKTOP-ITA7HLB\Scott@DESKTOP-ITA7HLB, O=mkcert development CA
  • http://schemas.xmlsoap.org/ws/2005/05/identity/claims/thumbprint: 96AF4D125E580B99B2F51E54DC5B5703A164AC39
  • http://schemas.xmlsoap.org/ws/2005/05/identity/claims/x500distinguishedname: CN=scottbrady91.test, OU=DESKTOP-ITA7HLB\Scott@DESKTOP-ITA7HLB (Scott Brady), O=mkcert development certificate
  • http://schemas.microsoft.com/ws/2008/06/identity/claims/serialnumber: 1EF3FA2F9790C4B6B8635179718326F0
  • http://schemas.xmlsoap.org/ws/2005/05/identity/claims/dns: scottbrady91.test
  • http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name: scottbrady91.test

Source Code

You can find the source code for this ASP.NET Core website on GitHub. You’ll need to create your certs for testing though. It’s a good thing you now know how 😀