Using Let's Encrypt in manual mode

Note: See also the newer article about auto-renewal of Let's Encrypt certificates with acme_tiny.

Let's Encrypt is a great project that aims to increase security in the web by making it easy and cheap (free, in fact) to obtain SSL certificates. Part of their aim is to make sure web servers are configured correctly. In order to do all this, the letsencrypt program generates the private key for you and changes your HTTP server configuration file.

This article describes how to use letsencrypt on a computer that is not necessarily the target server and without having to run the program as super user.

This post uses ideas from Johannes Schauer's blog and several posts on the Let’s Encrypt Forum.

Creating a key and a Certificate Signing Request

One cool thing about Let's Encrypt is that they allow more than two domains per certificate. You can add any number of domains you control in the SAN section. For this you need to enumerate all domains in the subjectAltName value in the [SAN] section of the openssl configuration file, e.g. this:

[SAN]
subjectAltName=DNS:example.com,DNS:www.example.com,DNS:example.net,DNS:www.example.net

The following command creates a secret key and a CSR. The letsencrypt command only accepts CSR files in DER format, so you'll have to use the -outform der option. Also change the values in the -subj option to your location and your details. Use the primary domain as the CN. If the -subj option is not used then openssl will query those values interactively.

openssl req \
    -new -newkey rsa:2048 -sha256 -nodes \
    -keyout privkey1.pem -out signreq.der -outform der \
    -subj "/C=UK/ST=Some State/L=Some Place/O=example.com/emailAddress=webmaster@example.com/CN=example.com" \
    -reqexts SAN

Submitting the CSR to the Let's Encrypt CA

Use the manual mode in letsencrypt to submit the CSR and to obtain the certificate.

The script may ask for the sudo password, which can be safely ignored.

letsencrypt certonly \
    --authenticator manual \
    --server https://acme-v01.api.letsencrypt.org/directory --text \
    --email webmaster@example.com \
    --csr signreq.der

For each domain in the SAN list the letsencrypt program will ask you to store a file in the /.well-known/acme-challenge directory on the web server.

Note that each domain you submit must be accessible both from the internet and from the computer where the letsencrypt program is run.

Automating it all

The following script automates the process described above. To use the script, change the variables country, state, town and email and call it with a list of domains you want to include in the certificate, separated by space.

#!/bin/sh
#
# Wrapper script for the letsencrypt client to generate a server certificate in
# manual mode. It uses openssl to generate the key and should not modify the
# server configuration. It can be called off-side, i.e. not on the destination
# server.
#
# usage: gencert DOMAIN [DOMAIN...]
#
# This is free and unencumbered software released into the public domain.
# For more information, please refer to http://unlicense.org/

set -e

if [ $# -lt 1 ]; then
    echo "$0: error: at least one domain name required."
    exit 1
fi
domain=$1

shift
other_domains=
while [ $# -gt 0 ]; do
    other_domains="$other_domains,DNS:$1"
    shift
done


country=UK
state="Some State"
town="Some Place"
email=webmaster@example.com

outdir="certs/$domain"
key="$outdir/privkey1.pem"
csr="$outdir/signreq.der"

if [ -d "$outdir" ]; then
    echo "output directory $outdir exists"
    exit 1
fi

tmpdir=
cleanup() {
    if [ -n "$tmpdir" -a -d "$tmpdir" ]; then
        rm -rf "$tmpdir"
    fi
}
trap cleanup INT QUIT TERM EXIT
tmpdir=`mktemp -d -t mkcert-XXXXXXX`

sslcnf="$tmpdir/openssl.cnf"
cat /etc/ssl/openssl.cnf > "$sslcnf"
echo "[SAN]" >> "$sslcnf"
echo "subjectAltName=DNS:$domain$other_domains" >> "$sslcnf"

mkdir -p "$outdir"
openssl req \
    -new -newkey rsa:2048 -sha256 -nodes \
    -keyout "$key" -out "$csr" -outform der \
    -subj "/C=$country/ST=$state/L=$town/O=$domain/emailAddress=$email/CN=$domain" \
    -reqexts SAN \
    -config "$sslcnf"

letsencrypt certonly \
    --authenticator manual \
    --server https://acme-v01.api.letsencrypt.org/directory --text \
    --config-dir letsencrypt/etc --logs-dir letsencrypt/log \
    --work-dir letsencrypt/lib --email "$email" \
    --csr "$csr"

Updates

3 December 2015
Removed the deprecated --agree-dev-preview option.
10 December 2015
Added feed-back from Holger Janning.
4 January 2016
Added copyright statement in the script.
31 January 2016
Added link to the acme_tiny post.