Skip to main content

Command Palette

Search for a command to run...

Creating a trusted self-signed certificate for development purposes

Updated
6 min read
D

David De Smet is an author, consultant and passionate software developer specializing in microservices and identity and access management with more than 10 years of experience in the Microsoft .NET software development stack.

David currently works as a Full Stack Developer for Definity First and is a contributor of the leading emergency notification and response system for first responders. He also works and contributes to various open-source projects.

We are in the era of running our websites in secure communications (encrypted communication over HTTPS). There are still plenty of websites serving pages without TLS based encryption however the norm is now HTTPS and Google encourages it.

There are free services like Let's Encrypt for generating our TLS certificates and you can also pay for those from Certified Authorities for your live production servers.

But what about local development? In this article, I will explain how to create a self-signed certificate that works on major browsers without giving a warning; at least on Chromium-based browsers like Google Chrome, Microsoft Edge, etc.

Why bother?

Depending on what you are currently working on, sometimes you need your local development to behave similarly to the production environment, or perhaps the technology stack requires you to have secure communication in order to send and/or receive data.

Creating a self-signed certificate for your development environment is pretty straightforward, the problem with this kind of certificate is that web browsers will display a warning message because the certificate isn't verified by a trusted Certificate Authority (CAs) and this is because it only includes a common name which is not trusted by major browsers. The workaround is to create a certificate with a Subject Alternative Name (SAN) for your DNS record.

The process

For the whole process, I will be using openssl so I'll assume you have it already installed.

Also, in this article, I will use an imaginary domain named dev.local since is easy to remember and it tells its purpose but you can use whatever name you like.

We will tell our Operating System to resolve this imaginary address by creating an entry in our hosts file.

# Localhost Development
127.0.0.1    dev.local

On Linux and macOS, this file is located by default at /etc/hosts. On Windows, it is typically found at C:\Windows\System32\drivers\etc\hosts.

Keep in mind that you might need privileged permissions to perform such change.

Now, let's create a private key file that will be used to create our certificate. So let's issue the following command (you will be asked for a passphrase, keep it safe):

ssh-keygen -t rsa -b 4096 -m PEM -f localhost

In order for openssl to be able to read it, we need to convert it to PEM format:

openssl rsa -in localhost -pubout -out localhost.pem

Now that we have our private key generated, we will be creating a localhost.conf file to store our settings which we will be going to pass to openssl.

[ req ]
prompt              = no
default_bits        = 4096
default_keyfile     = localhost.pem
distinguished_name  = subject
req_extensions      = req_ext
x509_extensions     = x509_ext
string_mask         = utf8only

# The Subject DN can be formed using X501 or RFC 4514 (see RFC 4519 for a description).
#   Its sort of a mashup. For example, RFC 4514 does not provide emailAddress.
[ subject ]
countryName         = GB
stateOrProvinceName = London
localityName        = London
organizationName    = David De Smet

# Use a friendly name here because its presented to the user. The server's DNS
#   names are placed in Subject Alternate Names. Plus, DNS names here is deprecated
#   by both IETF and CA/Browser Forums. If you place a DNS name here, then you 
#   must include the DNS name in the SAN too (otherwise, Chrome and others that
#   strictly follow the CA/Browser Baseline Requirements will fail).
commonName          = Localhost development
emailAddress        = dev@example.com

# Section x509_ext is used when generating a self-signed certificate. I.e., openssl req -x509 ...
[ x509_ext ]

subjectKeyIdentifier    = hash
authorityKeyIdentifier  = keyid,issuer

# You only need digitalSignature below. *If* you don't allow
#   RSA Key transport (i.e., you use ephemeral cipher suites), then
#   omit keyEncipherment because that's key transport.
basicConstraints        = CA:FALSE
keyUsage                = digitalSignature, keyEncipherment
subjectAltName          = @alternate_names
nsComment               = "OpenSSL Generated Certificate"

# RFC 5280, Section 4.2.1.12 makes EKU optional
#   CA/Browser Baseline Requirements, Appendix (B)(3)(G) makes me confused
#   In either case, you probably only need serverAuth.
# extendedKeyUsage      = serverAuth, clientAuth

# Section req_ext is used when generating a certificate signing request. I.e., openssl req ...
[ req_ext ]

subjectKeyIdentifier    = hash

basicConstraints        = CA:FALSE
keyUsage                = digitalSignature, keyEncipherment
subjectAltName          = @alternate_names
nsComment               = "OpenSSL Generated Certificate"

# RFC 5280, Section 4.2.1.12 makes EKU optional
#   CA/Browser Baseline Requirements, Appendix (B)(3)(G) makes me confused
#   In either case, you probably only need serverAuth.
# extendedKeyUsage      = serverAuth, clientAuth

[ alternate_names ]

DNS.1         = dev.local

# Add these if you need them. But usually you don't want them or
#   need them in production. You may need them for development.
# DNS.5       = localhost
# DNS.6       = localhost.localdomain
# DNS.7       = 127.0.0.1

# IPv6 localhost
# DNS.8       = ::1

The parts you may want to edit are the subject, email and DNS.1. The default_keyfile holds the filename of your private key in PEM format.

Now we need to tell openssl to use such config file along with some parameters which indicate the desired expiration, the RSA key size, etc. Feel free to edit to your needs.

To continue, let's issue the following command:

openssl req -config localhost.conf -new -x509 -days 365 -sha256 -newkey rsa:4096 -nodes -keyout localhost.key -out localhost.crt

This creates 2 files:

  • localhost.key - The public key of your TLS certificate.
  • localhost.crt - The private key of your TLS certificate.

You can also create a decrypted version of the private key (optional) using the command:

openssl rsa -in localhost.key -out localhost.dec.key

For our last openssl command, we will export our certificate to PKCS#12 format which is an X509 certificate containing both the public and private key, this newly generated file is what we need to serve our app over HTTPS.

Sending the following command will ask you for a password, so keep it safe, you are going to need it later.

openssl pkcs12 -export -in localhost.crt -inkey localhost.key -out localhost.pfx

Now that we have our self-signed certificate, we need to tell our OS to trust it.

On macOS, open Keychain Access app and drag the localhost.crt certificate into it, locate the added certificate in the Certificates section and double click on it, expand and enter the trust section and under When using this certificate select Always Trust. See screen below.

Screen Shot 2021-09-18 at 19.51.58.png

On Windows, you need to copy your certificate to Trusted Root Certification Authorities using the Management Console for Certificates.

On Linux, it varies depending on which flavour of Linux you're using, so please refer to the proper guide.

Configuring your app to use the self-signed certificate

This might differ on the development stack you are using, for the purposes of this article I will be using ASP.NET Core as an example. This project in particular needs to be serving pages in HTTPS since it uses a FIDO2 authenticator.

For simplicity sake, I'll load the certificate from the .pfx file and configure Kestrel to use it to serve requests over HTTPS.

As best practice, you should never include the certificate password (or any other secret) directly in the app so for this matter I'll be using the Secret Manager tool. For more details about such tool, please visit Safe storage of app secrets in development in ASP.NET Core.

dotnet user-secrets set "Kestrel:Certificates:Default:Path" "<path to .pfx file>"
dotnet user-secrets set "Kestrel:Certificates:Default:Password" "<certificate password>"

Finally, run the app:

dotnet run

And navigate to https://dev.local, in my case I'm using Edge as shown below:

Screen Shot 2021-09-18 at 20.15.55.png

And that's it!

I hope you enjoyed this post and if you have any suggestions or improvements, please let me know in the comments below.

T

So very useful, thanks for the advice!

1