Let's Encrypt Logo

Dehydrated ACME Client

Dehydrated is a client for signing certificates with an ACME server (as provided by Let’s Encrypt and others) implemented as a relatively simple bash-script.

pdns_api.sh, is a simple DNS hook for Dehydrated, which uses the HTTP API of a PowerDNS server to answer the challenge requests of the ACME server.

Installation

Dehydrated Shell Script

$ cd /usr/local/lib
$ sudo git clone --no-checkout https://github.com/lukas2511/dehydrated.git
$ sudo git tag
v0.1.0
v0.2.0
v0.3.0
v0.3.1
$ sudo git checkout v0.3.1

PowerDNS API Hook Script

$ cd /usr/local/lib
$ sudo git clone --no-checkout https://github.com/silkeh/pdns_api.sh.git
$ cd pdns_api.sh
v0.1.0
v0.2.0
$ sudo git checkout v0.2.0

Configuration

Dehydrated Configuration

/etc/dehydrated/config

########################################################
# This is the main config file for dehydrated          #
#                                                      #
# This file is looked for in the following locations:  #
# $SCRIPTDIR/config (next to this script)              #
# /usr/local/etc/dehydrated/config                     #
# /etc/dehydrated/config                               #
# ${PWD}/config (in current working-directory)         #
#                                                      #
# Default values of this config are in comments        #
########################################################

# Resolve names to addresses of IP version only (curl).
# supported values: 4, 6
# Default: <unset>
#IP_VERSION=

# Path to certificate authority.
# Default: https://acme-v01.api.letsencrypt.org/directory
#CA="https://acme-v01.api.letsencrypt.org/directory"
CA="https://acme-staging.api.letsencrypt.org/directory"

# Path to license agreement.
# default: https://letsencrypt.org/documents/LE-SA-v1.1.1-August-1-2016.pdf
#LICENSE="https://letsencrypt.org/documents/LE-SA-v1.1.1-August-1-2016.pdf"

# Which challenge should be used? Currently http-01 and dns-01 are supported.
#CHALLENGETYPE="http-01"
CHALLENGETYPE="dns-01"

# Path to a directory containing additional config files, allowing to override
# the defaults found in the main configuration file. Additional config files
# in this directory needs to be named with a '.sh' ending.
# Default: <unset>
#CONFIG_D=

# Base directory for account key, generated certificates and list of domains
# Default: $SCRIPTDIR -- uses config directory if undefined
BASEDIR="/etc/dehydrated"

# File containing the list of domains to request certificates for.
# Default: "$BASEDIR/domains.txt"
#DOMAINS_TXT="${BASEDIR}/domains.txt"

# Output directory for generated certificates.
# Default: "${BASEDIR}/certs"
#CERTDIR="${BASEDIR}/certs"

# Directory for account keys and registration information.
#ACCOUNTDIR="${BASEDIR}/accounts"

# Output directory for challenge-tokens to be served by webserver or deployed in
# HOOK
# Default: "/var/www/dehydrated"
WELLKNOWN="/var/lib/dehydrated"

# Default key size for private keys.
# Default: "4096"
#KEYSIZE="4096"

# Path to OpenSSL configuration file
# Default: <unset> - tries to figure out system default.
OPENSSL_CNF="${BASEDIR}/openssl.cnf"

# Program or function called in certain situations
#
# After generating the challenge-response, or after failed challenge (in this
# case altname is empty). Given arguments:
#	clean_challenge|deploy_challenge
# 	altname
#	token-filename
#	token-content
#
# After successfully signing certificate. Given arguments:
#	deploy_cert
#	domain
# 	path/to/privkey.pem
#	path/to/cert.pem
#	path/to/fullchain.pem
#
# BASEDIR and WELLKNOWN variables are exported and can be used in an external
# program.
# Default: <unset>
HOOK="/usr/local/lib/pdns_api.sh/pdns_api.sh"

# Chain clean_challenge|deploy_challenge arguments together into one hook call
# per certificate
# Default: "no"
HOOK_CHAIN="yes"

# Minimum days before expiration to automatically renew certificate.
# Default: 30
#RENEW_DAYS="30"

# Regenerate private keys instead of just signing new certificates on renewal.
# Default: "yes"
PRIVATE_KEY_RENEW="no"

