Stephen Ostermiller's Blog

LetsEncrypt for HTTPS on a Tomato Router’s Web Administration

I generate a LetsEncrypt wildcard certificate for my domain name. On my home LAN, each device has a subdomain (device.example.com). When those devices have a web interface for administration, I enable HTTPS and give the device the Let's Encrypt certificate to use. This allows you to administer the device without seeing scary warnings about security from your browser.

I installed FreshTomato on my router. Getting the certificate onto it is pretty easy if you use the script I wrote.

#!/bin/bash

###
### Transfer Lets Encrypt SSL certificates from a Linux computer
### to a router running Tomato firmware.
###
### Example usage (run on the Linux box that has the certs):
###     ./cert-to-router.sh example.com root@192.168.1.1
### Where example.com is the site name of the Lets Encrypt certificates
### and root@192.168.1.1 is the ssh user and host string for the router
###
### Prerequisites: 
### 1. Lets Encrypt certificates are already generated and in
###    /etc/letsencrypt/live/example.com/
### 2. You can ssh into the router running Tomato
### 3. "Administration" -> "Admin Access" on the router has:
###    - "Local Access" that includes HTTPS
###    - Checked "Save in NVRAM" under "SSL Certificate"
###

set -e
set -o pipefail

if [ ! -e "/etc/letsencrypt/live/" ]
then
    echo "Could not find Lets Encrypt certificates in /etc/letsencrypt/live/"
    exit 1
fi

letsencryptsite=
router=

for arg in "$@"
do
    if [ -e "/etc/letsencrypt/live/$arg" ]
    then
        letsencryptsite="$arg"
    else
        router="$arg"
    fi
done

if [ "z$letsencryptsite" == "z" ]
then
    echo "No Lets Encrypt site name specified"
    exit 1
fi

if [ "z$router" == "z" ]
then
    echo "No router specified"
    exit 1
fi

# Create the archive in the format expected by Tomato
#   If you run "nvram get https_crt_file" on the router
#   then base64 decode the output, it will be a gzipped 
#   tar file containing two files: 
#   etc/cert.pem and etc/key.pem
# This command creates the same format but with 
#   the certificates from Lets Encrypt:
#   cert.pem is from Lets Encrypt's fullchain.pem
#   key.pem is from Lets Encrypt's privkey.pem
# --directory is used to specify where the files come from.
# --transform is used to do the renaming.
# --dereference is needed because the Lets Encrypt directory
#   contains symlinks that need to be followed.
# --gzip applies the compression
# --create specifies an archive is being created
# --file - outputs on stdout to the pipe
# base64 is used to encode the archive.
# tr is used to remove the new-lines.
echo "Creating certificate archive from /etc/letsencypt/live/$letsencryptsite"
b64=`tar --directory /etc/letsencrypt/live/$letsencryptsite --dereference --transform "s|fullchain|etc/cert|;s|privkey|etc/key|" --gzip --create --file - fullchain.pem  privkey.pem | base64 --encode | tr -d '\n'`

#DEBUG: output base64
#echo "$b64"

#DEBUG: verify contents of archive
#echo "$b64" | base64 --decode | tar -tzf -

# A single SSH command to log into the router and do the work
# 1. The tar archive is expanded in the router's file system
#    The base64 is decoded and passed in on stdin and the
#    tar command reads it and and writes it to the filesystem.
# 2. The base 64 encoded version of the file is set in 
#    non-volatile memory (nvram).
#    When Tomato boots, the file system resets, but it expands 
#    this tar archive to replace the certificates
# 3. nvram is written (commit)
# 4. The web server (httpd) is restarted to use the new
#    certificate right away
echo "Transferring to router via ssh: $router"
echo "$b64" | base64 --decode | ssh $router "echo Extracting to file system && tar -C / -xvzf - && echo Storing in nvram && nvram set https_crt_save=1 && nvram set https_crt_file=$b64 && echo Persisting nvram && nvram commit && echo Restarting web server && service httpd restart"

Leave a comment

Your email address will not be published. Required fields are marked *