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 :

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...