Generating a self-signed cert with openssl that works in Chrome 58

Solution 1:

My solution:

openssl req \
    -newkey rsa:2048 \
    -x509 \
    -nodes \
    -keyout server.key \
    -new \
    -out server.crt \
    -subj /CN=dev.mycompany.com \
    -reqexts SAN \
    -extensions SAN \
    -config <(cat /System/Library/OpenSSL/openssl.cnf \
        <(printf '[SAN]\nsubjectAltName=DNS:dev.mycompany.com')) \
    -sha256 \
    -days 3650

Status: Works for me

Solution 2:

In Windows, save this script in your SSL folder as makeCERT.bat. It will create these files: example.cnf, example.crt, example.key

@echo off

REM IN YOUR SSL FOLDER, SAVE THIS FILE AS: makeCERT.bat
REM AT COMMAND LINE IN YOUR SSL FOLDER, RUN: makecert
REM IT WILL CREATE THESE FILES: example.cnf, example.crt, example.key
REM IMPORT THE .crt FILE INTO CHROME Trusted Root Certification Authorities
REM REMEMBER TO RESTART APACHE OR NGINX AFTER YOU CONFIGURE FOR THESE FILES

REM PLEASE UPDATE THE FOLLOWING VARIABLES FOR YOUR NEEDS.
SET HOSTNAME=example
SET DOT=com
SET COUNTRY=US
SET STATE=KS
SET CITY=Olathe
SET ORGANIZATION=IT
SET ORGANIZATION_UNIT=IT Department
SET EMAIL=webmaster@%HOSTNAME%.%DOT%

(
echo [req]
echo default_bits = 2048
echo prompt = no
echo default_md = sha256
echo x509_extensions = v3_req
echo distinguished_name = dn
echo:
echo [dn]
echo C = %COUNTRY%
echo ST = %STATE%
echo L = %CITY%
echo O = %ORGANIZATION%
echo OU = %ORGANIZATION_UNIT%
echo emailAddress = %EMAIL%
echo CN = %HOSTNAME%.%DOT%
echo:
echo [v3_req]
echo subjectAltName = @alt_names
echo:
echo [alt_names]
echo DNS.1 = *.%HOSTNAME%.%DOT%
echo DNS.2 = %HOSTNAME%.%DOT%
)>%HOSTNAME%.cnf

openssl req -new -x509 -newkey rsa:2048 -sha256 -nodes -keyout %HOSTNAME%.key -days 3560 -out %HOSTNAME%.crt -config %HOSTNAME%.cnf

Solution 3:

Here is a solution that works for me:

Create CA key and cert

# openssl genrsa -out server_rootCA.key 2048
# openssl req -x509 -new -nodes -key server_rootCA.key -sha256 -days 3650 -out server_rootCA.pem

Create server_rootCA.csr.cnf

# server_rootCA.csr.cnf
[req]
default_bits = 2048
prompt = no
default_md = sha256
distinguished_name = dn

[dn]
C=DE
ST=Berlin
L=NeuKoelln
O=Weisestrasse
OU=local_RootCA
[email protected]
CN = server.berlin

Create v3.ext configuration file

# v3.ext
authorityKeyIdentifier=keyid,issuer
basicConstraints=CA:FALSE
keyUsage = digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment
subjectAltName = @alt_names

[alt_names]
DNS.1 = server.berlin

Create server key

# openssl req -new -sha256 -nodes -out server.csr -newkey rsa:2048 -keyout server.key -config <( cat server_rootCA.csr.cnf )

Create server cert

# openssl x509 -req -in server.csr -CA server_rootCA.pem -CAkey server_rootCA.key -CAcreateserial -out server.crt -days 3650 -sha256 -extfile v3.ext

Add cert and key to Apache2 site-file, HTTPS (port 443) section

SSLCertificateFile    /etc/apache2/ssl/server.crt
SSLCertificateKeyFile    /etc/apache2/ssl/server.key

Copy server_rootCA.pem from the server to your machine..

# scp [email protected]:~/server_rootCA.pem .

.. and add it to Chromium browser

Chromium -> Setting -> (Advanced) Manage Certificates -> Import -> 'server_rootCA.pem'

YOU ARE ALL DONE!

P.S. Instead of creating a functional CA & server cert pair (per the instructions above) you could simply disable HSTS headers in your HTTP server config. This will prevent Chromium from enforcing HTTPS and will allow users to click “Advanced → proceed to your.url (unsafe)” without having to obtain and install your custom CA (server_rootCA.pem) certificate. In other words – having to disable HSTS will allow your site to be publicly viewed over HTTP and/or insecure HTTPS connection (beware!).

For Apache2 add the following to site-file, HTTP (port 80) section

Header unset Strict-Transport-Security
Header always set Strict-Transport-Security "max-age=0;includeSubDomains"

Tested on Debian/Apache2.4 + Debian/Chromium 59

https://ram.k0a1a.net/self-signed_https_cert_after_chrome_58


Solution 4:

There are several great answers that give examples of how to get this working, but none that explain where things went wrong in your attempt. OpenSSL can be pretty non-intuitive some times so it is worth walking through.

First, as an aside, OpenSSL defaults to ignoring any distinguished name values you provide in the config. If you want to use them you must add prompt = no to your config. In addition, the command as written only generates a certificate request not a certificate itself, so the -days command does nothing.

