Implémenter du DNS-Over-HTTPS en Python

le jeudi 15 octobre 2020

Bearstech | Dernière mise à jour : jeudi 15 octobre 2020

Le DNS over HTTPS est un protocole permettant de chiffrer la première étape de votre pérégrination sur Internet. Dans cette article, on vous explique comment implémenter la RFC mettant en place le DoH.

Notre prochain webinar

Le concept du DOH, DNS Over HTTPS, est simple : faire causer un serveur DNS à travers le protocole HTTP.

Le protocole classique du DNS utilise de l'UDP sans aucune sécurité : pas de confidentialité, pas d'authenticité.

Il existe DNSSEC, qui permet de signer les réponses sans solution simple pour récupérer la clef en toute confiance et qui ne chiffre pas l'échange, et donc n'apporte aucune confidentialité.

DNSCrypt propose de la signature et du chiffrage. Il a décidé de snober l'IETF et n'a pas soumis une RFC, il n'a aucune chance de devenir un standard.

DOT, DNS Over TLS. Chiffrer de l'UDP existe (DTLS) mais c'est compliqué. C'est tellement plus simple en TCP avec TLS. Le protocole sait aussi fonctionner en TCP, il est donc trivial d'ajouter la couche de sécurité.

Le service DNS est globalement indiscret, le nom de domaine du site que vous allez visiter ainsi que le moment où vous le demandez sont des super- métadonnées sur votre activité en ligne. Le service DNS est un point central d'Internet, facile à censurer. On a pu voir dans l'actualité des images de murs tagués avec des ip de DNS non contrôlés par leurs gouvernements.

Tag DNS en Turquie

Image de Kaan Sezyum via Twitter.

DoH (DNS-over-HTTPS), le dernier de cette lignée, propose du chiffrage, mais surtout passe par HTTP, rendant plus compliqué la censure. Il suffit d'une URL sur un gros site bien légitime pour mettre à disposition un service DNS. Les butineurs contemporains savent déjà l'utiliser, et ça arrive dans les OS des ordiphones puis des ordinateurs.

Protocole et RFC

Pour implémenter un protocole ce n'est pas compliqué : il suffit d'aller lire sa RFC, puis d'écrire du code. Pour le DoH, c'est la RFC 8484.

Le concept du DoH est simple : faire causer un serveur DNS à l'intérieur d'une requête HTTP. Tellement simple que l'échange utilise le même format binaire que le très tradionnel protocole UDP "over" DNS.

La question passe soit en GET, avec un payload encodé en base64, soit en POST, avec le payload dans le corps de la requête HTTP.

La documentation insiste beaucoup sur l'importance d'HTTP/2 plutôt que la classique version HTTP/1.1, et c'est avec cette version que les butineurs, première cible du DOH, vont causer aux serveurs DoH.

Implémentation

Python est le roi du prototypage avec une logithèque bien fournie. Bon, HTTP/2 a des implémentations matures cotés serveurs, mais c'est un peu plus en mode "peinture fraîche" côté client, hors écosystème Golang, premier lobbyiste HTTP/2 du Monde. Dans Python, il faut lâcher l'éternel Requests, pour Httpx comme bibliothèque HTTP.

Pour le DNS, la bibliothèque dnspython n'est pas super élégante, mais elle est très complète. Oui, dnspython fait du DoH de base, mais en HTTP/1.1, et ça coupe un peu la démonstration pédagogique.

#!/bin/env python3

import httpx
from dns import message, rdatatype


class Resolver:
    def __init__(self, server, client=None):
        self.server = server
        if client is None:
            client = httpx.Client(http2=True)
        self.client = client

Une class pour tout ranger, avec un client HTTP/2 que l'on peut réutiliser pour profiter du multiplexage des échanges. Techniquement, je peux envoyer une brouette de requête dans une même connexion TCP et recevoir les réponses dans leur ordre d'arrivée si j'utilise la version asynchrone du client.

    def query(self, queryname, rdtype=rdatatype.ANY):
        q = message.make_query(queryname, rdtype) # dnspython génère la requête au format wire
        response = self.client.post(self.server,
                             headers={
                                 "Accept": "application/dns-message",
                                 "Content-Type": "application/dns-message"
                             },
                         content=q.to_wire())
        assert response.status_code == 200
        return message.from_wire(response.content) # dnspython lit le format wire

Une requête POST avec les en-têtes requis et le blob binaire dans le corps. Une vérification pédagogique du statut de la réponse, puis sa désérialisation.

if __name__ == '__main__':
    r = Resolver('https://cloudflare-dns.com/dns-query')
    print(r.query('bearstech.com', 'TXT'))

Je choisis le DOH de Cloudflare, parce que pourquoi pas, et je lui demande ce qu'il pense des TXT de bearstech.com.

DOH tient ses promesses : avec une lib DNS et une lib HTTP, on peut coder très rapidement du DoH.

Mème Simpson

Service Audit de Performance web python

Bearstech vous propose ses services Audit de Performance web python

Découvrir ce service

Partager cet article

Flux RSS

flux rss

Partager cet article :