Making a DANE TLSA to work with LE

[Moderator’s note November 2019: I’ve updated the references here to refer to the Let’s Encrypt Authority X3 intermediate, which is currently in use. Please see further discussion below about coping with changing intermediates.]

so you want to have DANE with LE (let’s not discuss the politics thereof).

the ISRG Root X1 from is not what you want to use for the TLSA. you want the Let’s Encrypt Authority X3 (IdenTrust cross-signed), and this recipe is for the .pem.

stash the lets-encrypt-x3-cross-signed.pem and whack it into a 2 0 1 TLSA

openssl x509 -in lets-encrypt-x3-cross-signed.pem.txt -outform DER | openssl dgst -sha256 -hex | awk '{print "le-ca TLSA 2 0 1", $NF}'


le-ca TLSA 2 0 1 25847d668eb4f04fdd40b12b6b0740c567da7d024308eb6c2c96fe41d9de218d

[ you can actually just steal that TLSA value ]

i put this TLSA at the top of my fave base zone file, RG.NET in my case, so essentially it is a TLSA for

then, in the RRset for a server for which i have the LE cert, i can hack in, for example     A
_443._tcp    CNAME

forcing 443/tcp is because the LE cert is

X509v3 Extended Key Usage: 
    TLS Web Server Authentication, TLS Web Client Authentication

that’s it.

Thanks to Rob Austein and Shumon Huque for clues. DNSsec and DANE are just sooooo intuitive.


@randy, thanks for sharing this tip!

Thanks @randy , you made my day !

The TLSA entry is valid but i was never a fan of CA pinning only. I always use the full certificate for my TLSA entries.
Time to create a little API for my DNS servers to update the TLSA entries.

@rip: i understand; fair position. the TLSA-TA hack scales more easily, and i have way too many services which use the same roots. call me lazy.

See also Please avoid “3 0 1” and “3 0 2” DANE TLSA records with LE certificates and why you might be better off with a “2 1 1” record, though “3 1 1” also works, provided the key is kept stable.

1 Like

so the hack might be, for example

openssl x509 -in isrgrootx1.pem -noout -pubkey | openssl rsa -pubin -outform DER | openssl dgst -sha256 -hex | awk '{print "le-ca TLSA 2 1 1", $NF}'


le-ca TLSA 2 1 1 0b9fa5a59eed715c26c1020c711b4f6ec42d58b0015e14337a39dad301c5afc3

1 Like

The DANE-TA(2) SPKI(1) SHA2-256(1) (aka “2 1 1”) digest that everyone else publishes for the current LE issuer CA that is the immediate (depth 1) signer of LE leaf certificates is: IN TLSA 2 1 1 60b87575447dcba2a36b7d11ac09fb24a9db406fee12d2cc90180517616e8a18

Not sure which cacert you’re taking the digest of, perhaps it is the depth 2 issuer?

FWIW, the commands look correct, you just need to make sure that the right issuer certificate is at the top of the .pem file in question.

I use the bash script below to generate all possible DANE-EE(3) or DANE-TA(2) TLSA records for a chain file with multiple certificates (leaf followed by issuers)

#! /usr/bin/env bash
# Bash needed for PIPESTATUS array

extract() {
  case "$4" in
  0) openssl x509 -in "$1" -outform DER;;
  1) openssl x509 -in "$1" -noout -pubkey | openssl pkey -pubin -outform DER;;
digest() {
  case "$5" in
  0) cat;;
  1) openssl dgst -sha256 -binary;;
  2) openssl dgst -sha512 -binary;;
encode() {
  local cert=$1; shift
  local hostport=$1; shift
  local u=$1; shift
  local s=$1; shift
  local m=$1; shift
  local host=$hostport
  local port=25

  OIFS="$IFS"; IFS=":"; set -- $hostport; IFS="$OIFS"
  if [ $# -eq 2 ]; then host=$1; port=$2; fi

  printf "_%d._tcp.%s. IN TLSA %d %d %d %s\n" \
    "$port" "$host" "$u" "$s" "$m" \
     "$(hexdump -ve '/1 "%02X"')"

genrr() {
	extract "$@" | digest "$@" | encode "$@"
	exit $(( ${PIPESTATUS[0]} | ${PIPESTATUS[1]} | ${PIPESTATUS[2]} ))
    status=$?; if [ $status -ne 0 ]; then exit $status; fi
    echo "$rr"

error() { echo "$1" 1>&2; exit 1; }
usage() { error "Usage: $0 chain.pem host[:port]"; }
if [ $# -ne 2 ]; then usage; fi

# Validate and normalize the chain
certfile=$1; shift
    openssl crl2pkcs7 -nocrl -certfile "$certfile" |
	openssl pkcs7 -print_certs
    exit $(( ${PIPESTATUS[0]} | ${PIPESTATUS[1]} ))
status=$?; if [ $status -ne 0 ]; then exit $status; fi

hostport=$1; shift
printf "%s\n\n" "$chain" |
while read line
    if [[ -z "$cert" && ! "$line" =~ ^-----BEGIN ]]; then
    cert=$(printf "%s\n%s" "$cert" "$line")
    if [ -z "$line" -a ! -z "$cert" ]; then
	echo "$cert" |
	    openssl x509 -noout -subject -issuer -dates |
	    sed -e 's/^/;; /'
	echo ";;"
	genrr <(echo "$cert") "$hostport" $usage 0 1
	genrr <(echo "$cert") "$hostport" $usage 1 1
	genrr <(echo "$cert") "$hostport" $usage 0 2
	genrr <(echo "$cert") "$hostport" $usage 1 2

Note that the intermediate cert has changed—it’s no longer the “X1” cert, but the “X3” cert by default. There is also a “X4” cert, which according to Let’s Encrypt’s certificates page will be used in case of emergency.

I had noticed that my DANE validations were broken because I was still using the old (X1) value, but my certs from a few weeks ago already have the X3 one in their chain. The value I’m using now is:

le-ca			TLSA	2 0 1 25847d668eb4f04fdd40b12b6b0740c567da7d024308eb6c2c96fe41d9de218d

This has fixed it for me!

yes, they moved on to x3

TLSA 2 1 1 60b87575447dcba2a36b7d11ac09fb24a9db406fee12d2cc90180517616e8a18

Note, the reason I recommended “2 1 1” TLSA records in this case, is precisely because the certificate is likely to change more often than the public key. Its lifetime may get extended, or a new parent issuer may be used, all the while retaining the same underlying private/public key pair.

In particular the same:

TLSA 2 1 1 60b87575447dcba2a36b7d11ac09fb24a9db406fee12d2cc90180517616e8a18

record matches both the X1 and the X3 certificate. Even better is publishing both “3 1 1” and “2 1 1” and monitoring both, because this simplifies the logistics of key rotation, and improves “safety”. See:

Hi, after the Desaster of TLS-SNI i would like to bring this topic up again.
Because it have the charme that you do not need to change the dns record
based on an challenge. You prove that you posses the domain by asking the
certificate with an key validaded via the TLSA record.