Bas les MASQUE : découvrir les nouveaux proxys HTTP/3

Traductions

Sommaire

Dans cet article, je vous invite à découvrir les proxys MASQUE, qui permettent la proxification de flux Web reposant sur HTTP/3 ou encore la tunnelisation de communications avec HTTP/3. MASQUE fournit une compatibilité de la fonction proxy ou VPN pour les flux modernes HTTP/3.

Introduction

Les tranferts de données s’intensifiant au fil du temps sur Internet, les protocoles réseaux qui permettent de réaliser ces tranferts s’adaptent pour offrir des performances plus élevées et une sécurité accrue. Le protocole HyperText Transfer Protocol (HTTP) est le protocole principal pour les communications Web. Sa dernière version, HTTP/3, est assez disruptive dans son fonctionnement, même si elle repose sur des fondations établies par les versions antérieures. Près d’un tiers des transferts HTTP se font aujourd’hui en version HTTP/3. De nouveaux mécanismes en cours de standardisation émergent pour rendre compatibles les multiples cas d’usage d’HTTP avec HTTP/3 tout en tirant profit de ses avantages. Parmis les cas d’usage les plus répandus, on retrouve la fonction de proxification pour le surf sécurisé et celle des accès distants au travers de réseaux publics.
D’abord, nous verrons comment fonctionne HTTP/3, puis nous explorerons la méthode Extended CONNECT et le proxy MASQUE qui permettent la proxification et la tunnelisation des communications pour les accès distants. Enfin, je vous montrerai une démonstration de l’utilisation de MASQUE pour la proxification de flux HTTP/3.

HTTP/3 et QUIC

Après HTTP/1 et HTTP/2, HTTP/3, standardisé en 2022, est la troisième version majeure du protocole HyperText Transfer Protocol (HTTP). Il utilise le protocole QUIC qui repose lui-même sur le protocole de transport User Datagram Protocol (UDP), contraitement aux versions HTTP précédentes comme HTTP/2 qui reposent directement sur le protocole Transmission Control Protocol (TCP). Commençons par mieux comprendre ce qu’est QUIC.
QUIC fut standardisé en 2021 et a pour but de fournir un nouveau protocole de transport orienté connexion mais reposant sur UDP qui n’est lui-même pas orienté connexion. Il embarque une adaptation du protocole Transport Layer Security (TLS) pour la sécurisation des messages par l’authentification et le chiffrement des paquets QUIC. L’utilisation de communications sécurisées par TLS est d’ailleurs obligatoire dans QUIC.
Les informations qui sont échangées avec QUIC transitent via des flux ou streams qui sont des séquences d’octets et peuvent être unidirectionnelles ou bidirectionnelles. Afin d’assurer une priorité de connexion (comme peut le faire TCP), QUIC s’assure de la délivrance des paquets et du contrôle de congestion. Une extension de QUIC permet aussi l’envoi de paquets de manière non fiable, à la manière d’UDP, où on peut envoyer des datagrammes ou datagrams individuels sans contrôle de congestion ni retransmission sur pertes.

