SSL cert from local ACME server for OpenWrt

There is an acme package available for OpenWrt which allows to obtain SSL certificates from Let’s Encrypt. Wouldn’t it be nice if we could have OpenWrt obtain SSL certificates from our own private ACME server (within our local network)? This blog post explains the easy steps in order to achieve just that.

What’s wrong with the default ACME package?

Actually, there is nothing wrong with the default package. However, it doesn’t allow for any ACME server URL other than Let’s Encrypt’s URL. These are our requirements:

  1. make use of the –server parameter so that we can supply the URL of our internal ACME server
  2. make use of the –ca-bundle parameter so that we can verify the SSL certificate of our internal ACME server
  3. make use of the –days parameter so that we can set automatic renewal of certificates to something (way) less than the default 60 days

What needs to change?

There is one config file that we have to customize and there is a script that we need to extend. Also, we need to install our internal root certificate so that acme.sh can verify the SSL certificate of our internal ACME server.

/etc/config/acme

As soon as you install the acme package, a default configuration file will be created. Within the acme section we need to supply our credentials (our email address). There is one cert section that we can assign a name to it, although the name doesn’t really matter as in most cases we will be ok with just having the one cert section anyway. The last 3 lines in that configuration file are our own and will not be parsed by the default acme package (we will handle that in a minute). All 3 options correspond to the command line parameters of the official acme.sh script.

config acme
        option state_dir        '/etc/acme'
        option account_email    'john.doe@example.com'
        option debug            '0'

config cert 'localserver'
        option keylength        '2048'
        option update_uhttpd    '1'
        list domains            'openwrt.local'
        list domains            'openwrt'
        option use_staging      '0'
        option enabled          '1'
        option server           'https://acme.local:8443/acme/weekly/directory'
        option cabundle         '/etc/ssl/certs/tinkivity.pem'
        option renewal_days     '3'

/usr/lib/acme/run-acme

While OpenWrt’s acme package calls an unmodified acme.sh script, it does so via a wrapper script. The name of that wrapper script is run-acme and we need to insert 9 lines of code to handle our own configuration parameters.

Find the issue_cert() subroutine and go to the first line that modifies the acme_args parameter. Now insert the following lines (see marked in red below) into the script.

    [ "$enabled" -eq "1" ] || return

    [ "$DEBUG" -eq "1" ] && acme_args="$acme_args --debug"

    local server
    local cabundle
    local renewal_days
    config_get server "$section" server
    config_get cabundle "$section" cabundle
    config_get renewal_days "$section" renewal_days
    [ -n "$cabundle" ] && acme_args="$acme_args --ca-bundle $cabundle"
    [ -n "$server" ] && acme_args="$acme_args --server $server"
    [ -n "$renewal_days" ] && acme_args="$acme_args --days $renewal_days"

    set -- $domains
    main_domain=$1

Our first 3 custom lines (lines 5, 6 and 7 above) declare a local variable within the shell script. The next 3 lines (lines 8, 9 and 10 above) use OpenWrt’s functional API to get our custom parameters from the /etc/config/acme configuration file. The last 3 lines (lines 11, 12 and 13 above) append our custom configuration values to the command line parameters that will be handed over to acme.sh.

Test run

The only thing left to do is to test our modifications…

root@OpenWrt:~# /usr/lib/acme/run-acme
acme: Running pre checks for openwrt.local.
4+0 records in
4+0 records out
Generating a RSA private key
..........................+++++
......................+++++
writing new private key to '/etc/acme/openwrt.local/openwrt.local.key.new'
acme: Running ACME for openwrt.local
acme: Using standalone mode
Standalone mode.
Create account key ok.
Registering account
Registered
ACCOUNT_THUMBPRINT='2dYMwVSlI3Ewk69JJIpdhDa63uYdmoRiAWrBP64O8zg'
Creating domain key
The domain key is here: /etc/acme/openwrt.local/openwrt.local.key
Single domain='openwrt.local'
Getting domain auth token for each domain
Getting webroot for domain='openwrt.local'
Verifying: openwrt.local
Standalone mode server
Success
Verify finished, start to sign.
Lets finalize the order, Le_OrderFinalize: https://acme.local:8443/acme/weekly/order/mdApyrB1mSTDfrG0oJP97hCfo9MapD4g/finalize
Download cert, Le_LinkCert: https://acme.local:8443/acme/weekly/certificate/zT0aO2NdTXhIJZXE8DsSGdw1d6dw0Pbs
Cert success.
-----BEGIN CERTIFICATE-----
MIIFrDCCA5SgAwIBAgIQbFGdrtEr8eQq2McxMc2+ojANBgkqhkiG9w0BAQsFADCB
...
<< REDACTED >>
...
DPnJoXMPK0WR2dkRHtpZDQ==
-----END CERTIFICATE-----
Your cert is in  /etc/acme/openwrt.local/openwrt.local.cer 
Your cert key is in  /etc/acme/openwrt.local/openwrt.local.key 
The intermediate CA cert is in  /etc/acme/openwrt.local/ca.cer 
And the full chain certs is there:  /etc/acme/openwrt.local/fullchain.cer 
acme: Running post checks (cleanup).