If you generate your certificate request using this command you gave and inspect the result, the Subject Alt Name is present:

$ openssl req -new -key server.key -out server.csr -config config.cnf -sha256
$ openssl req -text -noout -in server.csr
Certificate Request:
    Data:
        Version: 1 (0x0)
        Subject: C = US, ST = Massachusetts, L = Boston, O = MyCompany
        Subject Public Key Info:
            Public Key Algorithm: rsaEncryption
                Public-Key: (2048 bit)
                Modulus:
                    ...
                Exponent: 65537 (0x10001)
        Attributes:
        Requested Extensions:
            X509v3 Subject Alternative Name:
                DNS:dev.mycompany.com
    Signature Algorithm: sha256WithRSAEncryption
         ...

But then if you generate the certificate using the command in heroku link and inspect the result, the Subject Alt Name is missing:

$ openssl x509 -req -sha256 -days 365 -in server.csr -signkey server.key -out server.crt
$ openssl x509 -text -noout -in server.crt
Certificate:
    Data:
        Version: 1 (0x0)
        Serial Number:
            89:fd:75:26:43:08:04:61
    Signature Algorithm: sha256WithRSAEncryption
        Issuer: C = US, ST = Massachusetts, L = Boston, O = MyCompany
        Validity
            Not Before: Jan 21 04:27:21 2018 GMT
            Not After : Jan 21 04:27:21 2019 GMT
        Subject: C = US, ST = Massachusetts, L = Boston, O = MyCompany
        Subject Public Key Info:
            Public Key Algorithm: rsaEncryption
                Public-Key: (2048 bit)
                Modulus:
                    ...
                Exponent: 65537 (0x10001)
    Signature Algorithm: sha256WithRSAEncryption
         ...

The reason is that by default OpenSSL does not copy extensions from the request to the certificate. Normally, the certificate would be created/signed by a CA based on a request from a customer, and some extensions could grant the certificate more power than the CA was intending if they were to blindly trust the extensions defined in the request.

There are ways to tell OpenSSL to copy the extensions, but IMHO it is more work than just providing the extensions in a config file when you generate the certificate.

If you were to attempt to use your existing config file, it won't work because the top level section is marked [req] so those settings only apply to the req command not the x509 command. It isn't necessary to have a top-level section marker, so you can just remove that first line, and then it will work fine for both generating requests or certificate.

$ openssl x509 -req -sha256 -days 365 -in server.csr -signkey server.key -out server.crt -extfile config.cnf

Alternately, you can use the -x509 argument to the req command to generate a self-signed certificate in a single command, rather than first creating a request and then a certificate. In this case it isn't necessary to remove the [req] section line, as that section is read and used by the req command.

$ openssl req -x509 -sha256 -days 365 -key server.key -out server.crt -config config.cnf

To recap, here is the modified config file used in the above commands:

default_bits        = 2048
distinguished_name  = dn
x509_extensions     = san
req_extensions      = san
extensions          = san
prompt              = no
[ dn ]
countryName         = US
stateOrProvinceName = Massachusetts
localityName        = Boston
organizationName    = MyCompany
[ san ]
subjectAltName      = DNS:dev.mycompany.com

Solution 5:

Bash script with config baked in

As a shell script that should work across platforms with bash. Assumes HOSTNAME env set for the shell or supply a hostname of your choosing, e.g. self_signed_cert.sh test

set -e

if [ -z "$1" ]; then
  hostname="$HOSTNAME"
else
  hostname="$1"
fi
    
local_openssl_config="
[ req ]
prompt = no
distinguished_name = req_distinguished_name
x509_extensions = san_self_signed
[ req_distinguished_name ]
CN=$hostname
[ san_self_signed ]
subjectAltName = DNS:$hostname, DNS:localhost, IP:127.0.0.1, IP:::1
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid:always,issuer
basicConstraints = CA:true
keyUsage = nonRepudiation, digitalSignature, keyEncipherment, dataEncipherment, keyCertSign, cRLSign
extendedKeyUsage = serverAuth, clientAuth, timeStamping
"
    
openssl req \
  -newkey rsa:2048 -nodes \
  -keyout "$hostname.key.pem" \
  -x509 -sha256 -days 3650 \
  -config <(echo "$local_openssl_config") \
  -out "$hostname.cert.pem"
openssl x509 -noout -text -in "$hostname.cert.pem"

The above more or less injects the bare minimum config file info openssl needs.

Note, included extra DNS:localhost as a SAN to allow testing via localhost more easily. Remove that extra bit from the script if you don't want it.

Credit

bcardarella's answer is great (can't comment/up-vote due insufficient rep). However, the answer uses an existing openssl config file location that is platform specific... hence:

Works for me

Obviously, one would simply need to find the openssl config file for your own given platform and substitute the correct location.

Test

For a way to test, import test.cert.pem into chrome's authorities in chrome://settings/certificates, and:

openssl s_server -key test.key.pem -cert test.cert.pem -accept 20443 -www &
openssl_pid=$!
google-chrome "https://localhost:20443"
google-chrome "https://127.0.0.1:20443"
google-chrome "https://[::1]:20443"

And after testing

kill $openssl_pid