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. Not to mention that Linux doesn’t even trust its own cert, making server to server communication hard in Linux.
Unfortunately, the tools provided by dotnet
doesn’t quite seem to do the trick when trying to get mutual trust to work. I’m not sure why, but at least on my machine, any cert that is generated by dotnet
has problems when it comes to being able to trust it in Linux. So because of this, there are a few hoops we need to jump through to get this to work…
Note: My guess is that Linux requires a CA to issue the cert to be able to trust it. However, the cert generated by .NET is not a properly issued cert with a CA as this has some inherent dangers… Dangers I will ignore on my development box, and try to mitigate by keeping my cert VERY safe
In the previous post, there were quite a few steps involved in getting it to work. However, it can be simplified a bit at least…and made to work…
Dev certs in Linux
When you install the .NET SDK, an ASP.NET developer certificate is generated and configured for use by ASP.NET. However, it doesn’t seem like that cert is being properly trusted by Linux, causing server to server communication to fail. Because of this, we need to generate our own self-signed cert. Luckily, this isn’t too hard with the help of Google…
The first step is to create an OpenSSL configuration file that looks like this
[req]
prompt = no
default_bits = 2048
distinguished_name = subject
req_extensions = req_ext
x509_extensions = x509_ext
[subject]
commonName = localhost
[req_ext]
basicConstraints = critical, CA:true
subjectAltName = @alt_names
[x509_ext]
basicConstraints = critical, CA:true
keyUsage = critical, keyCertSign, cRLSign, digitalSignature,keyEncipherment
extendedKeyUsage = critical, serverAuth
subjectAltName = critical, @alt_names
1.3.6.1.4.1.311.84.1.1 = ASN1:UTF8String:ASP.NET Core HTTPS development certificate
[alt_names]
DNS.1 = localhost
DNS.2 = 127.0.0.1
Note: This config creates a certificate that is both a CA and an SSL cert. The reason for this is that Linux needs a CA cert to be able to trust it.
Once we have our OpenSSL configuration, we can go ahead and generate our certificate by running
> openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
-keyout localhost.key \
-out localhost.crt \
-config localhost.conf
This generates a key pair based on the configuration file we just created.
Just for the sake of it, we can verify that it currently isn’t trusted, by running
> openssl verify localhost.crt
CN = localhost
error 18 at 0 depth lookup: self signed certificate
error localhost1.crt: verification failed
To trust the cert, we need to copy it to /usr/local/share/ca-certificates, and add it to our trusted CA:s. Like this
> sudo cp localhost.crt /usr/local/share/ca-certificates
> sudo update-ca-certificates
After that, we should be able to verify that the cert is trusted by running openssl verify
again
> openssl verify localhost.crt
localhost.crt: OK
The last step is to make sure that ASP.NET Core uses this certificate when SSL is turned on. This is easily done by running the dotnet dev-certs
command. However, this command expects a PKCS12 certificate. So before we can do that, we need to convert our key pair into a PKCS12 cert using OpenSSL
openssl pkcs12 -export -out localhost.pfx -inkey localhost.key -in localhost.crt
Once we have out PFX-file, we can finally tell ASP.NET to use it by running
dotnet dev-certs https --clean --import ./localhost.pfx -p <PASSWORD>
Remember: Make sure it is a secure password! If anyone should get their hands on this cert, it could be used to launch a malicious attach against your machine. So keeping it safe is VERY important! And also make sure that the key pair is stored in a safe place.
Ok, that’s it from the Linux side…
Dev certs in Windows
When you install the .NET Core SDK on Windows, it creates a development HTTPS certificate for you automatically just like it does in Linux. All you have to do is run dotnet dev-certs https --trust
to trust it and you are good to go! However, in this case we want to replace that cert with the cert we just created…
The first step in doing this, is to get hold of the PFX file we just generated. On my machine, I copy it to a user specific folder by running the following command in WSL
cp ./localhost.pfx /mnt/c/Users/<USERNAME>/.aspnet/https
Next, we need to tell ASP.NET to use it by running the following command in PowerShell
PS> dotnet dev-certs https --clean --import "${env:USERPROFILE}\.aspnet\https\localhost.pfx" -p <PASSWORD>
However, this installs the cert in the Personal directory. And since the cert has a CA (itself), it needs to be added to Trusted Root Certificate Authorities as well.
Once that is done, you should have the same cert in WSL and Windows. And both Windows and Linux should trust it!
Note: If you are using multiple WSL environments, you need to make sure that you copy the cert to the other environments as well. Making sure to both trust the cert, and add it as a dev-cert using dotnet dev-certs
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 preemptively 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 definitely 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!