How to trust self-signed localhost certificates on Linux Chrome and Firefox

fsenart picture fsenart · Jun 10, 2018 · Viewed 19.5k times · Source

I try to generate a self-signed certificate for a custom local domain pointing to 127.0.0.1:

# /etc/hosts
127.0.0.1 subdomain.domain.local

I've generated a self-signed certificate using openssl and remember that everything worked in the past. But it seems that since Chrome 58, there are far more restrictions on using self-signed certificates.

My attempts conclude with "Your connection is not private" following with one of the below errors:

  • "security certificate is not trusted" if I proceed like in the past.
  • "not a certification authority" when trying to import it into Chrome.
  • "subject alternative name missing" when using a certificate after importing its CA.

I'm pretty sure I'm missing something in the process. Please, can anyone provide the valid configuration to handle alternative names along with the exact steps to create the corresponding CA and a certificate so that Chrome and Firefox can handle my local custom domain?

Answer

lionels picture lionels · Jun 10, 2018

TLDR

  1. Create the file generate.sh

    #!/usr/bin/env bash
    find . \( -name "$1.*" -o -name "*.srl" \) -type f -delete
    cp /usr/lib/ssl/openssl.cnf $1.cnf
    python <(
    cat << "END"
    import sys
    from ConfigParser import ConfigParser
    from StringIO import StringIO
    
    domain = sys.argv[1]
    
    config = ConfigParser()
    config.optionxform = lambda option: option
    
    name = "{}.cnf".format(domain)
    
    with open(name, "rb") as stream:
      config.readfp(StringIO("[top]\n" + stream.read()))
    
    config.set(" v3_ca ", "subjectKeyIdentifier", "hash")
    config.set(" v3_ca ", "authorityKeyIdentifier", "keyid:always,issuer")
    config.set(" v3_ca ", "basicConstraints", "critical, CA:TRUE, pathlen:3")
    config.set(" v3_ca ", "keyUsage", "critical, cRLSign, keyCertSign")
    config.set(" v3_ca ", "nsCertType", "sslCA, emailCA")
    
    config.set(" v3_req ", "basicConstraints", "CA:FALSE")
    config.set(" v3_req ", "keyUsage", "nonRepudiation, digitalSignature, keyEncipherment")
    config.set(" v3_req ", "subjectAltName", "@alt_names")
    config.remove_option(" v3_req ", "extendedKeyUsage")
    
    config.add_section(" alt_names ")
    config.set(" alt_names ", "DNS.1", domain)
    config.set(" alt_names ", "DNS.2", "*.{}".format(domain))
    
    config.set(" req ", "req_extensions", "v3_req")
    
    with open(name, "wb") as stream:
        config.write(stream)
    END
    ) $1
    tail -n +2 $1.cnf > $1.cnf.tmp && mv $1.cnf.tmp $1.cnf
    echo "$1\n" | openssl genrsa -aes256 -out $1.ca.key 2048
    chmod 400 $1.ca.key
    openssl req -new -x509 -subj "/CN=$1" -extensions v3_ca -days 3650 -key $1.ca.key -sha256 -out $1.ca.crt -config $1.cnf
    openssl genrsa -out $1.key 2048
    openssl req -subj "/CN=$1" -extensions v3_req -sha256 -new -key $1.key -out $1.csr
    openssl x509 -req -extensions v3_req -days 3650 -sha256 -in $1.csr -CA $1.ca.crt -CAkey $1.ca.key -CAcreateserial -out $1.crt -extfile $1.cnf
    openssl x509 -in $1.crt -text -noout
    
  2. Call ./generate.sh example.com

    Requires Python 2


All credits go to this excellent article by Fabian Lee.

Create a trusted CA and SAN certificate using OpenSSL

  1. Customize openssl.cnf
  2. Create CA certificate
  3. Create Server certificate with SAN signed by CA

Prerequisite

As a prerequisite, ensure the SSL packages are installed:

$ sudo apt install libssl1.0.0 -y

Customized openssl.cnf

The first step is to grab the openssl.cnf template available on your system. On Ubuntu this can be found at /usr/lib/ssl/openssl.cnf. You may find this in /System/Library/OpenSSL/ on MacOS, and /etc/pki/tls on Redhat variants.

export prefix="mydomain"

cp /usr/lib/ssl/openssl.cnf $prefix.cnf

$prefix.cnf needs be modified with the specific information about the cert we are going to generate.