# Which public key algorithm should be used?
# Supported: rsa, prime256v1 and secp384r1
# Default: "rsa"
#KEY_ALGO="rsa"

# E-mail to use during the registration.
# Default: <unset>
#CONTACT_EMAIL=
CONTACT_EMAIL=webmaster@example.net

# Lock-file location, to prevent concurrent access.
# Default: $BASEDIR/lock
LOCKFILE="/var/run/dehydrated.lock"

# Option to add CSR-flag indicating OCSP stapling to be mandatory.
# Default: "no"
OCSP_MUST_STAPLE="yes"

# PowerDNS API settings for the hook script.
# Default:
HOST=ns0.example.net
# Default:
PORT=8081            # Optional. Defaults to 8081
# Default:
KEY=secret           # API key
# Default:
SERVER=localhost     # Optional. Server for the API to use, usually `localhost`
# Default:
VERSION=1            # Optional. API version, 0 for anything under PowerDNS 4
# Default:
WAIT=150             # Optional. Delay for when slaves are slow

OpenSSL

$ openssl version
OpenSSL 1.0.2g  1 Mar 2016
$ sudo wget --output-document=/etc/dehydrated/openssl.cnf
        https://raw.githubusercontent.com/openssl/openssl/OpenSSL_1_0_2g/apps/openssl.cnf

Domains

/etc/dehydrated/domains.txt

example.net www.example.net cloud.example.net
example.org www.example.org xmpp.example.org
mail.exammple.net mail.example.org
autoconfig.example.net autoconfig.example.org
xmpp.example.net

Increase Serials on API-Requests

$ mysql -u root -p pdns
> INSERT INTO domainmetadata (domain_id, kind, content)
        VALUES (
        (SELECT domains.id FROM domains WHERE domains.name = 'example.net'),
        'SOA-EDIT-API',
        'INCEPTION-INCREMENT'
);

> INSERT INTO domainmetadata (domain_id, kind, content)
        VALUES (
        (SELECT domains.id FROM domains WHERE domains.name = 'example.org'),
        'SOA-EDIT-API',
        'INCEPTION-INCREMENT'
);

Re-use Existing Keys

$ sudo mkdir -p /etc/dehydrated/certs/{example.org,example.net}
$ sudo cp /etc/ssl/private/example.org.key.pem /etc/dehydrated/certs/example.org/privkey.pem
$ sudo cp /etc/ssl/private/example.net.key.pem /etc/dehydrated/certs/example.net/privkey.pem

OCSP Stapling and Mail Servers

Postfix SMTP server and Dovecot IMAP server both don’t do OCSP stapling, or maybe just my mail client Thunderbird doesn’t or other MX don’t. I don’t know exactly, but the fact remains: SMTP and IMAP Connections to the mail server fail with an OpenSSL error message “alert bad certificate: SSL alert number 42”.

Since we configured Dehydrated to request certificates with the x.509 extension “OCSP must staple”, they will not work with connecting clients if the server does not staple. We therefore make an exception in the Dehydrated configuration:

/etc/dehydrated/certs/mail.example.net/config:

;;
;; Mail servers Postfix and Dovecot don't seem to support OCSP stapling (yet?).
;;
;; dovecot:
;;      imap-login: Error: SSL: Stacked error:
;;              error:14094412:SSL
;;                      routines:ssl3_read_bytes:sslv3
;;                              alert bad certificate: SSL alert number 42
;;
;; postfix/submission/smtpd:
;;      warning: TLS library problem: error:14094412:SSL
;;              routines:ssl3_read_bytes:sslv3
;;                      alert bad certificate:s3_pkt.c:1472:SSL alert number 42:
;;
;; Option to add CSR-flag indicating OCSP stapling to be mandatory
;; Default: no
OCSP_MUST_STAPLE="no"

OCSP Response Stapling

TBD …

cd /etc/dehydrated/certs
openssl ocsp \
        -issuer example.net/chain.pem \
        -CAfile example.net/chain.pem \
        -VAfile example.net/chain.pem \
        -cert example.net/cert.pem \
        -url http://ocsp.int-x3.letsencrypt.org/ \
        -header "HOST" "ocsp.int-x3.letsencrypt.org" \
        -no_nonce

