DoT, DoH : le chiffrement du DNS en pratique
Les mains dans le cambouis
En complément de ma causerie à Pas Sage En Seine ce 28 juin (et dont les diapos sont ici, je vous conseille d'y jeter un œil avant de lire texte – ou bien lire les RFC 7858, 8310 et 8484 🙂), un billet donc pour parler de ce que je n'ai pas pu placer dans une conférence d'une heure : l'utilisation d'un résolveur DNS sur TLS (DoT) dans la pratique, principalement en tant que client mais aussi quelques billes pour administrer un résolveur complet. J'en profiterai pour causer un peu de DNS sur HTTPS (DoH). Comme d'habitude, les configurations que je donne sont pour des systèmes Debian et dérivés. Les chemins peuvent donc changer si vous êtes sous Arch, une *BSD ou que-sais-je 🙂. Une bonne partie des bouts de configuration présentés ici viennent de ma présentation chez Root66 en avril dernier.
Utiliser un client DoT
Quelques généralités avant de rentrer dans le vif du sujet. Sous Linux, depuis sa version 239, systemd
incorpore un résolveur minimum capable d'utiliser DoT, mais nous n'en parlerons pas car ce n'est pas son boulot et par ailleurs, il n'est à ma connaissance pas capable d'utiliser des profils d'authentification stricts
Avant de commencer, il faut bien évidemment choisir un résolveur DoT, récupérer son ou ses adresses IPs, son nom de domaine ainsi que sa clé publique (SPKI ou Subject Public Key Info). Pour vérifier si le SPKI fourni est correct, on peut utiliser gnutls
en se connectant directement au serveur (à noter que faire la même chose avec OpenSSL demandera l'absorption d'aspirine à la vue de la commande).
# gnutls-cli --dane --verify-hostname dot.example.org 2001:db8:7858::853 -p 853 Processed 130 CA certificate(s). Resolving '2001:db8:7858::853'... Connecting to '2001:db8:7858::853'... - Certificate type: X.509 - Got a certificate list of 2 certificates. - Certificate[0] info: - subject 'CN=dot.example.org',... Public Key ID:sha1:52a55c564dfd4ee38a6a51ad6962fc0a6dcc7b4asha256:f504504952e7fd8edbd444dd102ad707c74ff8c6af9a0f7f66fc85e237d2b7fc Public Key PIN:pin-sha256:9QRQSVLn/Y7b1ETdECrXB8dP+Mavmg9/ZvyF4jfSt/w= ... - Status: The certificate is trusted. - DANE: Certificate matches. - Description: (TLS1.3)-(ECDHE-SECP256R1)-(RSA-PSS-RSAE-SHA256)-(AES-256-GCM)
Le SPKI correspond au Public Key PIN
. L'option --dane
n'est pas obligatoire et s'il n'y a pas d'enregistrement TLSA
pour le service, gnutls
retournera un message d'erreur. Pour vérifier l'existence d'un enregistrement TLSA
, on utilise :
# dig TLSA _853._tcp.dot.example.org
Stubby
Après avoir été le chien le plus décoré de la Première Guerre Mondiale, Stubby est devenu un résolveur minimum pensé pour le chiffrement et basé sur getdns
. Il est disponible dans le gestionnaire de paquets de la plupart des distributions Linux (à partir de Buster uniquement concernant Debian) et fonctionne parfaitement avec sa configuration de base. Cette dernière contient toute une liste de résolveurs DoT déjà configurés (il s'agit de ceux listés sur le site du projet DNS Privacy). DoT étant avant tout une question de confiance dans l'administrateur·trice à qui l'on confie sa résolution DNS, ne pas les utiliser est parfaitement légitime.
Sous Debian, la configuration se fait dans /etc/stubby/stubby.yml
. Pensez à commenter tous les résolveurs pré-configurés et si le résolveur que vous utilisez est accessible via IPv4 et IPv6, une entrée par protocole (ou gardez juste l'entrée IPv6). La configuration initiale de Stubby étant déjà optimale concernant les techniques de protection de vie privée, il n'y a qu'à donner les informations du résolveur :
upstream_recursive_servers:
- address_data: 2001:db8:7858::853
tls_auth_name: "dot.example.org"
tls_pubkey_pinset:
- digest: "sha256"
value: 9QRQSVLn/Y7b1ETdECrXB8dP+Mavmg9/ZvyF4jfSt/w=
Et voilà, vous avez un résolveur minimum qui fait suivre les requêtes DNS à un résolveur complet via TLS. Nous verrons plus bas comment coupler Stubby à Unbound afin de profiter des avantages de ce dernier, à commencer par un cache, ce qui diminuera le nombre de requêtes et améliorera les performances.
Knot Resolver
Knot Resolver est un résolveur complet avec cache. Il a la particularité de se configurer en utilisant du LUA, ce qui le rends particulièrement puissant, mais aussi beaucoup plus complexe que Stubby ou Unbound. Sa documentation est également assez touffue. Son intégration à systemd
est par ailleurs particulière (on y reviendra sur la partie Configurer un serveur DoT
). Pour activer le service qui va bien, il faut utiliser :
systemctl enable --now kresd@1.service
Ensuite, la configuration se fait dans /etc/knot-resolver/kresd.conf
. Attention, tout comme Stubby, il faut une entrée par IP.
policy.add(policy.all(
policy.TLS_FORWARD({
{'192.0.2.13', pin_sha256='9QRQSVLn/Y7b1ETdECrXB8dP+Mavmg9/ZvyF4jfSt/w='},
{'2001:db8:7858::853', hostname='dot.example.org', ca_file='/etc/ssl/certs/ca-certificates.crt'}
})
))
Quelques remarques. Cet exemple donne la configuration pour un même serveur accessible en IPv4 et en IPv6. Contrairement à Stubby qui est capable de faire une double vérification ADN + IP et SPKI + IP, ce n'est pas le cas ici. Si l'on veut utiliser une authentification via ADN, il faut donner le chemin du magasin d'autorités de certification (c'est ici celui de Debian).
Unbound
Avec la version actuelle (1.9.2, en juin 2019) et l'incapacité d'Unbound de garder une connexion TCP ouverte quand il est utilisé en tant que forwarder, je déconseille fortement l'utilisation d'Unbound en tant que client DoT. En attendant que ses développeurs se mettent à la page du RFC 8490, la configuration est donnée à titre indicatif. Unbound ne permet actuellement qu'une authentification ADN + IP.
Sous Debian, créer un fichier .conf
dans /etc/unbound/unbound.conf.d/
server:
chroot: ""
auto-trust-anchor-file: "/var/lib/unbound/root.key"
# Les 2 lignes précédentes sont inutiles sous Debian
do-not-query-localhost: no
tls-cert-bundle: /etc/ssl/certs/ca-certificates.crt
forward:
name: "."
forward-addr: 2001:db8:7858::853@853#dot.example.org
forward-tls-upstream: yes
À noter une petite subtilité dans la verson pour Windows, utiliser le magasin d'autorités de certification du système se fait via :
tls-win-cert: yes
Unbound + Stubby
Je rappelle le principe : sur la machine locale, Unbound est le résolveur utilisé par le système mais transmet tout à Stubby qui va gérer la partie TLS et discuter avec le serveur DoT.

L'avantage est de profiter de la qualité de Stubby pour ce qui concerne DoT, et de celle d'Unbound pour le reste (cache, possibilité de faire facilement un bloqueur de publicités/traqueurs...). On commence donc par configurer Unbound pour tout envoyer à Stubby :
server:
chroot: ""
auto-trust-anchor-file: "/var/lib/unbound/root.key"
# Les 2 lignes précédentes sont inutiles sous Debian
do-not-query-localhost: no
forward-zone:
name: "."
forward-addr: 127.0.0.1@8053
forward-addr: ::1@8053
Et on configure Stubby :
...
listen_addresses:
- 127.0.0.1@8053
- 0::1@8053
upstream_recursive_servers:
- address_data: 2001:db8:7858::853
tls_auth_name: "dot.example.org"
tls_pubkey_pinset:
- digest: "sha256"
value: 9QRQSVLn/Y7b1ETdECrXB8dP+Mavmg9/ZvyF4jfSt/w=
Configurer un serveur DoT
Voici quelques éléments de configuration de serveurs DoT. 2 principaux logiciels ici : Unbound et Knot Resolver. BIND (dans sa branche 9.x tout du moins) ne gère pas DoT et nécessite de passer par des techniques détournées type stunnel
(ce que fait LDN avec son service) ou HAProxy. On va donc l'éviter.
Avant de se lancer, il y a quelques éléments à avoir en sa possession : un certificat X.509 et le SPKI de ce dernier. Je vous laisse voir pour récupérer un certificat (je vous laisse choisir votre autorité de certification, ceci dit attention avec Let's Encrypt : il est préférable de ne pas changer de la clé à chaque renouvellement du certificat, avec certbot
par exemple). Pour calculer le SPKI, on peut prendre la clé privée et la passer dans cette moulinette OpenSSL :
# openssl rsa -in /path/to/private.key -outform der -pubout | \ openssl dgst -sha256 -binary | \ openssl enc -base64
Attention à changer le rsa
en ec
si vous utilisez une clé ECDSA.
Unbound
La configuration de base d'Unbound pour le transformer en serveur DoT est relativement simple : on lui donne une clé privée et un certificat, on lui dit d'écouter sur le bon port et c'est tout.
server:
chroot : ""
auto-trust-anchor-file: "/var/lib/unbound/root.key"
# Les 2 lignes précédentes sont inutiles sous Debian
interface: 127.0.0.1 # Pour que le système ait un résolveur
interface: ::1 # Pour que le système ait un résolveur
interface: 192.0.2.53@853
interface: 2001:db8:dead:cafe::853@853
udp-upstream-without-downstream: yes
tls-service-key: /path/to/private.key
tls-service-pem: /path/to/cert.pem
remote-control:
# Écoute sur localhost:8953 par défaut
control-enable: yes
control-use-cert: no
remote-control
permet d'activer unbound-control
, l'utilitaire d'administration du serveur (capable de fonctionner à distance, mais nous laissons ici les paramètres de base ne fonctionnant que sur le localhost). udp-upstream-without-downstream
n'est pas vraiment utile ici, il permet quand Unbound est configuré pour ne pas être accessible via UDP (ce qui devrait être le cas en utilisant TLS) de pouvoir tout de même utiliser UDP pour la résolution. De ce que j'ai pu constater, désactiver UDP et activer cette option ne pose pas de problèmes pour la résolution des requêtes des clients du serveur, sauf le localhost, pour lequel cela pose des soucis.
Attention, à partir de Debian Buster, AppArmor est installé et actif sur le système et Unbound vient avec un profil. Il faut éditer /etc/apparmor.d/local/usr.sbin.unbound
et ajouter :
/path/to/private.key r,
/path/to/cert.pem r
afin qu'Unbound puisse accèder à ces fichiers.
Une configuration plus avancée, pour un résolveur faisant face à un trafic plus conséquent, pourrait être :
server:
verbosity: 1
use-syslog: no
logfile: "/var/log/unbound.log"
log-time-ascii: yes
num-threads: 4 # On crée un thread par cœur
chroot : ""
auto-trust-anchor-file: "/var/lib/unbound/root.key"
# Les 2 lignes précédentes sont inutiles sous Debian
interface: 127.0.0.1 # Pour que le système ait un résolveur
interface: ::1 # Pour que le système ait un résolveur
interface: 192.0.2.53@853
interface: 2001:db8:dead:cafe::853@853
# Taile du cache. Estimation raisonnable pour un système avec 4 Gio de RAM
rrset-cache-size: 256m
msg-cache-size: 128m
# Limite de requêtes par secondes par IPs
# Si atteint, ne jette pas tout mais limite le client à 1/10 de ip-ratelimit
ip-ratelimit: 300
ip-ratelimit-factor: 10
incoming-num-tcp: 1000
udp-upstream-without-downstream: yes
edns-tcp-keepalive: yes
# Qname minimization activée par défaut, harden-below-nxdomain (RFC 8020) est recommandé
# qname-minimisation-strict est désactivé car casse pas encore pas mal de services
# (Imdb, service de mise à jour de la Switch de Nintendo...)
harden-below-nxdomain: yes
# Diverses mesures de sécurité
harden-glue: yes
harden-dnssec-stripped: yes
harden-referral-path: yes
# Aggressive use of NSEC. RFC 8198.
aggressive-nsec: yes
use-caps-for-id: yes
prefetch: yes
prefetch-key: yes
tls-service-key: /path/to/private.key
tls-service-pem: /path/to/cert.pem
tls-port: 853 # Donné à titre indicatif, c'est la valeur par défaut
# TLS 1.2 ciphers
tls-ciphers: ECDHE-RSA-CHACHA20-POLY1305:EECDH+AES:+AES128:+AES256:+SHA256:+SHA384:!SHA
# TLS 1.3 ciphers
tls-ciphersuites: TLS_CHACHA20_POLY1305_SHA256:TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384
remote-control:
# Écoute sur localhost:8953 par défaut
control-enable: yes
control-use-cert: no
Knot Resolver
Je maîtrise beaucoup moins ce logiciel, je donne donc uniquement une configuration de base :
-- Default empty Knot DNS Resolver configuration in -*- lua -*-
-- Bind ports as privileged user (root) --
-- net = { '127.0.0.1', '::1' }
net.tls('/etc/knot-resolver/cert.pem', '/etc/knot-resolver/key.key')
-- On active le remplissage
net.tls_padding(true)
net.listen('2001:db8:dead:cafe::853', 853)
net.listen('192.0.2.53', 853)
-- Switch to unprivileged user --
user('knot-resolver','knot-resolver')
-- Unprivileged
-- cache.size = 100*MB # À augmenter éventuellement
Sur les systèmes utilisant systemd
, il faut indiquer les sockets.
# systemctl edit kresd-tls.socket
et mettre :
[Socket]
ListenStream=192.168.2.53:853
ListenStream=[2001:db8:dead:cafe::853]:853
Par ailleurs, pour pouvoir faire tourner Knot sur plusieurs threads, on utilise encore systemd
. Ils partageront la même configuration :
# systemctl enable --now kresd@{1..n}.service
et on les démarre :
# systemctl start kresd@{1..n}.service
Surveillez !
Le DNS étant un service critique, il est nécessaire de monitorer ses serveurs DoT. L'équipe de GetDNS propose getdns_server_mon
, un utilitaire présent dans le paquet getdns-utils
à partir de sa version 1.4.0. Il est compatible avec Icinga, Naemon, Nagios, Shinken et Sensu. Sa documentation se trouve sur Github. Tous les tests qu'il propose sont à faire.
DNS sur HTTPS (DoH)
DoH est récent (octobre 2018) et les logiciels ne sont pas encore forcément compatibles. Outre les bouts de code traînant à droite à gauche, le principal serveur disponible est Knot Resolver, compatible de manière expérimentale depuis la version 4.0.0 sortie en avril dernier et niveau client, Firefox depuis la version 62. Un vrai client sera disponible avec la version 0.3 de Stubby, dont la sortie ne devrait plus trop tarder.
Knot Resolver
Je n'ai pas vraiment testé DoH dans Knot Resolver (j'attends Stubby pour faire de vrais tests), le bout de configuration est donc piqué à la documentation du logiciel 😬
modules.load('http')
http.config({
tls = true,
cert = '/etc/knot-resolver/cert.pem',
key = '/etc/knot-resolver/key.key',
}, 'doh')
Firefox
À l'heure actuelle, Firefox est donc malheureusement le principal client DoH disponible dans la nature. Sur la version stable (67 actuellement), l'interface amicale n'est pas encore implémentée et il faut donc aller dans about:config
pour trouver les paramètres (il s' agit de ceux contenant trr
).
Les deux paramètres qui nous intéressent sont network.trr.mode
et network.trr.uri
. Le premier commande le comportement du module DoH de Firefox, le deuxième est l'URL du résolveur DoH à utiliser (on constate que c'est le résolveur de CloudFlare qui est configuré par défaut. Vous pouvez le supprimer 🙂).

Les valeurs possibles de network.trr.mode
sont :
- 0 : DoH est désactivé. Valeur par défaut.
- 1 : Envoie la requête au résolveur DoH configuré et au résolveur configuré au niveau du système. Utilise la première réponse qui arrive.
- 2 : Utilise DoH en premier et si la résolution échoue, se repli sur le résolveur du système.
- 3 : Utilise uniquement DoH.
- 4 : Utilise DoH et le résolveur du système en parallèle mais n'utilise pas les réponses obtenues via DoH. Ce dernier ne sert qu'à faire des mesures de performances.
- 5 : Désactive explicitement DoH. C'est le paramètre que je recommande si vous ne comptez pas utiliser DoH.
Voilà, vous êtes armé·e·s pour chiffrer votre trafic DNS. Comme je le disais dans ma présentation, ce n'est malheureusement pas vraiment accessible pour un large public (celui capable de changer le résolveur à utiliser dans Windows à peu près), la faute au manque de logiciels simples d'accès. En attendant, que les OS intègrent nativement ces techniques via des interfaces graphiques (seul Android 9 le fait pour l'instant), cela risque de ne pas changer...