Mettre en place DANE TLSA

Publié le 22/12/2023, dans informatique, infrastructure, système, dns, tls

J'avais déjà des enregistrements TLSA en place, mais ils étaient tous DANE-TA, j'ai donc décidé de faire ça un peu plus sérieusement en passant à DANE-EE. Pas de panique si cette phrase ne vous dit rien, je vais tout expliquer dans la suite de cet article.

DANE TLSA, c'est quoi ?

DANE (DNS based Authentication of Named Entities) est un protocole qui s'appuie sur le DNS pour authentifier des certificats X.509. Il est standardisé par les RFC 6698, 7671, 7672 et 7673.

Pour faire simple, le but est de stocker le hash de notre certificat TLS dans un enregistrement DNS. Comme notre client à confiance dans nos enregistrements DNS cryptographiquement signés, alors il peut comparer le hash fourni dans le DNS avec celui du certificat qui lui est présenté (dans un navigateur web par exemple). C'est un moyen de réduire les risques d'attaques man-in-the-middle, qui pourraient par exemple générer un certificat TLS valide avec une autre Autorité de Certification que la nôtre.

La définition est donc assez simple, mais il existe plusieurs cas d'usage.

Les possibilités

Il est possible de configurer 3 champs TLSA qui servent à définir la donnée qui se trouve dans le 4eme champ :

  • Le champ usage
  • Le champ selector
  • Le champ matching-type

Le champ usage peut prendre les valeurs 0, 1, 2 ou 3, les 2 premiers se basent sur X.509, tandis que les 2 derniers non :

  • 0 (PKIX-TA) permet comme son nom l'indique d'ajouter une contrainte sur la chaîne d'Autorité de Certification en indiquant quel certificat de l'AC doit se trouver dans la chaîne. C'est un cas qui permet d'éviter les attaques passant par d'autres Autorités de Certification pour générer un certificat ennemi, comme avec les enregistrements CAA.
  • 1 (PKIX-EE) est identique au précédent mais place cette fois-ci la contrainte sur le certificat final plutôt que sur celui de l'AC. Ainsi on se protége de notre propre Autorité de Certification.
  • 2 (DANE-TA) permet comme PKIX-TA de mettre une contrainte sur un certificat d'AC ayant signé notre certificat final mais sans passer par X.509. On peut donc utiliser une AC auto-signé.
  • 3 (DANE-EE) permet comme PKIX-EE de mettre la contrainte sur le certificat final mais sans passer par X.509. Ici on ne fait confiance qu'à notre propre certificat. On peut donc utiliser un certificat auto-signé par exemple.

Le champ selector peut prendre les valeurs 0 ou 1 :

  • 0 (Cert) indique que la donnée stockée est un certificat complet.
  • 1 (SPKI) indique que la donnée stockée est la clé publique du certificat.

Le champ matching-type peut prendre les valeurs 0,1 ou 2 :

  • 0 (Full) indique que la donnée stockée n'est pas hashée.
  • 1 (SHA-256) indique que la donnée est hashée via SHA-256.
  • 2 (SHA-512) indique que la donnée est hashée via SHA-512.

Structure d'un enregistrement

Du coup un enregistrement TLSA peut ressembler à ceci :

_443._tcp.toutetrien.lithio.fr. IN TLSA 3 1 1 5847c0d6b2993bdd9fef2a824e5cff03df116b6b13fe123d7947854813f25c0f

Et oui, on doit aussi indiquer via notre nom de domaine quels sont le port et le protocole auquel s'applique l'enregistrement.

Ainsi cet exemple (celui trouvable dans ma zone DNS à l'instant où j'écris ces lignes) sert à valider, de droite à gauche : le hash (5847c0d6b2993bdd9fef2a824e5cff03df116b6b13fe123d7947854813f25c0f) SHA-256 (1 : SHA-256) de la clé publique (1 : SPKI) du certificat (3 : DANE-EE) qui sera présenté sur le port 443 en TCP pour le nom de domaine toutetrien.lithio.fr.

Mon cas jusqu'en 2023

Jusqu'à présent je faisais du DANE-TA, donc je validai au niveau des certificats de ma CA, Let's Encrypt.

Pour cela j'avais créé les enregistrements suivants qui contiennent les hash des clés publiques des certificats Let's Encrypt :

letsencrypt._tlsa IN TLSA 2 1 1 60b87575447dcba2a36b7d11ac09fb24a9db406fee12d2cc90180517616e8a18 ; LE X3
letsencrypt._tlsa IN TLSA 2 1 1 276fe8a8c4ec7611565bf9fce6dcace9be320c1b5bea27596b2204071ed04f10 ; LE E1
letsencrypt._tlsa IN TLSA 2 1 1 bd936e72b212ef6f773102c6b77d38f94297322efc25396bc3279422e0c89270 ; LE E2
letsencrypt._tlsa IN TLSA 2 1 1 8d02536c887482bc34ff54e41d2ba659bf85b341a0a20afadb5813dcfbcf286d ; LE R3
letsencrypt._tlsa IN TLSA 2 1 1 e5545e211347241891c554a03934cde9b749664a59d26d615fe58f77990f2d03 ; LE R4

