Using Let's Encrypt - Entware/Entware GitHub Wiki

Introduction

This How-To helps to configure A+ SSL protected web server, using nginx and Let's Encrypt as a certificate issuer right on router.

Requirements

  • You need domain name (my.domain.ru as example below) which is resolved to router IP address,
  • You need TCP80 and TCP443 ports open on router.

Installation

Install necessary packages:

opkg install bash ca-certificates coreutils-mktemp curl diffutils grep nginx openssl-util

Download helper script to get/renew Let's Encrypt certificates:

cd /opt/etc/nginx
curl -O https://raw.githubusercontent.com/lukas2511/dehydrated/master/dehydrated

Edit two sections at /opt/etc/nginx/nginx.conf:

  • in server section:

server_name "my.domain.ru";

  • in location section:

root /opt/share/nginx/html;

Now start web server and make sure it's available from internet as http://my.domain.ru

/opt/etc/init.d/S80nginx start

Prepare folder, which will be used by Let's Encrypt service to make sure this web server belongs to you:

echo WELLKNOWN="/opt/share/nginx/html/.well-known/acme-challenge" > config
mkdir -p /opt/share/nginx/html/.well-known/acme-challenge

and start script to get new SSL sertificate:

bash ./dehydrated --domain my.domain.ru --cron

You'll get following answer if all goes fine:

/opt/etc/nginx # bash ./dehydrated --domain my.domain.ru --cron
# INFO: Using main config file /opt/etc/nginx/config.sh
Processing my.domain.ru
 + Signing domains...
 + Generating private key...
 + Generating signing request...
 + Requesting challenge for my.domain.ru...
 + Responding to challenge for my.domain.ru...
 + Challenge is valid!
 + Requesting certificate...
 + Checking certificate...
 + Done!
 + Creating fullchain.pem...
 + Done!

Note: You may encounter errors here depending on your settings. Many people have found it necessary to add the following to their server block for their configuration

location ^~ /.well-known {
	allow all;
}

The next step can take from five minutes (on Asus RT-N66U) to one hour (on Asus RT-N14U). If you've got Linux PC, you can run following command on PC then transfer dhparams.pem to router:

openssl dhparam -out dhparams.pem 2048

Now, configure HTTPS on nginx by adding following strings to server section of /opt/etc/nginx/nginx.conf:

listen 443 ssl;
include ssl.conf;

and put following content to /opt/etc/nginx/ssl.conf:

ssl_certificate /opt/etc/nginx/certs/my.domain.ru/fullchain.pem;
ssl_certificate_key /opt/etc/nginx/certs/my.domain.ru/privkey.pem;
ssl_ciphers 'ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:AES:CAMELLIA:DES-CBC3-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!aECDH:!EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA';
ssl_prefer_server_ciphers on;
ssl_dhparam /opt/etc/nginx/dhparams.pem;

ssl_session_cache shared:SSL:10m;
ssl_session_timeout 5m;
# ssl_stapling on;

add_header Strict-Transport-Security "max-age=31536000; includeSubDomains";

Restart web server and make sure https://my.domain.ru is available from internet:

/opt/etc/init.d/S80nginx restart

Renewing of SSL certificates

By default, the Let's Encrypt SSL certificate is valid for 3 months. You can renew it from cron automatically:

opkg install cron

Create cron job by placing following script to /opt/etc/cron.monthly/renew_certs.sh:

#!/bin/sh
cd /opt/etc/nginx
bash ./dehydrated --domain my.domain.ru --cron
nginx -s reload

and make it executable:

chmod +x /opt/etc/cron.monthly/renew_certs.sh

Don't forget to start cron:

/opt/etc/init.d/S10cron start

Known Issues


Depending on the version of busybox you are running on your router, there is an issue with the tr command that was later fixed, whereby lowercase letter 'u' is incorrectly replaced with a lowercase letter 'l', and uppercase letter 'U' is ignored when doing uppercase->lowercase replacements.

As a workaround, if you have a domain with a letter U/u in it, you can change the following line in dehydrated - and then be sure to use all lowercase letters when passing your domain name(s).

Change Line 627 in dehydrated

From the Following:

for line in $(<"${DOMAINS_TXT}" tr -d '\r' | tr '[:upper:]' '[:lower:]' | _sed -e 's/^[:space:](/Entware/Entware/wiki/:space:)*//g' -e 's/[:space:](/Entware/Entware/wiki/:space:)*$//g' -e 's/[:space:](/Entware/Entware/wiki/:space:)+/ /g' | (grep -vE '^(#|$)' || true)); do

to the below:

for line in $(<"${DOMAINS_TXT}" tr -d '\r' | _sed -e 's/^[:space:](/Entware/Entware/wiki/:space:)*//g' -e 's/[:space:](/Entware/Entware/wiki/:space:)*$//g' -e 's/[:space:](/Entware/Entware/wiki/:space:)+/ /g' | (grep -vE '^(#|$)' || true)); do

Also, other issues encountered, for example those upgrading to the latest asus firmwares in 2018, many of the scripts "checks" to ensure items are installed will fail due to new version incompatibilities. However the script still works fine if these checks are bypassed.

Bypass can be accomplished by commenting out almost all of the following lines as indicated below starting at line 30:

# Check for script dependencies
check_dependencies() {
  # just execute some dummy and/or version commands to see if required tools exist and are actually usable
  # openssl version > /dev/null 2>&1 || _exiterr "This script requires an openssl binary."
  # _sed "" < /dev/null > /dev/null 2>&1 || _exiterr "This script requires sed with support for extended (modern) regular expressions."
  # command -v grep > /dev/null 2>&1 || _exiterr "This script requires grep."
  # _mktemp -u > /dev/null 2>&1 || _exiterr "This script requires mktemp."
  # diff -v > /dev/null || _exiterr "This script requires diff."

  # curl returns with an error code in some ancient versions so we have to catch that
  # set +e
  # curl -V > /dev/null 2>&1
  # retcode="$?"
  # set -e
  # if [ ! "${retcode}" = "0" ](/Entware/Entware/wiki/-!-"${retcode}"-=-"0"-) && [ ! "${retcode}" = "2" ](/Entware/Entware/wiki/-!-"${retcode}"-=-"2"-); then
  #   _exiterr "This script requires curl."
  # fi
  echo "Bypassing Version Checks..."
}

Links