Under the [ v3_ca ] section, add the following values. For the CA, this signifies we are creating a CA that will be used for key signing.

[ v3_ca ]
subjectKeyIdentifier=hash
authorityKeyIdentifier=keyid:always,issuer
basicConstraints = critical, CA:TRUE, pathlen:3
keyUsage = critical, cRLSign, keyCertSign
nsCertType = sslCA, emailCA

Then under the [ v3_req ] section, set the following along with all the valid alternative names for this certificate.

[ v3_req ]
basicConstraints = CA:FALSE
keyUsage = nonRepudiation, digitalSignature, keyEncipherment
#extendedKeyUsage=serverAuth
subjectAltName = @alt_names

[ alt_names ]
DNS.1 = mydomain.com 
DNS.2 = *.dydomain.com

Also uncomment the following line under the [ req ] section so that certificate requests are created with v3 extensions.

req_extensions = v3_req

When we generate each type of key, we specify which extension section we want to use, which is why we can share $prefix.cnf for creating both the CA as well as the SAN certificate.

Create CA Certificate

Now we will start using OpenSSL to create the necessary keys and certificates. First generate the private/public RSA key pair:

openssl genrsa -aes256 -out ca.key.pem 2048

chmod 400 ca.key.pem

This encodes the key file using an passphrase based on AES256. Then we need to create the self-signed root CA certificate.

openssl req -new -x509 -subj "/CN=myca" -extensions v3_ca -days 3650 -key ca.key.pem -sha256 -out ca.pem -config $prefix.cnf

You can verify this root CA certificate using:

openssl x509 -in ca.pem -text -noout

This will show the root CA certificate, and the Issuer and Subject will be the same since this is self-signed. This is flagged as CA:TRUE meaning it will be recognized as a root CA certificate; meaning browsers and OS will allow it to be imported into their trusted root certificate store.

Issuer: CN=myca 
... 
Subject: CN=myca 
... 
X509v3 Basic Constraints: 
  critical CA:TRUE, pathlen:3 
X509v3 Key Usage: 
  critical Certificate Sign, CRL Sign 
Netscape Cert Type: 
  SSL CA, S/MIME CA

Create Server certificate signed by CA

With the root CA now created, we switch over to the server certificate. First generate the private/public RSA key pair:

openssl genrsa -out $prefix.key.pem 2048

We didn’t put a passphrase on this key simply because the CA is more valuable target and we can always regenerate the server cert, but feel free to take this extra precaution.

Then create the server cert signing request:

openssl req -subj "/CN=$prefix" -extensions v3_req -sha256 -new -key $prefix.key.pem -out $prefix.csr

Then generate the server certificate using the: server signing request, the CA signing key, and CA cert.

openssl x509 -req -extensions v3_req -days 3650 -sha256 -in $prefix.csr -CA ca.pem -CAkey ca.key.pem -CAcreateserial -out $prefix.crt -extfile $prefix.cnf

The $prefix.key.pem is the server private key and $prefix.crt is the server certificate. Verify the certificate:

openssl x509 -in $prefix.crt -text -noout

This will show the certificate, and the Issuer will be the CA name, while the Subject is the prefix. This is not set to be a CA, and the Subject Alternative Name field contains the URLs that will be considered valid by browsers.

Issuer: 
 CN=myca 
... 
Subject: 
  CN=mydomain 
... 
X509v3 Basic Constraints: 
  CA:FALSE 
X509v3 Key Usage: 
  Digital Signature, Non Repudiation, Key Encipherment 
X509v3 Subject Alternative Name:
  DNS:mydomain.com, DNS:*.mydomain.com

Browser Evaluation

When you first point Chrome or Firefox at the site with your SAN cert with CA signing, it will throw the same type of exceptions as a self-signed SAN cert. This is because the root CA cert is not known as a trusted source for signed certificates.

Chrome

Linux

On Linux, Chrome manages its own certificate store and again you should import ca.pem into the Authorities. This should now make the security icon turn green. enter image description here

Windows

In Chrome settings (chrome://settings), search for certificates and click on Manage Certificates. On Windows this will open the Windows certificate manager and you should import the ca.pem file at the Trusted Root Certification Authorities tab. This is equivalent to adding it through mmc.exe, in the local user trusted root store (not the computer level).

Firefox

In Firefox Options about:preferences, search for certificates and click View Certificates. Go to the Authorities tab and import ca.pem. Check the box to have it trust websites, and now the lock icon should turn green when you visit the page. enter image description here