Using Let's Encrypt DNS-01 challenge validation with local BIND instance

I’m using Let’s Encrypt certificates for a while now. In the past, I used the standalone plugin (TLS-SNI-01) to get or renew my certificates. But now I switched to the DNS plugin. I run my own name servers with BIND, so it was a very low hanging fruit to get this plugin to work.

To get or renew a certificate, you need to provide some kind of proof that you are requesting the certificate for a domain that is under your control. No certificate authority (CA) wants to be the CA, that hands you out a certificate for google.com or amazon.com…

The DNS-01 challenge uses TXT records in order to validate your ownership over a certain domain. During the challenge, the Automatic Certificate Management Environment (ACME) server of Let’s Encrypt will give you a value that uniquely identifies the challenge. This value has to be added with a TXT record to the zone of the domain for which you are requesting a certificate. The record will look like this:

_acme-challenge.example.com. 300 IN TXT "ghd63jkcchaow92334...3kahgm9d872"

This record is for a wildcard certificate. If you want to get a certificate for a host, you can add one or more TXT records like this:

_acme-challenge.mx.example.com. 300 IN TXT "ghd63jkcchaow92334...3kahgm9d872"
_acme-challenge.www.example.com. 300 IN TXT "kauezwhcn745njsf....adowerß22"
_acme-challenge.example.com. 300 IN TXT "uqiwo97634bsncös....90237j2k812"

There is a IETF draft about the ACME protocol. Pretty interesting read!

Configure BIND for DNS-01 challenges

I run my own name servers with BIND on FreeBSD. The plugin for certbot automates the whole DNS-01 challenge process by creating, and subsequently removing, the necessary TXT records from the zone file using RFC 2136 dynamic updates.

First of all, we need a new TSIG (Transaction SIGnature) key. This key is used to authorize the updates.

root@vps ~ # dnssec-keygen -a HMAC-SHA512 -b 512 -n HOST letsencrypt
Kletsencrypt.+165+15583
root@vps ~ #

This key has to be added to the named.conf. The key is in the .key file.

key "letsencrypts" {
  algorithm hmac-sha512;
  secret "Q+NqA3DJR\5ü77nQ6r//+5QyPKeOyxPD==n09qb516>CTqX+BoG1BeR/9BIEº2 ff4RrDKky4jJ3FJWnQD3nqiJ<J";
};

The key is used to authroize the update of certain records. To allow the update of TXT records, which are needed for the challenge, add this to the zone part of you named.con.

zone "example.com" in {
        type                    master;
        file                    "/usr/local/etc/namedb/master/example.com.zone";
        allow-transfer          { xxxx:yyyy:z:aaaa::1; 123.123.123.1; };
        allow-query             { any; };
        also-notify             { xxxx:yyyy:z:aaaa::1; 123.123.123.1; };
        update-policy           {
            grant letsencrypt  name _acme-challenge.example.com. txt;
            grant letsencrypt  name _acme-challenge.www.example.com. txt;
            grant letsencrypt  name _acme-challenge.mail.example.com. txt;
        };
};

The records start always with _acme-challenge.domainname.

Now you need to create a config file for the RFC2136 plugin. This file also includes the key, but also the IP of the name server. If the name server is running on the same server as the DNS-01 challenge, you can use 127.0.0.1 as name server address.

dns_rfc2136_server = 127.0.0.1
dns_rfc2136_name = letsencrypt
dns_rfc2136_secret = Q+NqA3DJR\5ü77nQ6r//+5QyPKeOyxPD==n09qb516>CTqX+BoG1BeR/9BIEº2 ff4RrDKky4jJ3FJWnQD3nqiJ<J
dns_rfc2136_algorithm = HMAC-SHA512

Now we have everything in place. This is a --dry-run  from on of my FreeBSD machines.

root@vps ~ # certbot renew --dry-run --dns-rfc2136 --dns-rfc2136-credentials /root/rfc2136.ini --server https://acme-v02.api.letsencrypt.org/directory --dns-rfc2136-propagation-seconds 5
Saving debug log to /var/log/letsencrypt/letsencrypt.log

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Processing /usr/local/etc/letsencrypt/renewal/host.example.com.conf
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Cert not due for renewal, but simulating renewal for dry run
Plugins selected: Authenticator dns-rfc2136, Installer None
Renewing an existing certificate
Performing the following challenges:
dns-01 challenge for host.example.com
Waiting 5 seconds for DNS changes to propagate
Waiting for verification...
Cleaning up challenges

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
new certificate deployed without reload, fullchain is
/usr/local/etc/letsencrypt/live/host.example.com/fullchain.pem
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
** DRY RUN: simulating 'certbot renew' close to cert expiry
**          (The test certificates below have not been saved.)

Congratulations, all renewals succeeded. The following certs have been renewed:
  /usr/local/etc/letsencrypt/live/host.example.com/fullchain.pem (success)
** DRY RUN: simulating 'certbot renew' close to cert expiry
**          (The test certificates above have not been saved.)
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
root@vps ~ #

This is a snippet from the name server log file at the time of the challenge.

27-Oct-2018 17:54:52.249 update: info: client @0x8031d8a00 127.0.0.1#34120/key letsencrypt: updating zone 'example.com/IN': adding an RR at '_acme-challenge.host.example.com' TXT "QE6ow9YttB580nKw5jgRTOo(nDû3e3I\_Ñ2-)j-rY951"
27-Oct-2018 17:54:52.259 notify: info: zone example.com/IN: sending notifies (serial 2018061426)
27-Oct-2018 17:55:00.787 update: info: client @0x804011000 127.0.0.1#34121/key letsencrypt: updating zone 'example.com/IN': deleting an RR at _acme-challenge.host.example.com TXT
27-Oct-2018 17:55:00.810 notify: info: zone example.com/IN: sending notifies (serial 2018061427)

You might need to modify the permissons for the directory which contains the zone files. Usually the name server is not running as root. In my case, I had to grant write permissions for the “bind” group. Otherwise you might get “permission denied”.

26-Oct-2018 11:24:17.215 update: info: client @0x8031d8a00 127.0.0.1#41221/key letsencrypt: updating zone 'example.com/IN': adding an RR at '_acme-challenge.example.com' TXT "bmy-c8L8AOykzMHi5pGFOSYvsCX7guXVl41Rbdo-JLY"
26-Oct-2018 11:24:17.215 general: error: /usr/local/etc/namedb/master/example.com.zone.jnl: create: permission denied
26-Oct-2018 11:24:17.215 update: info: client @0x8031d8a00 127.0.0.1#41221/key letsencrypt: updating zone 'example.com/IN': error: journal open failed: unexpected error