Setting up ASP.NET Core dev certs for both WSL and Windows

A while back, I wrote this blog post about setting up ASP.NET Core dev certificates for both WSL and Windows. However, when running through the steps today, it didn’t work… So I decided to write an updated version that is a bit simpler, and works…

For those of you who haven’t read the old post, here is some background information. If you ever want to do your ASP.NET Core development using both WSL (using the Remote - WSL extension) and Windows, you will soon realize that there are some inherent issues with the local development certs… Mainly that ASP.NET Core sets up one development certificate in Windows, and one in Linux. And neither environment trusts the other.

There are 2 solutions to this. One would be to get Windows to trust the Linux cert, and then get Linux to trust the Windows one. Another would be to get Linux to use and trust the Windows one, which is what I have opted for.

In the previous post, there were quite a few steps involved in getting it to work. However, there seems to be a much easier way. A way that also seems to work in later versions of .NET Core.

Dev certs in Windows

The first time you start an ASP.NET Core application on Windows, it installs a development HTTPS certificate for you automatically. All you have to do is run dotnet dev-certs https --trust to trust it and you are done!

Using the Windows dev certs in WSL

Just as with Windows, the first time you run an ASP.NET Core application in WSL, a development HTTP cert is created and used. However, as mentioned before, this is not the same cert as the one used in Windows, which is what is causing the problem. The easiest way to get around this is to get WSL to use and trust the Windows generated cert instead. So that’s what I’m going to do.

The first step is to get hold of the certificate that ASP.NET installed in the certificate store. This can be done by opening PowerShell and running

> dotnet dev-certs https -ep C:\temp\aspnetcore.pfx -p <SECURE PASSWORD>

A valid HTTPS certificate is already present.

You get a really awkward “A valid HTTPS certificate is already present.” as a response, but that’s apparently the expected result.

Note: If you get an error that there is no valid HTTPS certificate (if you for example haven’t run any ASP.NET Core apps yet), try running dotnet dev-certs https --trust to generate one.

Now that you have the cert you want to use, you need to tell WSL to use that instead of the one that ASP.NET Core has created inside your Linux environment. This is actually a very simple thing to do. Just open a WSL terminal and run

> dotnet dev-certs https --clean --import /mnt/c/temp/aspnetcore.pfx -p <SECURE PASSWORD>

HTTPS development certificates successfully removed from the machine.
The certificate was successfully imported.

This means that whenever you start an ASP.NET Core application in WSL, it will now use the same certificate that is used if you started it in Windows. It also means that Windows already trusts the certificate, so you will get no certificate warnings in your browser if you use you Windows browser to browse to an ASP.NET Core application running in WSL. Awesome!

However, this is only part of the problem… The second part is to have you WSL environment trust the certificate as well.

Why is this important you might wonder? Well, having Windows trust it means that we do not get SSL warnings in our browser. And any JavaScript that calls an WSL endpoint will happily accept the certificate that is presented. However, if you are doing server to server communication it will fail. The reason for this is simply that when your WSL-based application calls the ASP.NET Core application, it won’t trust the self-signed certificate. So this must be fixed as well…

This is once again a fairly simple task, including 2 steps. Step one is to export the public key, formatted as PEM. This is done by running

> sudo dotnet dev-certs https -ep /usr/local/share/ca-certificates/aspnet/https.crt --format PEM

A valid HTTPS certificate is already present.

Once again you get that awkward “A valid HTTPS certificate is already present.”, but that’s ok. It seems to be the default response for anything that works…

This command will export the public key to a subfolder of ca-certificates. This is where Linux stores public key for certs it should trust. However, you also need to tell Linux to trust it. Just placing the file there isn’t enough, it needs to be trusted as well. Something that can be done by running

> sudo update-ca-certificates

Updating certificates in /etc/ssl/certs...
1 added, 0 removed; done.
...

That’s all there is to it. WSL should you use the same cert as Windows. And both Windows and Linux should now trust that cert!

Note: This needs to be done per WSL distro you are using!

However, there is one more thing I want to mention…

Changing certs when using IIS Express

If you for some reason decide to reset your ASP.NET Core dev cert, IIS Express won’t play nice unfortunately. The reason for this is that IIS Express uses HTTP.SYS, which sets up a list that contains the certificates to bind to different ports. By default, IIS Express (or maybe Visual Studio, not sure) seems to bind ports 44300 to 44399 to the default self-signed certificate. This causes some problems if you ever change cert. Something that can happen if you for example happen to delete the IIS Express self-signed cert. Don’t ask me how I know…

