Rodrigo Rosenfeld Rosas

Testing HTTPS in a Linux development environment with self-signed certificates

Fri, 01 Dec 2017 19:12:00 +0000 (last updated at Sun, 03 Dec 2017 11:45:00 +0000)

Note: if you only care about getting the certificates, jump to the end of the article and you'll find a button to just do that. This way you don't even need Linux to generate them.

For a long time I've been testing my application locally using a certificate issued by Let's encrypt, which I must renew every few months for domains such as dev.mydomain.com. Recently, I've been considering creating a new app and I don't have a domain for it yet.

So I decided to take some time to learn how to create self-signed certificates in such a way that browsers such as Chrome and Firefox would accept it without any disclaimer with no extra step.

It took me about 2 hours to be able achieve this task, so I decided to write it down so that it would save me time in the future when I need to repeat this process.

I'll use the myapp.example.com domain for my new app, since the example.com domain is reserved.

The first step is add that domain in /etc/hosts:

1127.0.0.1 localhost myapp.example.com

Recent browsers will require the subject alternate names extension, so the script will generate that extension using a template like this:

1[SAN]
2subjectAltName = @alternate_names
3
4[ alternate_names ]
5
6DNS.1 = myapp.example.com
7IP.1 = 127.0.0.1
8IP.2 = 192.168.0.10

Replace the second IP with your own fixed IP if you have one just in case you need to access it from another computer in the network, like some VM, for example. Edit the script below to change the template. You'll need to add the root CA certificate we'll generate soon to those other computers in the network in order to do so, as I'll explain in the last steps in this article. Just remove IP.2 if you don't care about it.

Then create this script to help generating the certificates in ~/.ssl/generate-certificates:

1#!/bin/bash
2
3FQDN=${1:-myapp.example.com}
4
5# Create our very own Root Certificate Authority
6
7[ -f my-root-ca.key.pem ] || \
8openssl genrsa -out my-root-ca.key.pem 2048
9
10# Self-sign our Root Certificate Authority
11
12[ -f my-root-ca.crt.pem ] || \
13openssl req -x509 -new -nodes -key my-root-ca.key.pem -days 9131 \
14 -out my-root-ca.crt.pem \
15 -subj "/C=US/ST=Utah/L=Provo/O=ACME Signing Authority Inc/CN=example.net"
16
17# Create Certificate for this domain
18
19[ -f ${FQDN}.privkey.pem ] || \
20openssl genrsa -out ${FQDN}.privkey.pem 2048
21
22# Create the extfile including the SAN extension
23
24cat > extfile <<EOF
25[SAN]
26subjectAltName = @alternate_names
27
28[ alternate_names ]
29
30DNS.1 = ${FQDN}
31IP.1 = 127.0.0.1
32IP.2 = 192.168.0.10
33EOF
34
35# Create the CSR
36
37[ -f ${FQDN}.csr.pem ] || \
38openssl req -new -key ${FQDN}.privkey.pem -out ${FQDN}.csr.pem \
39 -subj "/C=US/ST=Utah/L=Provo/O=ACME Service/CN=${FQDN}" \
40 -reqexts SAN -extensions SAN \
41 -config <(cat /etc/ssl/openssl.cnf extfile)
42
43# Sign the request from Server with your Root CA
44
45[ -f ${FQDN}.cert.pem ] || \
46openssl x509 -req -in ${FQDN}.csr.pem \
47 -CA my-root-ca.crt.pem \
48 -CAkey my-root-ca.key.pem \
49 -CAcreateserial \
50 -out ${FQDN}.cert.pem \
51 -days 9131 \
52 -extensions SAN \
53 -extfile extfile
54
55# Update this machine to accept our own root CA as a valid one:
56
57sudo cp my-root-ca.crt.pem /usr/local/share/ca-certificates/my-root-ca.crt
58sudo update-ca-certificates
59
60cat <<EOF
61Here's a sample nginx config file:
62
63server {
64 listen 80;
65 listen 443 ssl;
66
67 ssl_certificate ${PWD}/${FQDN}.cert.pem;
68 ssl_certificate_key ${PWD}/${FQDN}.privkey.pem;
69
70 root /var/www/html;
71
72 index index.html index.htm index.nginx-debian.html;
73
74 server_name ${FQDN};
75
76 location / {
77 # First attempt to serve request as file, then
78 # as directory, then fall back to displaying a 404.
79 try_files $uri $uri/ =404;
80 }
81}
82EOF
83
84grep -q ${FQDN} /etc/hosts || echo "Remember to add ${FQDN} to /etc/hosts"

Then run it:

1cd ~/.ssl
2chmod +x generate-certificates
3./generate-certificates # will generate the certificates for myapp.example.com
4
5# to generate for another app:
6./generate-certificates otherapp.example.com

The script will output a sample nginx file demonstrating how to use the certificate and will remind you about adding the entry to /etc/hosts if it detects the domain is not present already.

That's it. Even curl should work out-of-the-box, just like browsers such as Chrome and Firefox:

1curl -I https://myapp.example.com

If you need to install the root certificate in other computers in the network (or VMs), it's located in ~/.ssl/my-root-ca.crt.pem. If the other computers are running Linux:

1# The .crt extension is important
2sudo cp my-root-ca.crt.pem /usr/local/share/ca-certificates/my-root-ca.crt
3sudo update-ca-certificates

I didn't research about how to install them in other OS, so please let me know in the comments if you know and I'll update the article explaining the instructions for setting up VM guests of other operating systems.

I've also created a Docker container with a simple Ruby Rack application to generate those certs. The code is simple and is available at Github.

It's also published to Docker Hub.

You can give it a try here:

I hope you'll find it useful as much as I do ;)

Powered by Disqus