Openssl - Generate Self-signed Certificate for Chrome
Introduction
After the release of Google Chrome 58, self-signed certificates were no longer working in Chrome as they once did. Google started enforcing new security rules and this made Chrome consider many certificates invalid.
In my case, the problems I encountered were:
- The Subject Common Name was invalid
NET::ERR_COMMON_CERT_INVALID
- The cert did not have a Subject Alternative Name
Developer tools said "Subject Alternative Name Missing"
- The cert used a weak algorithm
NET::ERR_CERT_WEAK_SIGNATURE_ALGORITHM
- Importing a cert into Chrome as a server certificate no longer worked
Contents
Why is this a problem?
I access my personal servers a lot, and I use self-signed certificates. Before Chrome 58, I could import the certificate as a server cert, and this would bypass the SSL error screen and would display a secure lock icon with green text on the address bar. This saved me the time of clicking through the error, but the lock icon also told me that I'm accessing my server. If someone man-in-the-middle attacked me I would get a certificate error, thus giving me a blatant indicator that something is wrong.
I searched a long time for a solution, but I abandoned the effort for a while. For about 6 months, I was seeing an SSL error every time I accessed my servers. Now since I see an SSL error all the time, if someone attacked me I wouldn't notice. "Thanks Google...", I thought, "...for making your browser more secure for everyone except me." I can't complain about them adding security features, but not providing any guides or documentation that helps the average Joe Techie generate valid self-signed certificates, it's just cruel.
The following section describes the process that worked for me.
Note: I'm using OpenSSL 1.1.0g 2 Nov 2017
Generating an RSA Key
The rest of this tutorial assumes you already have an RSA key. If you don't already have a key, the following command will generate a new 2048 bit RSA key that is not locked with a password (so your web server can start without human intervention).
openssl genrsa -out server.key 2048
Generating a CA Cert
I'm assuming you're using openssl on Linux (or MacOS) in this tutorial. I've simplified things using a bash script, but if you're doing this on a Windows platform, you can simply create a custom openssl.cnf
file that has the added lines that I create in my script.
In my case, I'm using this cert for my Apache server. I'm going to backup my old certificates, cd
into the folder where I keep the certs, and create a shell script (do these steps as root):
cd /etc/apache2/
cp -r ssl ssl.orig
cd ssl
touch make-cert.sh
chmod u+x make-cert.sh
Before we continue, we need to understand some things.
Openssl Config File
Openssl uses a default config file called openssl.cnf
. Different systems have this file in different locations. If you can't find it, search for it:
find / -type f -name openssl.cnf
Replace the ssl_config variable in the bash script below with the correct openssl.cnf
file location for your system.
If you take a look at the contents of openssl.cnf
you will see a section like this:
[ v3_ca ]
# ..several comments..
subjectKeyIdentifier=hash
authorityKeyIdentifier=keyid:always,issuer
basicConstraints = CA:true
This is the magic that makes our cert a CA cert. We can add this as an extension, but we are going to create a custom version of v3_ca so we can include multiple Subject Alternative Names.
Organization Name
Notice in the script below that I've chosen "AAA" as the Organization Name (/O=AAA) and Organizational Unit (/OU=AAA). This was just to make the cert appear at the top of the cert list in Chrome. It was faster for troubleshooting. You can choose any name you want.
Common Name
Your Subject Common Name (/CN=name) needs to be valid. Chrome no longer considers a simple hostname a valid CN. You need to include a domain name. In my example, because I'm using this cert for accessing my server from my local network (I use a different cert for public access), it's sufficient to use server.mydomain.local
.
Alternative Names
Notice the [ v3_ca_with_san ]
extension I created (below) is slightly modified, and includes the subjectAltName
value which refers to another custom extension that I named [ alt_names ]
. The [ alt_names ]
extension then defines several DNS names. You must include the Common Name (ie. server.mydomain.local) as an Alternative Name. After that you can add any other alternative names you want, and even IP addresses. Be sure to number them as seen below.
Hash Algorithm
Lastly, we are including the -sha256
parameter to the openssl
command to ensure we are using the minimum strength hash algorithm that is accepted in Chrome. You could also use -sha512
.
Checklist
You are almost ready to create your script. Here's a checklist of assumptions for this script:
- You already have a private key called
server.key
- You want the output certificate to be saved to
server.crt
- You've edited ssl_config variable in this script to the correct location of your
openssl.cnf
file on your system - Your Subject Common Name is valid (must have domain name, ie. "server.mydomain.local")
- You've added all the Alternative Names you want to use
- You've edited/added organization name, email, etc. to reflect your needs
The Script
Now edit the make-cert.sh
script and add these contents (modify to meet your needs):
#!/bin/bash
#
# Generate CA certificate from a pre-existing key that works with Chrome 58+.
#
input_key=server.key
output_crt=server.crt
ssl_config=/usr/lib/ssl/openssl.cnf
ssl_subj=$(cat <<EOF
/C=US
/ST=California
/L=SomeCity
/emailAddress=root@server.mydomain.local
/O=AAA
/OU=AAA
/CN=server.mydomain.local
EOF
)
ssl_custom=$(cat <<EOF
[ v3_ca_with_san ]
subjectKeyIdentifier=hash
authorityKeyIdentifier=keyid:always,issuer
basicConstraints = critical,CA:true
subjectAltName = @alt_names
[alt_names ]
DNS.1 = server
DNS.2 = www
DNS.3 = server.mydomain.local
DNS.4 = www.mydomain.local
IP.1 = 192.168.0.2
EOF
)
openssl req \
-key "$input_key" \
-x509 \
-nodes \
-new \
-out "$output_crt" \
-subj $(echo -n $ssl_subj|tr -d ' ') \
-reqexts 'v3_ca_with_san' \
-extensions 'v3_ca_with_san' \
-config <(cat "$ssl_config" \
<(printf "$ssl_custom")) \
-sha256 \
-days 10000
# Restart Apache. I did this for quick testing. You don't need to include this.
systemctl restart apache2
Read through this script to understand what it's doing. Now run the script:
./make-cert.sh
If there was no output, it likely completed successfully. There should be a server.crt
file and your Apache server be restarted. You can now import the certificate into Chrome.
Concatenating the Key and Certificate
This is not necessary in most cases, but I've run into web applications that expect the key and the certificate to be contained in a single file (this does not cause the application to send the private key to end users). Simply combine them into one file with the key at the top.
cp server.crt server.crt.orig
cat server.key server.crt.orig > server.crt
Sometimes it's expected that the certificate file have a .pem extension instead of .crt, but usually either will work.
Importing the Certificate into Chrome
Open Chrome and navigate to your site via HTTPS (ie. https://server.mydomain.local
). You will now see the following SSL error.
Open the Developer tool by pressing F12
or Shift + Ctrl + I
. Click the Security tab and click View Certificate.
In the Certificate Viewer click the Details tab. Look through the details to make sure you are, in fact, recieving the correct certificate. Click the Export... button at the bottom.
Save the file as a Base64-encoded ASCII, single certificate and be sure to add a .pem
extension to the file name. For some reason, Chrome doesn't export the cert with an extension, but when you import a cert it expects an extension. Logical, eh?
Go to the main menu in Chrome and click Settings. Scroll to the bottom and click Advanced. Under the Privacy and Security section click on Manage Certificates.
Click on the Authorities tab and click IMPORT. Locate the server.mydomain.local.pem
file you exported and click Open.
Check all three boxes and click Ok. You should be able to scroll down the Authorities list and find your certificate in the list.
Close all Chrome windows and open it again. You should now be able to navigate to your site via HTTPS. No SSL errors and a nice, green lock icon. Success!!
Importing the Certificate into Android
I found that Chrome on Android does not have the option to export a certificate the same way as before. Also, you don't import the cert into the browser, you import it into the operating system's credential storage. Also consider that some Android devices are different, so your process may differ from mine.
First, download the certificate to your SD card. I just attached my phone via USB to my computer and transferred the PEM file.
Go to Settings and click on the General tab. Then click on Security.
Go to Certificate Management and click on Install from Storage.
Locate the cert file you saved and click it. You will be asked to confirm your PIN (or lock pattern, fingerprint, etc.).
Now enter your Certificate name. This is just a superficial name; I used the server name. Everything else you can leave as it is. Now click Ok.
You should now be able to open your browser (close any existing tabs) and navigate to your server via HTTPS without any SSL errors.