Keys and Certificates File Permissions

Private keys must be carefully protected, that’s why in most cases the private key files are owned by the root user and have strict file permissions, to not allow access to anyone else. Most servers start with root privileges to open IP ports lower then 1024 and read their certificate and private keys. They lower their privileges after that and run under their own dedicated user and group.

Some servers (e.g. Prosody) have a different approach. Their user profile is part of the ssl-cert group. The ssl-cert group is given read access to private keys and certificate files. That way such a server never needs to be started or run with root privileges to begin with.

Dehydrated does not currently allow read-access to the ssl-cert group.

chgrp -R ssl-cert/etc/dehydrated/certs/*

Restarting Servers

TBD …

  • Nginx

  • Postfix

  • Dovecot

  • Prosody

Test-Run

Make sure the line CA contains the Letsencrypt staging server:

CA="https://acme-staging.api.letsencrypt.org/directory"

Run the script against the staging server:

$ sudo /usr/local/lib/dehydrated/dehydrated --cron

Publish/Update TSLA Records

Avoid “3 0 1” and “3 0 2” TLSA records with LetsEncrypt certificates

TLSA Record Types

  1. The Certificate Usage Field

Value

Acronym

Short Description

0

PKIX-TA

CA constraint

1

PKIX-EE

Service certificate constraint

2

DANE-TA

Trust anchor assertion

3

DANE-EE

Domain-issued certificate

  1. A commonly trusted top-level CA certificate or public key.

  2. A commonly trusted server certificate or public key.

  3. Certificate or public key of the signing CA. No common trust required.

  4. Domain certificate or public key. No common trust required.

  1. The Selector Field

Value

Acronym

Short Description

0

Cert

Full certificate

1

SPKI

SubjectPublicKeyInfo

  1. The full certificate.

  2. The public key of the certificate.

  1. The Matching Type Field

Value

Acronym

Short Description

0

Full

No hash used

1

SHA2-256

256 bit hash by SHA2

2

SHA2-512

512 bit hash by SHA2

  1. The full certificate or public key.

  2. SHA-256 hash of the certificate or public key

  3. SHA-512 hash of the certificate or public key

Not Recommended:

  • 000 - Un-hashed full trusted CA certificate, too big might change

  • 001 - SHA2-256 hash of trusted CA certificate, might change

  • 002 - SHA2-512 hash of trusted CA certificate, might change

  • 010 - Un-hashed public key of trusted CA certificate, too big.

  • 100 - Un-hashed full trusted server certificate, too big, changes on renewal.

  • 101 - SHA2-256 hashed trusted server certificate, changes on renewal.

  • 102 - SHA2-512 hashed trusted server certificate, changes on renewal.

  • 110 - Un-hashed server public key, too big.

  • 111 - SHA2-256 hashed server public key.

  • 112 - SHA2-512 hashed server public key.

  • 200 - Un-hashed full CA certificate, too big, might change.

  • 201 - SHA2-256 hashed CA certificate, might change.

  • 202 - SHA2-512 hashed CA certificate, might change.

  • 210 - Un-hashed CA public key, too big.

  • 300 - Un-hashed server certificate, too big, changes on renewal.

  • 301 - SHA2-256 hashed server certificate, changes on renewal.

  • 302 - SHA2-512 hashed server certificate, changes on renewal.

  • 310 - Un-hashed server public key, too big.

Possible, but still big:

  • 012 - SHA-512 hashed public key of trusted CA

  • 212 - SHA-512 hashed CA public key.

  • 312 - SHA-512 hashed server public key.

Recommended:

  • 011 - SHA-256 hashed public key of trusted CA

  • 211 - SHA-256 hashed CA public key.

  • 311 - SHA-256 hashed server public key.

Create TLSA Records

Download:

$ sudo wget -O /usr/local/bin/chaingen https://go6lab.si/DANE/chaingen
$ sudo chmod +x /usr/local/bin/chaingen

Run:

$ sudo -s
$ cd /etc/dehydrated/certs
$ chaingen /etc/dehydrated/certs/example.net/fullchain.pem example.org:443

Upgrade

Dehydrated

$ cd /usr/local/lib
$ sudo git fetch --tags
$ git checkout v0.3.1