et j'utilisai des CNAME pour faire pointer les enregistrements TLSA dessus :

_443._tcp.toutetrien.lithio.fr IN CNAME letsencrypt._tlsa.lithio.fr.

Mon cas depuis décembre 2023

J'ai décidé hier de passer sur du DANE-EE, donc de valider directement au niveau de mes certificats.

Mais avec plus de 20 certificats sur mes serveurs, faire ça à la main serait un peu trop long. J'ai donc écrit un petit script pour générer les enregistrements TLSA.

Avant ça il m'a fallu configurer Certbot.

Configurer Certbot

Par défaut lorsque que l'on renouvel un certificat Let's Encrypt (qui ont une durée de vie de 90 jours) c'est un renouvellement complet qui a lieu : une nouvelle clé privée (et donc aussi la clé publique qui en est dérivée) est créé. Le problème est donc que l'on devrait mettre à jours nos enregistrements TLSA à chaque renouvellement de certificat.

Pour éviter ça, on va indiquer à Certbot de réutiliser la même clé privée pour soumettre une demande de signature plutôt que d'en recréer une nouvelle.

Ça se fait via les arguments --keep indiquant de garder le certificat jusqu'à expiration et --reuse-key indiquant de conserver la même clé privée.

Personnellement j'ai mis ces arguments à la suite de ma commande certbot renew dans le script qui renouvel mes certificats.

Créer les enregistrements TLSA

Pour créer des enregistrements TLSA on peut bien sur utiliser des logiciels dédiés comme hash-slinger avec sa commande tlsa. Mais comme je n'aime pas installer plus de dépendances que nécessaire sur mes serveurs, je passe directement par OpenSSL via :

openssl x509 -noout -pubkey -in "/etc/letsencrypt/live/example.com/cert.pem" | openssl pkey -pubin -outform DER | sha256sum | awk '{print $1}'

Cette commande me donne le hash SHA256 à placer dans mon enregistrement TLSA.

Comme mon but est d'automatiser tout ça, j'ai fait un petit fichier de configuration de la forme suivante :

lithio.fr               _443._tcp _5222._tcp _5223._tcp _5269._tcp _5270._tcp
toutetrien.lithio.fr    _443._tcp

Chaque ligne contient le nom du certificat suivi par les combinaisons de port/protocole sur lesquels il est exposé.

Et le script Bash tlsa.sh suivant me génére mes enregistrements TLSA :

#!/bin/bash
#
# Configuration file: <domain-name>.conf
# Usage: ./tlsa.sh <domain-name>

CERTBOT_LIVE_PATH="/etc/letsencrypt/live"

while IFS= read -r configuration
do
    common_name=$(echo "${configuration}" | awk '{print $1}')
    IFS=' ' read -r -a port_proto_configurations <<< $(echo "${configuration}" | awk '{$1=""; print $0}')
    for port_proto in "${port_proto_configurations[@]}"
    do
        pubkey_hash=$(openssl x509 -noout -pubkey -in "${CERTBOT_LIVE_PATH}/${common_name}/cert.pem" | openssl pkey -pubin -outform DER | sha256sum | awk '{print $1}')
        record=$(echo -e "${port_proto}.${common_name}.\t\tIN\tTLSA\t\t3 1 1 ${pubkey_hash}" | sed "s/\.${1}\.//g")
        echo -e "${record}"
    done
done < ./${1}.conf

À noter que ce script génére des labels relatifs plutôt que des FQDN.

Maintenant on peut publier nos enregistrements TLSA dans notre zone DNS.

Vérifier nos enregistrements TLSA

Pour vérifier que tout fonctionne bien, on peut utiliser un logiciel comme hash-slinger via la commande :

tlsa --verify --port 443 --protocol tcp toutetrien.lithio.fr

Qui devrait nous répondre ainsi si tout va bien :

SUCCESS (Usage 3 [DANE-EE]): Certificate offered by the server matches the TLSA record (82.65.255.252)
SUCCESS (Usage 3 [DANE-EE]): Certificate offered by the server matches the TLSA record (2a01:e0a:21a:be00::6)

Ou bien passer par un service en ligne comme DaneCheck.

Et voilà

On a à présent des enregistrements TLSA valides pour tout ce qui sert un certificat TLS sur nos serveurs.

Il peut être bien d'ajouter ça à notre système de surveillance automatisé.


Vous pouvez commenter en envoyant un mail via ce bouton (votre adresse ne sera pas publié).

Commenter par mail