Always current TLSA records for Let's Encrypt & Buypass Go

About TLSA.is

TLSA.is provides a managed alternative to generating and publishing own TLSA records, which are required for DANE. TLSA.is creates, publishes and keeps current DANE-TA TLSA resource records for a number of supported Certificate Authorities (Let's Encrypt and Buypass).

Generation of the TLSA records has been integrated into the project owner's own DNS management tool navn and takes place at least weekly, just before the periodic refresh of DNSSEC signatures. The TLSA generation setup is documented below.

WTF?

The TLSA DNS resource record (RR), specified in RFC 6698, is used to associate a TLS server certificate or public key with the domain name where the record is found, thus forming a "TLSA certificate association".

Supported Certificate Authorities

Let's Encrypt

Let's Encrypt is a non-profit certificate authority run by Internet Security Research Group (ISRG).

TLSA.is publishes TLSA records for the intermediate certificates published by Let's Encrypt.

In order to use the TLSA resource record, a CNAME or a DNAME record pointing to _letsencrypt.tlsa.is should be published as needed, e.g.:

; Using CNAME for a single service
_25._tcp.mail		IN	CNAME	_letsencrypt.tlsa.is.

; Using DNAME for all TCP services
_tcp.mail6		IN	DNAME	_letsencrypt.tlsa.is.

Buypass

The Norwegian Certificate Authority Buypass provides Buypass Go as an alternative to Let's Encrypt.

TLSA.is publishes TLSA record for the issuing certificate published by Buypass.

In order to use the TLSA resource record, a CNAME or a DNAME record pointing to _buypass-go.tlsa.is should be published as needed, e.g.:

; Using CNAME for a single service
_25._tcp.mail		IN	CNAME	_buypass-go.tlsa.is.

; Using DNAME for all TCP services
_tcp.mail6		IN	DNAME	_buypass-go.tlsa.is.

DIY

Summary

The generation of the TLSA records is done by a Python script (Python 3.8 is required due to use of the walrus operator) which simply prints the resource records to stdout and which are then included in a zone file (e.g. by using the $INCLUDE statement).

Configuration

The script uses a configuration file, which specifies what the resource records shall be called, where to fetch the certificates etc.

Required modules

The following Python modules are required:

Python script (download)

#!/usr/bin/env python3

import sys
import time
import hashlib
from pathlib import Path

import yaml
import requests
from yarl import URL

from cryptography import x509
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives.serialization import Encoding, PublicFormat

from pprint import pprint

###

basedir = Path(sys.argv[0]).resolve().parent
certdir = basedir.joinpath('tlsa.certs')

if not certdir.exists():
    certdir.mkdir(0o750)

curtime = time.time()
certlist = yaml.safe_load(basedir.joinpath('tlsa.yaml').open())
for (tag, crt) in certlist.items():
    if 'issuer' in crt and 'docs' in crt:
        print(f"; {crt['issuer']} ({crt['docs']})")
    elif 'issuer' in crt:
        print(f"; {crt['issuer']}")
    else:
        print(f"; {tag}")
    printtag = True
    if 'certs' in crt:
        for u in crt['certs']:
            url = URL(u)
            urlpath = Path(url.path)
            certfile = certdir.joinpath(urlpath.name)
            if not certfile.exists() or \
                    (refresh := crt.get('refresh')) and certfile.stat().st_mtime < curtime-refresh:
                res = requests.get(url)
                res.raise_for_status()
                certpem = res.content
                certfile.write_bytes(certpem)
            else:
                certpem = certfile.read_bytes()
            cert = x509.load_pem_x509_certificate(certpem, default_backend())
            key = cert.public_key()
            keybytes = key.public_bytes(Encoding.DER, PublicFormat.SubjectPublicKeyInfo)
            tlsadigest = hashlib.sha256(keybytes).hexdigest()
            record = f'TLSA\t2 1 1\t{tlsadigest}'
            print('{}\t{}'.format('_' + tag if printtag else '\t', record))
            if 'records' not in crt:
                crt['records'] = []
            crt['records'].append(record)
            printtag = False
    if 'include' in crt:
        for i in crt['include']:
            if i not in certlist or not certlist[i]['records']:
                continue
            for r in certlist[i]['records']:
                print('{}\t{}'.format('_' + tag if printtag else '\t', r))
                printtag = False
            if 'records' not in crt:
                crt['records'] = []
            crt['records'].append(r)
    if 'records' in crt and crt['records']:
        print(f'*._{tag}\tCNAME\t_{tag}')
    print()

print(f'_timestamp\tTXT\t"{int(curtime)}"')

YAML configuration (download)

---
letsencrypt:
  issuer:   Let's Encrypt
  docs:     https://letsencrypt.org/certificates/
  refresh:  8640000
  certs:
    - https://letsencrypt.org/certs/letsencryptauthorityx3.pem
    - https://letsencrypt.org/certs/lets-encrypt-r3.pem
    - https://letsencrypt.org/certs/lets-encrypt-e1.pem
    - https://letsencrypt.org/certs/letsencryptauthorityx4.pem
    - https://letsencrypt.org/certs/lets-encrypt-r4.pem
    - https://letsencrypt.org/certs/lets-encrypt-e2.pem

buypass-go:
  issuer:   Buypass
  docs:     https://www.buypass.com/security/buypass-root-certificates
  refresh:  8640000
  certs:
    - https://crt.buypass.no/crt/BPClass2CA5.pem

combinded:
  issuer:   Let's Encrypt + Buypass
  include:
    - letsencrypt
    - buypass-go

Result

; Let's Encrypt (https://letsencrypt.org/certificates/)
_letsencrypt	TLSA	2 1 1	60b87575447dcba2a36b7d11ac09fb24a9db406fee12d2cc90180517616e8a18
		TLSA	2 1 1	8d02536c887482bc34ff54e41d2ba659bf85b341a0a20afadb5813dcfbcf286d
		TLSA	2 1 1	276fe8a8c4ec7611565bf9fce6dcace9be320c1b5bea27596b2204071ed04f10
		TLSA	2 1 1	b111dd8a1c2091a89bd4fd60c57f0716cce50feeff8137cdbee0326e02cf362b
		TLSA	2 1 1	e5545e211347241891c554a03934cde9b749664a59d26d615fe58f77990f2d03
		TLSA	2 1 1	bd936e72b212ef6f773102c6b77d38f94297322efc25396bc3279422e0c89270
*._letsencrypt	CNAME	_letsencrypt

; Buypass (https://www.buypass.com/security/buypass-root-certificates)
_buypass-go	TLSA	2 1 1	42519999c31433a6bcf82c4bd9399301fa180a6f9f5c0a2e033cca602c46a2cb
*._buypass-go	CNAME	_buypass-go

; Let's Encrypt + Buypass
_combinded	TLSA	2 1 1	60b87575447dcba2a36b7d11ac09fb24a9db406fee12d2cc90180517616e8a18
		TLSA	2 1 1	8d02536c887482bc34ff54e41d2ba659bf85b341a0a20afadb5813dcfbcf286d
		TLSA	2 1 1	276fe8a8c4ec7611565bf9fce6dcace9be320c1b5bea27596b2204071ed04f10
		TLSA	2 1 1	b111dd8a1c2091a89bd4fd60c57f0716cce50feeff8137cdbee0326e02cf362b
		TLSA	2 1 1	e5545e211347241891c554a03934cde9b749664a59d26d615fe58f77990f2d03
		TLSA	2 1 1	bd936e72b212ef6f773102c6b77d38f94297322efc25396bc3279422e0c89270
		TLSA	2 1 1	42519999c31433a6bcf82c4bd9399301fa180a6f9f5c0a2e033cca602c46a2cb
*._combinded	CNAME	_combinded

_timestamp	TXT	"1601362801"

Big Red Warning

TLSA.is solves the project owner's personal requirement. It may, however, stop working at any time – use at own risk.

Contact

Please get in touch if you have discovered an error, if some TLSA records for the supported authorities should be added, deleted or updated, or if you have any other comments or suggestions.


– Created and operated by Kirill Miazine