Fonctionnalités des piles protocolaires de HTTP/2 et HTTP/3 (source : https://gcore.com/learning/what-is-http-3/)

Les streams QUIC sont matérialisées sous forme de trames ou frames QUIC pour la transmissions de données, elle-mêmes encapsulées dans un paquet QUIC qui réside dans un datagramme UDP. Les streams QUIC sont identifiées grâce à un identifiant de stream ou stream ID.

Revenons à HTTP/3. Ce dernier succède donc à HTTP/2, qui avait en 2015 déjà apporté notamment des améliorations de performances en permettant le multiplexage des requêtes. Cela signifie que plusieurs requêtes HTTP peuvent survenir au sein d’une même connexion TCP (très fréquent lors de la visite d’un site Web par exemple). L’avènement de TLS 1.3 avait également permis d’accélerer l’établissement des sessions cryptographiques par rapport à TLS 1.2 et on parle ainsi d’un établissement de connexions avec aucun aller-retour ou Zero Round-Trip Time (0-RTT). Avec HTTP/3, ce concept de 0-RTT est porté également sur la couche de transport et ainsi, l’établissement de la session de transport est combiné avec celui de la session cryptographique. HTTP/3 impose le chiffrement par défaut avec TLS 1.3.
Comparer l’établissement d’une session HTTP/2 avec celle d’une session HTTP/3, c’est un peu comme comparer cette conversation :

  • Salut !
  • Salut !
  • Ca va ?
  • Ca va bien, et toi ça va ?
  • Ca va merci.
  • Je voulais te parler de quelque chose…

Avec cette conversation :

  • Salut, ça va ?
  • Salut, ça va bien et toi ?
  • Ca va merci. Je voulais te parler de quelque chose…

Si on regarde de plus près, voici un établissement de session avec HTTP/2 et TLS 1.2 :

Etablissement de session HTTP/2 avec TLS 1.2

Voici un établissement de session avec HTTP/2 et TLS 1.3 :

Etablissement de session HTTP/2 avec TLS 1.3

Et voici un établissement de session avec HTTP/3 :

Etablissement de session HTTP/3

Notez que des initiatives similaires existent également au niveau de TCP avec TCP Fast Open, où le principe est de commencer l’envoi de données dans le premier paquet TCP SYN. Cependant cette solution n’a pas été vraiment adoptée notamment par les navigateurs Web.

On peut également noter qu’HTTP/3 permet l’élimination de l’effet des pertes de paquets inhérent à TCP ou Head-of-line blocking, où le traitement séquentiel des segments peut faire qu’un large paquet en attente de traitement dans la file d’attente bloque le traitement de plus petits paquets. Ainsi, avec HTTP/3, des flux indépendants peuvent être multiplexés dans la même connexion et potentiellement ne pas impacter les autres flux.

L’unité de communication dans HTTP/3 est la trame ou frame qui peut être de type HEADERS ou DATA pour le transfert de données, ou bien d’autres types comme SETTINGS pour le contrôle du transfert. Les requêtes HTTP/3 peuvent être multiplexées grâce aux streams QUIC. Chaque paire HTTP requête-réponse correspond à une stream QUIC qui sont indépendantes les unes des autres.

Encapsulation de trames HTTP/3

HTTP/3 étant de plus en plus adopté pour tous les avantages énumérées et sa nature changeant la manière dont les données sont transportées, il est requis de nouveaux mécanismes pour pouvoir faire circuler des flux sur Internet dans des cas d’usages plus avancés que de simples accès Web.

Extended CONNECT et proxy MASQUE

Il existe de nombreux cas d’usage où les protocoles HTTP et TLS sont impliqués pour permettre la navigation Web sécurisée à travers des proxys Web ou serveurs mandataires Web, ou encore les connexions distantes via des Virtual Private Networks (VPN).
La méthode HTTP CONNECT est une méthode HTTP, comme peuvent l’être par exemple les célèbres GET (obtenir la représentation d’une ressource) ou POST (soumettre une entité à une ressource). CONNECT sert à demander au destinataire l’établissement d’une connexion TCP vers une ressource cible, c’est-à-dire de demander au destinataire la création d’un socket TCP : demi-connexion qui sert d’interface pour faire circuler des données via TCP et ainsi établir des connexions TCP complètes. Le besoin principal est initialement de pouvoir créer des tunnels TLS afin de rendre possible des connexion HTTPS à travers des proxys Web. Grâce à CONNECT, on peut également faire passer d’autres protocoles reposant sur TCP : Secure SHell (SSH), File Transfer Protocol (FTP)…

HTTP CONNECT avec un proxy Web

QUIC utilisant UDP, il a été proposé d’étendre CONNECT pour supporter la proxification de trafic UDP. On parle d’Extended CONNECT et particulièrement de CONNECT-UDP. Ce concept a été introduit pour HTTP/2 pour les WebSockets (communications bidirectionnelles entre un client et un serveur au sein d’une même connexion) et est désormais aussi porté dans HTTP/3. C’est dans ce contexte qu’a émergé Multiplexed Application Substrate over QUIC Encryption (MASQUE), qui vient proposer une fonction de proxy générique afin de relayer des connexions sécurisées de bout en bout sur HTTP. Il repose sur Extended CONNECT.

HTTP/3 Extended CONNECT-UDP avec un proxy MASQUE pour une connexion HTTP/3

Une des utilisations préposées d’un proxy MASQUE, au-delà de relayer des connexions vers des serveurs Web en HTTP/3, est de pouvoir établir des VPN de type client-à-site pour permettre l’accès distant d’utilisateurs à un site (au sens localité) via HTTP. En poussant la réflexion, on peut aussi envisager de pouvoir proxifier n’importe quel type de trafic reposant sur le protocole IP voire Ethernet. Cela permettrait d’adresser, en plus de TCP et UDP, des protocoles comme Internet Control Message Protocol (ICMP) et Encapsulating Security Payload (ESP). On parle alors de CONNECT-IP. Pour Ethernet, on parle de CONNECT-ETHERNET, où un domaine de diffusion réseau ou broadcast peut ainsi être étendu au travers de HTTP.

HTTP/3 Extended CONNECT-IP avec un proxy MASQUE pour une connexion VPN

Il est à noter que c’est le protocole Capsule qui régit la manière dont les paquets de données et les messages de contrôle sont encapsulés et transmis. Les messages de contrôle permettent, par exemple dans le cas du VPN, d’assigner une configuration réseau aux clients.

Démonstration

Les implémentations de MASQUE sont rares et encore à l’état expérimental. Les navigateurs Web ou les célèbres outils en ligne de commande comme cURL ou wget ne le proposent pas encore au moment où j’écris cet article.
Cela dit, j’ai trouvé plusieurs preuves de concepts intéressantes comme les suivantes :

J’ai testé la première, masque-go, qui fait partie d’une suite logicielle, quic-go, écrite en langage Go et implémentant le protocole QUIC avec des extensions dont MASQUE.
Je vous propose de tester ici la méthode CONNECT-UDP, et vous recommande donc d’aller lire la documentation associée à la proxification d’UDP avec HTTP.

Voici l’architecture que j’ai utilisée pour tester le proxy MASQUE sur ma machine.

Architecture utilisée pour la démonstration

Le proxy MASQUE, qui écoute sur le port UDP 3128, et le client MASQUE partagent la même interface réseau virtuelle locale lo0. Le proxy MASQUE utilise l’interface physique en0 pour se connecter à l’Internet et notamment, à la demande du client, au site https://cloudflare-quic.com qui fonctionne en HTTP/3.

Tout d’abord, téléchargez le projet masque-go :

$ git clone https://github.com/quic-go/masque-go.github
$ cd masque-go

Afin de pouvoir mener mes tests localement sans déployer des configurations complexes, j’ai légèrement modifié le code du client afin de désactiver la vérification du certificat TLS fourni par le serveur (à ne pas faire en environnement de production !). J’ai donc ajouté l’argument InsecureSkipVerify: true lors de l’instanciation de la configuration TLS du client. Aussi, afin de pouvoir analyser les échanges QUIC chiffrés entre le client et le proxy et donc pouvoir récupérer les clés de session TLS générées dans un fichier de journalisation (ici sslkeylog.txt), j’ai créé un KeyLogWriter que j’ai ajouté via l’argument KeyLogWriter: sslKeyLogFile lors de l’instanciation de la configuration TLS du client. Voici les modifications associées au fichier client.go :

sslKeyLogFileName := "sslkeylog.txt"
sslKeyLogFile, err := os.OpenFile(sslKeyLogFileName, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0644)
if err != nil {
    log.Fatalf("Failed to open SSL key log file: %v", err)
}
defer sslKeyLogFile.Close()

tlsConf := c.TLSClientConfig
if tlsConf == nil {
    tlsConf = &tls.Config{NextProtos: []string{http3.NextProtoH3}, InsecureSkipVerify: true, KeyLogWriter: sslKeyLogFile}
}

Générez une clé privée RSA ainsi qu’un certificat TLS auto-signé pour le proxy MASQUE :

$ cd cmd/proxy
$ openssl genpkey -algorithm RSA -out private.key -pkeyopt rsa_keygen_bits:4096
$ openssl req -new -x509 -key private.key -out certificate.crt -days 365 -subj "/C=FR/ST=State/L=Home/O=Local/OU=IT/CN=localhost"

Lancez le proxy MASQUE sur le port UDP 3128 en utilisant le certificat et la clé privée précédemment générés :

$ go run main.go -b 127.0.0.1:3128 -c certificate.crt -k private.key -t "https://localhost:3128/masque?h={target_host}&p={target_port}"

Dans un autre terminal, lancez le client HTTP/3 pour contacter le site https://cloudflare-quic.com en utilisant le proxy MASQUE qui écoute localement sur le port UDP 3128 :

$ cd masque-go/cmd/client/
$ go run main.go -t "https://localhost:3128/masque?h={target_host}&p={target_port}" https://cloudflare-quic.com
Vous devriez obtenir la réponse HTML du site Web https://cloudflare-quic.com. Bien entendu, il faut que le réseau auquel votre proxy MASQUE est connecté autorise les flux QUIC.

J’ai ensuite effectué une capture de paquet avec Wireshark en parallèle. Pour pouvoir déchiffrer la partie chiffrée dans QUIC avec TLS, dans Wireshark j’ai indiqué dans Preferences... > Protocols > TLS > (Pre)-Master-Secret log filename le chemin vers le fichier sslkeylog.txt. Afin d’améliorer la visibilité, j’ai appliqué le filtre Wireshark suivant :

quic or http3

J’ai reporté les paquets QUIC échangés entre client, proxy et site Web dans le graphe suivant.

Echanges de paquets entre le client, proxy et site Web

Dans la pratique, on constate qu’il y a de nombreux échanges impliqués. Voici donc les explications associées.

Les premiers échanges, en bleu sur le graphe, correspondent à la négociation de connexion QUIC, démarrée par un paquet de type Initial, et la négociation TLS (contenue dans les frames QUIC de type CRYPTO). Il est à noter que plusieurs paquets sont envoyés du proxy vers le client pour répondre à la négociation TLS. Ceci est dûe à la taille du certificat, ce dernier étant répartie sur 3 paquets. En outre, les frames PADDING permettent d’ajouter du bourrage et augmenter la taille du paquet QUIC lors de l’établissement de la communication afin de notamment s’assurer que la communication pourra bien supporter le transfert de paquets de cette taille. Les frames QUIC ACK indiquent l’intégration de la fonctionnalité de contrôle de congestion explicit ou Explicit Congestion Notification (ECN) pour l’amélioration de la détection et le contrôle de congestion réseau. Les frames PING servent à vérifier et maintenir la connexion QUIC en cours entre le client et le proxy.

Ensuite, en rouge sur le graphe, on retrouve la première frame HTTP/3 de type SETTINGS permettant d’indiquer au client le support du transport de datagrammes UDP de manière individuelle et non fiable ainsi que le support de la méthode Extended CONNECT. Puis, une frame HTTP/3 de type HEADERS réalise un CONNECT-UDP avec comme chemin cible ou path /masque?h=172.67.9.235&p=443 (le client a effectué une résolution DNS au préalable afin de résoudre cloudflare-quic.com qui est associée à l’adresse IP 172.67.9.235). Une frame QUIC ACK avec ECN et une frame HTTP/3 de type HEADERS envoyant un statut HTTP de type 200 OK confirme la prise en compte de la demande de connexion.

Première partie de la capture des paquets entre le client, proxy et site Web avec le détail du paquet HTTP/3 CONNECT-UDP

Puis, en orange sur le graphe, on retrouve les flux QUIC échangés entre le client et le site Web via le proxy où principalement la négociation TLS qui se déroule.

Enfin, on retrouve en jaune les échanges de données HTTP concrets.

Seconde partie de la capture des paquets entre le client, proxy et site Web avec le détail du paquet HTTP/3 GET

Conclusion

Dans cet article, nous avons découvert ensemble ce qu’est un proxy MASQUE, permettant la proxification et la tunnelisation de flux sur le protocole HTTP/3 qui repose sur QUIC. Nous avons également vu une démonstration expérimentale concrète de son utilisation en langage Go afin de proxifier une connection HTTP/3 vers un site Web à travers un proxy MASQUE.

Sources

Traductions