Mit TLSA kann man für eine Domain im DNS festlegen, welche Zertifizierungsstelle für die Domain Zertifikate ausstellen darf (Eintrag mit 2 1 1) oder welcher Schlüssel (Eintrag mit 3 1 1) für TLS gilt. Auf diesem Weg kann sich ein Nutzer bei einer zweiten Quelle (DNS) über die Echtheit eines angebotenen Zertifikats informieren. Wenn dabei DNSSec genutzt wird, ist die Information aus der zweiten Quelle abgesichert.

Da sich bei Letsencrypt regelmäßig der Schlüssel für TLS ändert, müsste man die Anpassung des Eintrags im DNS automatisieren. Leider habe ich dafür nicht die Möglichkeit und kann im DNS als TLSA-Eintrag nur das Zertifikat von Letsencrypt festlegen.

Durch einen Wechsel der Zwischenzertifizierungsstellen bei Let's Encrypt habe ich gelernt, dass man auch mehrere TLSA-Einträge hinterlegen kann, um genau dem Umstand eines Wechsels Rechnung zu tragen.

Im DNS habe ich zwei verschiedene Einträge erstellt:

Die Hash-Werte lassen sich leicht mit openssl erzeugen:

% sudo sed 1,/-----END/d /etc/letsencrypt/live/jo-so.de/fullchain.pem \
  |openssl x509 -pubkey -noout |openssl pkey -pubin -outform DER |openssl sha256
SHA2-256(stdin)= 2bbad93ab5c79279ec121507f272cbe0c6647a3aae52e22f388afab426b4adba

% sudo sed 1,/-----END/d /etc/letsencrypt/live/jo-so.de-ecdsa/fullchain.pem \
  |openssl x509 -pubkey -noout |openssl pkey -pubin -outform DER |openssl sha256
SHA2-256(stdin)= d016e1fe311948aca64f2de44ce86c9a51ca041df6103bb52a88eb3f761f57d7

Webseite zum Generieren des TLSA-Eintrags

Prüfungen der TLSA-Einträge

Das Ergebnis lässt sich dann mit resolvectl abfragen

% resolvectl tlsa jo-so.de
_443._tcp.jo-so.de IN TLSA 2 1 1 2bbad93ab5c79279ec121507f272cbe0c6647a3aae52e22f388afab426b4adba
        -- Cert. usage: Trust anchor assertion
        -- Selector: SubjectPublicKeyInfo
        -- Matching type: SHA-256 -- link: wlp59s0

_443._tcp.jo-so.de IN TLSA 2 1 1 d016e1fe311948aca64f2de44ce86c9a51ca041df6103bb52a88eb3f761f57d7
        -- Cert. usage: Trust anchor assertion
        -- Selector: SubjectPublicKeyInfo
        -- Matching type: SHA-256 -- link: wlp59s0

-- Information acquired via protocol DNS in 1.2ms.
-- Data is authenticated: yes
% resolvectl tlsa jo-so.de:25
_25._tcp.jo-so.de IN TLSA 2 1 1 2bbad93ab5c79279ec121507f272cbe0c6647a3aae52e22f388afab426b4adba
        -- Cert. usage: Trust anchor assertion
        -- Selector: SubjectPublicKeyInfo
        -- Matching type: SHA-256 -- link: wlp59s0

oder mit openssl prüfen:

% openssl s_client -connect jo-so.de:443 -dane_tlsa_domain jo-so.de \
  -dane_tlsa_rrdata '2 1 1 d016e1fe311948aca64f2de44ce86c9a51ca041df6103bb52a88eb3f761f57d7' \
  </dev/null |grep DANE
DANE TLSA 2 1 1 ...f6103bb52a88eb3f761f57d7 matched the TA certificate at depth 1

Fehlerfall:

% openssl s_client -connect jo-so.de:25 -starttls smtp \
  -dane_tlsa_domain jo-so.de -dane_tlsa_rrdata \
  '2 1 1 d016e1fe311948aca64f2de44ce86c9a51ca041df6103bb52a88eb3f761f57d7' \
  </dev/null |grep DANE
verify error:num=65:no matching DANE TLSA records

Ein Skript zur Automatisierung:

for h in {,{sub1,sub2,…}.}jo-so.de:443 jo-so.de:{25,…}
do
    # keys=( ${${(M)${(f)"$(host -t TLSA _${h#*:}._tcp.${h%:*} root-dns.netcup.net)"}:#_*}#*TLSA record } )
    keys=( ${${(M)${(f)"$(resolvectl tlsa $h)"}:#_*}#*TLSA } )

    printf '%-20s' $h:
    if (( ! $#keys ))
    then
        echo 'TLSA entry missing'
        continue
    fi

    starttls=
    <span class="createlink"> &#36;h &#61;&#61; &#42;:25 </span> && starttls=(-starttls smtp)
    if ! openssl s_client -connect $h $starttls -dane_tlsa_domain ${h%:*} \
        -dane_tlsa_rrdata=$^keys </dev/null 2>&1 |grep DANE
    then
        echo 'connection failed'
    fi
done

Siehe auch: Certbot mit Nginx, Netzwerkverwaltung mit Systemd