You can see the current SSL port bindings by running

> netsh http show sslcert

And if you look through that list, you will likely see that all ports between 44300 and 44399 are pre-emptively bound to the same dev cert even if they aren’t actually in use yet. And if you want to change the cert being used for a port, you need do 2 things.

First, you need to make sure that the cert you want to use is available in your to the local machine’s certificate store, in the Personal > Certificates folder. And since the dotnet dev-certs https command only adds the certificate to the users certificate store, it needs to be added to the local machine’s as well. This can be done by running the following command in an elevated PowerShell terminal

> $pwd = ConvertTo-SecureString <SECURE PASSWORD> -AsPlainText -Force
> Import-PfxCertificate -FilePath c:\temp\aspnetcore.pfx -CertStoreLocation Cert:\LocalMachine\My -Password $pwd

This adds the cert to the local machine’s store.

Note: This assumes that you still have the PFX-file in the temp folder from previous commands.

Next, you need to bind the certificate to the port you want to use. This isn’t hard at all, if you just have the thumbprint of the cert. If not, you can get hold of it using the certificate MMC snap-in, or by running a PowerShell command that looks like this

> Write-Host (Get-ChildItem -Path Cert:\LocalMachine\My | Where-Object {$_.Subject -match "CN=localhost"}).ThumbPrint

Tip: If you get more than one thumbprint, you probably have both the new and the old IIS Express cert in the store. Make sure you pick the one you want to use!

Once you have the thumbprint, you need to bind it to the port you want to use using netsh. Like this

> netsh http update sslcert ipport=0.0.0.0:<PORT> appid='{<APPID>}' certhash=<CERT_HASH>

Where PORT is the port you want to use, APPID is any GUID you want and CERT_HASH is the thumbprint you just retrieved.

Another options is to use IisExpressAdminCmd.exe. This is a little nicer as it doesn’t care about any GUIDs. All you need to do is to navigate to C:\Program Files (x86)\IIS Express and run IisExpressAdminCmd.exe. Like this

> cd "C:\Program Files (x86)\IIS Express"
> .\IisExpressAdminCmd.exe setupsslUrl -url:https://localhost:<PORT>/ -CertHash:<THUMBPRINT>

Where THUMBPRINT is the certificate thumbprint for you cert, and PORT is the port that you want to bind it to.

Both options end up with exactly the same result. And either way, you can verify the rebound port by running

> netsh http show sslcert ipport=0.0.0.0:<PORT>

This should show you that the cert you want to use is now bound to that port.

Unfortunately, this needs to be done for every port you want to use through IIS Express, which is a bit tedious.

So, if you want to make sure you get the same experience as you would with the original set up, which allows you to spin up Visual Studio, start a new project, press F5, and the cert just works, you need to execute the following.

For ($i=0; $i -le 99; $i++) {
   .\IisExpressAdminCmd.exe setupsslUrl -url:"https://localhost:443$($i.ToString().PadLeft(2, "0"))/" -CertHash:<THUMBPRINT>
}

This will go through all the pre-bound ports (44300 -> 44399) and re-bind them to the new cert. This should make everything “just work”, allowing you to transparently use your new self-signed cert in IIS Express.

Note: Personally, I prefer to run the application through the terminal instead, as it allows me to view the log outputs easily. But having that said, IIS Express is still a very valid option!

Clean up

There isn’t a lot to clean up as the certs are stored in the Windows certificate store, and in the corresponding places in WSL. However, we still have that exported PFX version of the cert. So don’t forget to delete that by running

> rm C:\temp\aspnetcore.pfx

Conclusion

I am not sure why the previous solution to this problem failed to be honest, however, this one is defintiely a LOT easier!

The IIS Express stuff is a bit annoying, but should not really be necessary unless you for some reason need to (or happen to) replace the original certificate in Windows. However, since I did happen to do that while trying a few things for this post, I thought I might as well cover that. Also, it was covered in the old post, and I wanted this post to be a complete replacement of the old to be honest…

Hopefully this solves your WSL/Windows SSL problems!

If you have any comments or questions, feel free to reach out at @ZeroKoll!

zerokoll

Chris

Developer-Badass-as-a-Service at your service