Les RFC (Request For Comments) sont les documents de référence de l'Internet. Produits par l'IETF pour la plupart, ils spécifient des normes, documentent des expériences, exposent des projets...
Leur gratuité et leur libre distribution ont joué un grand rôle dans le succès de l'Internet, notamment par rapport aux protocoles OSI de l'ISO organisation très fermée et dont les normes coûtent cher.
Je ne tente pas ici de traduire les RFC en français (un projet pour cela existe mais je n'y participe pas, considérant que c'est une mauvaise idée), mais simplement, grâce à une courte introduction en français, de donner envie de lire ces excellents documents. (Au passage, si vous les voulez présentés en italien...)
Le public visé n'est pas le gourou mais l'honnête ingénieur ou l'étudiant.
Date de publication du RFC : Novembre 2024
Auteur(s) du RFC : J. Snijders
(Fastly), B. Cartwright-Cox (Port
179), Y. Qu (Futurewei)
Chemin des normes
Première rédaction de cet article le 8 novembre 2024
Que doit faire un routeur BGP lorsque le pair en face ne traite manifestement plus ses messages ? Ce n'était pas précisé avant mais la réponse est évidente : raccrocher (mettre fin à la communication).
Un problème classique lors d'une connexion réseau, par exemple sur TCP, est de détecter si la machine en face est toujours là. Par défaut, TCP ne fournit pas ce service : s'il n'y a aucun trafic, vous ne pouvez pas savoir si votre partenaire est mort ou simplement s'il n'a rien à dire. Une coupure de réseau, par exemple, ne sera pas détectée tant que vous n'avez pas de trafic à transmettre (avec attente d'une réponse). Et BGP ne transmet que les changements donc l'absence de trafic ne signale pas forcément un problème. Il existe des solutions, comme d'envoyer périodiquement des messages même quand on n'a rien à dire (RFC 4271, section 4.4), mais aucune n'est parfaite : un programme qui utilise TCP ne sait typiquement pas immédiatement si ses messages sont vraiment partis (et l'alarme actuelle ne couvre que la réception des messages, pas leur envoi). Et BGP n'a pas de fonction « ping », qui exigerait une réponse.
Quand la coupure est franche et détectée, aucun problème, la session BGP (RFC 4271) s'arrête et les routes correspondantes sont retirées de la table de routage. Mais ce RFC traite le cas de où le routeur BGP d'en face a un problème mais qu'on ne détecte pas. Un exemple : si ce routeur en face a complètement fermé sa fenêtre TCP de réception (RFC 9293, notamment la section 3.8.6), on ne pourra pas lui envoyer de messages, mais la session BGP ne sera pas coupée et les paquets continueront à être transmis selon des annonces de routage dépassées, alors qu'ils finiront peut-être dans un trou noir (le problème des « zombies BGP »).
La solution (section 3 de notre RFC) est de modifier
l'automate de BGP (RFC 4271, section 8), en ajoutant une alarme (RFC 4271, section 10),
SendHoldTimer
. Quand elle expire, on coupe la
connexion TCP et on retire les routes qu'avait annoncé le pair dont
on n'a plus de nouvelles. Le RFC recommande une configuration par
défaut de huit minutes de patience avant de déclencher l'alarme.
L'erreur « Send Hold Timer Expired » est désormais dans le registre IANA des erreurs BGP et tcpdump sait l'afficher. Il existe plusieurs mises en œuvre de ce RFC :
Si les processus IETF vous passionnent, il y a une documentation des discussions autour de ce RFC.
Date de publication du RFC : Novembre 2024
Auteur(s) du RFC : C. Bormann (Universität Bremen
TZI)
Chemin des normes
Réalisé dans le cadre du groupe de travail IETF cbor
Première rédaction de cet article le 19 novembre 2024
Voici une légère mise à jour de la grammaire du langage de description de schéma CDDL (Concise Data Definition Language), originellement normalisé dans le RFC 8610. Pas de gros changement.
Il y avait juste quelques errata à traiter, et des ambiguités concernant notamment les chaines de caractères. Il était devenu nécessaire de modifier l'ABNF. CDDL a été décrit dans le RFC 8610, puis étendu par le RFC 9165. Il vise à décrire un schéma pour des fichiers CBOR ou JSON. Depuis quatre ans qu'il est normalisé, plusieurs erreurs ont été relevées dans la norme. (Dont une, c'est amusant, causée par le traitement du source du RFC, traitement qui avait fait disparaitre accidentellement des barres obliques inversées.)
Donc, notre RFC 9682 modifie l'ABNF qui décrit les littéraux pour les chaines de caractères, ABNF qui était trop laxiste (section 2 du RFC). Il change également la grammaire générale (section 3) pour autoriser (au niveau syntaxique) des schémas vides (ils restent interdits au niveau sémantique). Ainsi, l'ancienne règle :
cddl = S 1*(rule S)
devient :
cddl = S *(rule S)
Date de publication du RFC : Octobre 2024
Auteur(s) du RFC : J. M. Halpern (Ericsson), J. Daley
(IETF Administration LLC)
Pour information
Première rédaction de cet article le 31 octobre 2024
Les normes publiées par l'IETF ne sont pas que des documents techniques à seule destination des techniciens. L'Internet et, de manière plus générale, les protocoles TCP/IP sont aussi une grosse industrie qui brasse beaucoup d'argent. Il y a donc un risque que des acteurs de cette industrie essaient d'influencer les normes à leur profit, par exemple en formant des alliances qui, dans certains pays, seraient illégales au regard des lois antitrust. Ce court RFC administratif explique aux participant·es IETF ce que sont ces lois et comment éviter de les violer.
En effet, les organisations, notamment les entreprises à but lucratif, qui participent à l'IETF peuvent être concurrentes sur leurs marchés. Or, le développement de normes nécessite de la collaboration entre ces concurrents. Pour maintenir une concurrence et pour éviter les ententes, plusieurs pays ont des lois, dites « antitrust », lois que des participant·es à l'IETF ne connaissent pas forcément bien. La justification idéologique de ces lois est rappelée par le RFC, dans le cas étatsunien mais d'autres pays capitalistes ont des principes similaires : « Competition in a free market benefits consumers through lower prices, better quality and greater choice. Competition provides businesses the opportunity to compete on price and quality, in an open market and on a level playing field, unhampered by anticompetitive restraints. » Que ce soit vrai ou pas, peu importe, les lois antitrust doivent être respectées. Ce RFC n'édicte pas de règles pour l'IETF (« respectez la loi » est de toute façon déjà obligatoire), mais il explique des subtilités juridiques aux participant·es à l'IETF.
Il y a en effet deux risques pour l'IETF, qu'un·e représentant officiel de l'IETF soit accusé de comportement anti-concurrentiel, engageant la responsabilité de l'organisation, ou que des participant·es soient accusés de comportement anti-concurrentiel, ce qui n'engagerait pas la responsabilité de l'IETF mais pourrait affecter sa réputation.
Les participant·es à l'IETF sont censés suivre des règles dont certaines limitent déjà le risque de comportement anti-concurrentiel, entre autres :
Maintenant, s'y ajoutent les questions spécifiques au respect des lois sur la concurrence. La section 4, le cœur de ce RFC, attire l'attention sur :
legal@ietf.org
) ou via
le service
spécifique pour les lanceurs d'alerte.Date de publication du RFC : Octobre 2024
Auteur(s) du RFC : R. Hinden (Check Point
Software), G. Fairhurst (University of
Aberdeen)
Chemin des normes
Réalisé dans le cadre du groupe de travail IETF 6man
Première rédaction de cet article le 31 octobre 2024
Parmi les différentes options qui peuvent être placées dans un en-tête d'un paquet IPv6, certaines sont à traiter par chaque routeur situé sur le trajet. On les appelle les options « par saut » (hop-by-hop). Elles sont très peu utilisées en pratique, entre autres parce que leur traitement, tel que spécifié dans le RFC 8200, est trop contraignant pour les routeurs. Ce nouveau RFC change donc les règles, dans le sens d'un plus grand pragmatisme.
À part l'en-tête « Hop-by-hop Options » (RFC 8200, section 4.3), tous les en-têtes IPv6 ne concernent que les machines terminales. « Hop-by-hop Options », lui, concerne les routeurs et, avant le RFC 8200, tous les routeurs sur le trajet avaient l'obligation de le lire et d'agir en fonction des options qu'il contenait (la liste complète des options possibles est dans un registre IANA). Bien trop coûteuse pour les routeurs, cette obligation a été supprimée par le RFC 8200. Ce nouveau RFC 9673 modifie le traitement des options de cet en-tête (et donc le RFC 8200) dans l'espoir qu'il voit enfin un vrai déploiement dans l'Internet (actuellement, cet en-tête par saut - hop-by-hop - est quasiment inutilisé). Si vous concevez des routeurs, et êtes pressé·e, sautez directement à la section 5 du RFC, qui décrit les nouvelles règles, mais ce serait dommage.
Petite révision sur l'architecture des routeurs (section 3 du RFC). Les routeurs de haut de gamme ont une voie rapide (fast path) pour le traitement des paquets, lorsque ceux-ci n'ont pas de demande particulière. Mise en œuvre en dehors du processeur principal du routeur, cette voie rapide est traitée par des circuits spécialisés, typiquement des ASIC. Si le paquet nécessite des opérations plus complexes, on passe par une voie plus lente, utilisant des méthodes et du matériel plus proches de ceux d'un ordinateur classique. (Les RFC 6398 et RFC 6192 sont des lectures recommandées ici.) D'autre part, on distingue souvent, dans le routeur, la transmission (forwarding plane) et le contrôle (control plane). La transmission est le travail de base du routeur (transmettre les paquets reçus sur une interface via une autre interface, et le plus vite possible), le contrôle regroupe notamment les opérations de manipulation de la table de routage, par exemple lors de mises à jour reçues via des protocoles comme OSPF ou BGP. Contrairement à la transmission, le contrôle n'est pas en « temps réel ».
Aujourd'hui, un paquet IPv6 utilisant des options par saut risque fort de ne même pas arriver à destination, sacrifié par des routeurs qui ne veulent pas le traiter. (Voir le RFC 7872, l'exposé « Internet Measurements: IPv6 Extension Header Edition » ou l'article « Is it possible to extend IPv6? ».)
Que disent donc les nouvelles procédures (section 5) ?
Pour faciliter la tâche des routeurs, et toujours dans l'espoir que les options par saut deviennent enfin une possibilité réaliste, la section 6 du RFC encadre la définition de nouveaux en-têtes (le RFC 8200 recommandait carrément de ne plus en définir, tant qu'on n'avait pas mieux défini leur utilisation). Les éventuelles futures options doivent être simples à traiter et conçues en pensant au travail qu'elles imposeront au routeur. Le protocole qui les utilise doit intégrer le fait que le routeur est autorisé à ignorer cette option, voire qu'il puisse jeter le paquet.
Ah, et un mot sur la sécurité (section 8). Plusieurs RFC ont déjà documenté les problèmes de sécurité que peuvent poser les options par saut, notamment le risque qu'elles facilitent une attaque par déni de service sur le routeur : RFC 6398, RFC 6192, RFC 7045 et RFC 9098.
Est-ce que le déploiement de ce RFC va améliorer les choses pour l'en-tête par saut, qui est jusqu'à présent un exemple d'échec ? Je suis assez pessimiste, étant donné la difficulté à changer des comportements bien établis.
Un peu d'histoire pour terminer (section 4 du RFC) : les
premières normes IPv6 (RFC 1883, puis RFC 2460) imposaient à tous les routeurs d'examiner
et de traiter les options par saut. Le RFC 7045 avait été le premier à constater que cette règle
n'était pas respectée et ne pouvait pas l'être vu l'architecture des
routeurs modernes. Le problème de performance dans le traitement des
options était d'autant plus grave que les options n'avaient pas
toutes le même format (cela a été résolu par le RFC 6564, qui imposait un format unique). Le résultat, comme
vu plus haut, était que les routeurs ignoraient les options par saut
ou, pire, jetaient le paquet qui les contenait. (Le RFC 9288 et l'article « Threats
and Surprises behind IPv6 Extension Headers »
expliquent pourquoi c'est une mauvaise
idée. L'Internet Draft
draft-ietf-v6ops-hbh
discute également cette
question.)
Date de publication du RFC : Octobre 2024
Auteur(s) du RFC : D. Thaler
Chemin des normes
Réalisé dans le cadre du groupe de travail IETF bpf
Première rédaction de cet article le 1 novembre 2024
On a souvent envie de faire tourner des programmes à soi dans le noyau du système d'exploitation, par exemple à des fins de débogage ou d'observation du système. Cela soulève plein de problèmes (programmer dans le noyau est délicat) et la technique eBPF permet, depuis de nombreuses années, de le faire avec moins de risques. Ce RFC spécifie le jeu d'instructions eBPF. Programmeureuses en langage d'assemblage, ce RFC est pour vous.
eBPF désigne ici un jeu d'instructions (comme ARM ou RISC-V). Programmer en eBPF, c'est donc programmer en langage d'assemblage et, en général, on ne le fait pas soi-même, on écrit dans un langage de plus haut niveau (non spécifié ici mais c'est souvent un sous-ensemble de C) et on confie à un compilateur le soin de générer les instructions. Ce jeu d'instructions a plusieurs particularités. Notamment, il est délibérément limité, puisque toute bogue dans le noyau est particulièrement sérieuse, pouvant planter la machine ou pire, permettre son piratage. Vous ne pouvez pas faire de boucles générales, par exemple. eBPF est surtout répandu dans le monde Linux (et c'est là où vous trouverez beaucoup de ressources) où il est une alternative aux modules chargés dans le noyau. Pas mal du code réseau d'Android est ainsi en eBPF. Normalisé ici, eBPF peut être mis en œuvre sur d'autres noyaux (il tourne sur Windows, par exemple). Le monde eBPF est très riche, il y a plein de logiciels (pas toujours faciles à utiliser), plein de tutoriels (pas toujours à jour et qui ne correspondent pas toujours à votre système d'exploitation) mais cet article se focalise sur le sujet du RFC : le jeu d'instructions.
On trouve de nombreux exemples d'utilisation en production par exemple le répartiteur de charge Katran chez Facebook, via lequel vous êtes certainement passé, si vous utilisez Facebook. En plus expérimental, j'ai trouvé amusant qu'on puisse modifier les réponses DNS en eBPF.
Passons tout de suite à la description de ce jeu
d'instructions (ISA = Instruction Set
Architecture). D'abord, les types (section 2.1) :
u32
est un entier non
signé sur 32 bits, s16
, un signé
sur 16 bits, etc. eBPF fournit des fonctions de conversions utiles
(section 2.2) comme be16
qui convertit en
gros boutien (le RFC cite IEN137…). Au
passage, une mise en œuvre d'eBPF n'est pas obligée de tout fournir
(section 2.4). La norme décrit des groupes de conformité et une
implémentation d'eBPF doit lister quels groupes elle met en
œuvre. Le groupe base32
(qui n'a rien à voir
avec le Base32 du RFC 4648) est le minimum requis dans tous les cas. Par
exemple, divmul32
ajoute multiplication et
division. Tous ces groupes figurent dans un registre IANA.
Les instructions eBPF sont encodées en 64 ou 128 bits (section
3). On y trouve les instructions classiques de tout jeu, les
opérations arithmétiques (comme ADD
), logiques
(comme AND
), les sauts
(JA
, JEQ
et autrs), qui se
font toujours vers l'avant, pour, je suppose, ne pas permettre de boucles
(souvenez-vous du problème de l'arrêt, qui n'a
pas de solution avec un jeu
d'instructions plus étendu), l'appel de fonction, etc.
En parlant de fonctions, eBPF ne peut pas
appeler n'importe quelle fonction. Il y a deux sortes de fonctions
utilisables, les fonctions d'aide (section 4.3.1), pré-définies par
la plateforme utilisée, et non normalisées (pour celles de Linux,
voir la
documentation, qui est sous
Documentation/bpf
si vous avez les sources du
noyau). Il y a aussi les fonctions locales (section 4.3.2), définies
par le programme eBPF.
Il y a enfin des instructions pour lire et écrire dans la mémoire
(LD
, ST
, etc). Pour
mémoriser plus facilement, eBPF utilise
des dictionnaires (maps,
cf. section 5.4.1).
La section 6 concerne la sécurité, un point évidemment crucial puisque les programmes eBPF tournent dans le noyau, où les erreurs ne pardonnent pas. Un programme eBPF malveillant peut provoquer de nombreux dégâts. C'est pour cela que, sur Linux, seul root peut charger un tel programme dans le noyau. Le RFC recommande de faire tourner ces programmes dans un environnement limité (bac à sable), de limiter les ressources dont ils disposent et de faire tourner des vérifications sur le programme avant son exécution (par exemple, sur Linux, regardez cette documentation ou bien l'article « Simple and Precise Static Analysis of Untrusted Linux Kernel Extensions »).
Enfin, section 7, les registres (pas les registres du processeur, ceux où on enregistre les codes utilisés). Deux registres IANA sont créés, celui des groupes de conformité et celui du jeu d'instructions. L'annexe A du RFC donne les valeurs actuelles. Les registres sont extensibles et la politique d'enregistrement est « Spécification nécessaire » et « Examen par un expert », cf. RFC 8126. (J'avoue ne pas savoir pourquoi, si les opcodes sont enregistrés, les mnémoniques ne le sont pas, cela rend les registres difficiles à lire.)
Un peu d'histoire, au passage. eBPF est dérivé de BPF, ce qui voulait dire Berkeley Packet Filter, et était spécifique au filtrage des paquets réseau. Cet usage a été notamment popularisé par tcpdump. D'ailleurs, ce programme a une option pour afficher le code BPF produit :
% sudo tcpdump -d port 53 (000) ldh [12] (001) jeq #0x86dd jt 2 jf 10 (002) ldb [20] (003) jeq #0x84 jt 6 jf 4 (004) jeq #0x6 jt 6 jf 5 (005) jeq #0x11 jt 6 jf 23 (006) ldh [54] … (021) jeq #0x35 jt 22 jf 23 (022) ret #262144 (023) ret #0
Si vous voulez vous mettre à eBPF (attention, la courbe
d'apprentissage va être raide), man 4 bpf
est
utile. Typiquement, vous écrirez vos programmes dans un
sous-ensemble de C et vous
compilerez en eBPF, par exemple avec clang,
après avoir installé tous les outils et bibliothèques nécessaires
(il faut souvent des versions assez récentes) :
% cat count.c … int count_packets(struct __sk_buff *skb) { __u32 key = 0; __u64 *counter; counter = bpf_map_lookup_elem(&pkt_counter, &key); if (counter) { (*counter)++; } return 0; } … % clang -target bpf -c count.c % file count.o count.o: ELF 64-bit LSB relocatable, eBPF, version 1 (SYSV), not stripped % objdump -d count.o … 0000000000000000 <count_packets>: 0: 7b 1a f8 ff 00 00 00 00 stxdw [%r10-8],%r1 8: b7 01 00 00 00 00 00 00 mov %r1,0 10: 63 1a f4 ff 00 00 00 00 stxw [%r10-12],%r1 18: 18 01 00 00 00 00 00 00 lddw %r1,0 20: 00 00 00 00 00 00 00 00 28: bf a2 00 00 00 00 00 00 mov %r2,%r10 30: 07 02 00 00 f4 ff ff ff add %r2,-12
(Notez l'utilisation du désassembleur objdump.) Vous pouvez alors charger le code eBPF dans votre noyau, par exemple avec bpftool (et souvent admirer de beaux messages d'erreur comme « libbpf: elf: legacy map definitions in 'maps' section are not supported by libbpf v1.0+ »). Si tout fonctionne, votre code eBPF sera appelé par le noyau lors d'événements particuliers que vous avez indiqués (par exemple la création d'un processus, ou bien l'arrivée d'un paquet par le réseau) et fera alors ce que vous avez programmé. Comme me le fait remarquer Pierre Lebeaupin, il y a une bogue dans le source ci-dessus : l'incrémentation du compteur n'est pas atomique et donc, si on a plusieurs CPU, on risque de perdre certaines incrémentations. La solution de ce problème est laissé à la lectrice.
Un exemple d'utilisation d'eBPF pour observer ce que fait le noyau (ici avec un outil qui fait partie de bcc), on regarde les exec :
% sudo /usr/sbin/execsnoop-bpfcc PCOMM PID PPID RET ARGS check_disk 389622 1628 0 /usr/lib/nagios/plugins/check_disk -c 10% -w 20% -X none -X tmpfs -X sysfs -X proc -X configfs -X devtmpfs -X devfs -X check_disk 389623 1628 0 /usr/lib/nagios/plugins/check_disk -c 10% -w 20% -X none -X tmpfs -X sysfs -X proc -X configfs -X devtmpfs -X devfs -X check_swap 389624 1628 0 /usr/lib/nagios/plugins/check_swap -c 25% -w 50% check_procs 389625 1628 0 /usr/lib/nagios/plugins/check_procs -c 400 -w 250 ps 389627 389625 0 /bin/ps axwwo stat uid pid ppid vsz rss pcpu etime comm args sh 389632 389631 0 /bin/sh -c [ -x /usr/lib/php/sessionclean ] && if [ ! -d /run/systemd/system ]; then /usr/lib/php/sessionclean; fi sessionclean 389633 1 0 /usr/lib/php/sessionclean sort 389635 389633 0 /usr/bin/sort -rn -t: -k2,2 phpquery 389638 389634 0 /usr/sbin/phpquery -V expr 389639 389638 0 /usr/bin/expr 2 - 1 sort 389642 389638 0 /usr/bin/sort -rn
Le code eBPF est interprété par une machine virtuelle ou bien traduit à la volée en code natif.
Même ChatGPT peut écrire de l'eBPF (les tours de Hanoi et un serveur DNS).
De nombreux exemples se trouvent dans le répertoire
samples/bpf
des sources du noyau Linux. (Le
fichier README.rst
explique comment compiler
mais seulement dans le cadre de la compilation d'un noyau. En gros,
c'est make menuconfig
, cd
samples/bpf
puis make -i
.) Un bon
exemple, relativement simple, pour commencer avec le réseau est
tcp_clamp_kern.c
.
Si vous préférez travailler en Go (là aussi, avec un Go récent…), il existe un bon projet. Si vous suivez bien la documentation, vous pourrez compiler des programmes et les charger :
% go mod init ebpf-test % go mod tidy % go get github.com/cilium/ebpf/cmd/bpf2go % go generate % go build % sudo ./ebpf-test 2024/08/20 15:21:43 Counting incoming packets on veth0.. … 2024/08/20 15:22:03 Received 25 packets 2024/08/20 15:22:04 Received 26 packets 2024/08/20 15:22:05 Received 27 packets 2024/08/20 15:22:06 Received 502 packets <- ping -f 2024/08/20 15:22:07 Received 57683 packets 2024/08/20 15:22:08 Received 75237 packets ^C2024/08/20 15:22:09 Received signal, exiting..
Vous trouverez beaucoup de ressources eBPF sur
. Et si vous voulez plonger dans
les détails précis des choix de conception d'eBPF, je recommande
ce
document.https://ebpf.io/
Ce RFC avait fait l'objet de pas mal de débats à l'IETF car, normalement, l'IETF ne normalise pas de langages de programmation ou de jeux d'instructions. (La première réunion était à l'IETF 116 en mars 2023 donc c'est quand même allé assez vite.)
Date de publication du RFC : Octobre 2024
Auteur(s) du RFC : H. Salgado (NIC Chile), M. Vergara (DigitalOcean), D. Wessels (Verisign)
Chemin des normes
Réalisé dans le cadre du groupe de travail IETF dnsop
Première rédaction de cet article le 13 octobre 2024
Cette nouvelle option du DNS permet au client d'obtenir du serveur le numéro de version de la zone servie. (Et, non, le numéro de série dans l'enregistrement SOA ne suffit pas, lisez pour en savoir plus.)
Cela permettra de détecter les problèmes de mise à jour des serveurs faisant autorité si, par exemple, un des secondaires ne se met plus à jour. C'est surtout important pour l'anycast, qui complique le déboguage. En combinaison avec le NSID du RFC 5001, vous trouverez facilement le serveur qui a un problème. Cette nouvelle option ressemble d'ailleurs à NSID et s'utilise de la même façon.
Vous le savez, le DNS ne garantit pas que tous les serveurs faisant
autorité d'une zone servent la même version de la zone au
même moment. Si je regarde la zone
.com
à cet instant (14
juillet 2024, 09:48 UTC) avec check-soa, je
vois :
% check-soa com a.gtld-servers.net. 2001:503:a83e::2:30: OK: 1720950475 192.5.6.30: OK: 1720950475 b.gtld-servers.net. 192.33.14.30: OK: 1720950475 2001:503:231d::2:30: OK: 1720950475 c.gtld-servers.net. 2001:503:83eb::30: OK: 1720950475 192.26.92.30: OK: 1720950475 d.gtld-servers.net. 2001:500:856e::30: OK: 1720950475 192.31.80.30: OK: 1720950475 e.gtld-servers.net. 2001:502:1ca1::30: OK: 1720950475 192.12.94.30: OK: 1720950475 f.gtld-servers.net. 2001:503:d414::30: OK: 1720950475 192.35.51.30: OK: 1720950475 g.gtld-servers.net. 192.42.93.30: OK: 1720950475 2001:503:eea3::30: OK: 1720950475 h.gtld-servers.net. 192.54.112.30: OK: 1720950475 2001:502:8cc::30: OK: 1720950475 i.gtld-servers.net. 2001:503:39c1::30: OK: 1720950460 192.43.172.30: OK: 1720950475 j.gtld-servers.net. 2001:502:7094::30: OK: 1720950475 192.48.79.30: OK: 1720950475 k.gtld-servers.net. 192.52.178.30: OK: 1720950460 2001:503:d2d::30: OK: 1720950475 l.gtld-servers.net. 192.41.162.30: OK: 1720950475 2001:500:d937::30: OK: 1720950475 m.gtld-servers.net. 2001:501:b1f9::30: OK: 1720950475 192.55.83.30: OK: 1720950475
On observe que, bien que le numéro de série dans l'enregistrement SOA soit 1720950475, certains serveurs sont restés à 1720950460. Le DNS est « modérement cohérent » (RFC 3254, sur ce concept).
Dans l'exemple ci-dessus, check-soa a simplement fait une requête pour le type de données SOA (section 4.3.5 du RFC 1034). Une limite de cette méthode est que, si on observe des données d'autres types qui ne semblent pas à jour et que, pour vérifier, on fait une requête de type SOA, on n'a pas de garantie de tomber sur le même serveur, notamment en cas d'utilisation d'anycast (RFC 4786) ou bien s'il y a un répartiteur de charge avec plusieurs serveurs derrière. (D'ailleurs, dans l'exemple ci-dessus, vous avez remarqué que vous n'avez pas la même réponse en IPv4 et IPv6, probablement parce que vous arriviez sur deux instances différentes du nuage anycast.) Il faut donc un mécanisme à l'intérieur même de la requête qu'on utilise, comme pour le NSID. C'est le cas du ZONEVERSION de ce RFC, qui permet d'exprimer la version sous forme d'un numéro de série (comme avec le SOA) ou par d'autres moyens dans le futur, puisque toutes les zones n'utilisent pas le mécanisme de synchronisation habituel du DNS. On peut par exemple avoir des zones entièrement dynamiques et tirées d'une base de données, ou bien d'un calcul.
Notez aussi que certaines zones, comme
.com
, changent très
vite, et que donc, même si on tombe sur le même serveur, le numéro
de série aura pu changer entre une requête ordinaire et celle pour
le SOA. C'est une raison supplémentaire pour avoir le mécanisme
ZONEVERSION.
Bref, le nouveau mécanisme (section 2 du RFC), utilise EDNS (section 6.1.2 du RFC 6891). La nouvelle option EDNS porte le numéro 19. Encodée en TLV comme toutes les options EDNS, elle comprend une longueur et une chaine d'octets qui est la version de la zone. La longueur vaut zéro dans une requête (le client indique juste qu'il souhaite connaitre la version de la zone) et, dans la réponse, une valeur non nulle. La version est elle-même composée de trois champs :
foo.bar.example.org
et que le serveur renvoie
la version de la zone example.org
, ce champ
vaudra deux.
Si vous voulez voir dans un pcap, regardez
.zoneversion.pcap
L'option ZONEVERSION n'est renvoyée au client qui si celui-ci l'avait demandé, ce qu'il fait en mettant une option ZONEVERSION vide dans sa requête (section 3 du RFC). Si le serveur fait autorité pour la zone concernée (ou une zone ancêtre), et qu'il gère cette nouvelle option, il répond avec une valeur. Même si le nom demandé n'existe pas (réponse NXDOMAIN), l'option est renvoyée dans la réponse.
Comme les autres options EDNS, elle n'est pas signée par DNSSEC (section 8). Il n'y a donc pas de moyen de vérifier son authenticité et elle ne doit donc être utilisée qu'à titre informatif, par exemple pour le déboguage. (En outre, elle peut être modifiée en route, sauf si on utilise un mécanisme comme DoT - RFC 7858.)
Question mises en œuvre de cette option, c'est surtout du code expérimental pour l'instant, voir cette liste (pas très à jour ?). Personnellement, j'ai ajouté ZONEVERSION à Drink, et écrit un article sur l'implémentation d'options EDNS (mais notez que l'option a changé de nom et de format depuis). Notez que, contrairement à presque toutes les options EDNS, ZONEVERSION est par zone, pas par serveur, ce qui est une contrainte pour le ou la programmeureuse, qui ne peut pas choisir la valeur avant de connaitre le nom demandé. Du côté des autres logiciels, NSD a vu un patch (mais apparemment abandonné). Voici ce que voit dig actuellement (en attendant une intégration officielle de l'option) :
% dig +ednsopt=19 @ns1-dyn.bortzmeyer.fr dyn.bortzmeyer.fr SOA … ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 64655 ;; flags: qr aa rd; QUERY: 1, ANSWER: 2, AUTHORITY: 0, ADDITIONAL: 1 … ;; OPT PSEUDOSECTION: ; EDNS: version: 0, flags: do; udp: 1440 ; OPT=19: 03 00 78 a5 08 cc ("..x...") … ;; ANSWER SECTION: dyn.bortzmeyer.fr. 0 IN SOA ns1-dyn.bortzmeyer.fr. stephane.bortzmeyer.org. ( 2024081612 ; serial …
Vous noterez que "03" indique trois composants
(dyn.bortzmeyer.fr
), "00" le SOA-SERIAL, et "78
a5 08 cc" égal 2024081612. Vous pouvez aussi tester ZONEVERSION avec
le serveur de test 200.1.122.30
(un NSD
modifié), avec le domaine example.com
.
Date de publication du RFC : Septembre 2024
Auteur(s) du RFC : M. Nottingham
(Cloudflare), P-H. Kamp (The Varnish Cache
Project)
Chemin des normes
Première rédaction de cet article le 6 octobre 2024
Plusieurs en-têtes HTTP sont structurés,
c'est-à-dire que le contenu n'est pas juste une suite de caractères
mais est composé d'éléments qu'un logiciel peut analyser. C'est par
exemple le cas de Accept-Language:
ou de
Content-Disposition:
. Mais chaque en-tête ainsi
structuré a sa propre syntaxe, sans rien en commun avec les autres
en-têtes structurés, ce qui en rend l'analyse pénible. Ce nouveau
RFC (qui
remplace le RFC 8941) propose donc
des types de données et des algorithmes que les futurs en-têtes qui
seront définis pourront utiliser pour standardiser un peu l'analyse
d'en-têtes HTTP. Les en-têtes structurés anciens ne sont pas
changés, pour préserver la compatibilité. De nombreux RFC utilisent
déjà cette syntaxe (RFC 9209, RFC 9211, etc).
Imaginez : vous êtes un concepteur ou une conceptrice d'une extension au protocole HTTP qui va nécessiter la définition d'un nouvel en-tête. La norme HTTP, le RFC 9110, section 16.3.2, vous guide en expliquant ce à quoi il faut penser quand on conçoit un en-tête HTTP. Mais même avec ce guide, les pièges sont nombreux. Et, une fois votre en-tête spécifié, il vous faudra attendre que tous les logiciels, serveurs, clients, et autres (comme Varnish, pour lequel travaille un des auteurs du RFC) soient mis à jour, ce qui sera d'autant plus long que le nouvel en-tête aura sa syntaxe spécifique, avec ses amusantes particularités. Amusantes pour tout le monde, sauf pour le programmeur ou la programmeuse qui devra écrire l'analyse.
La solution est donc que les futurs en-têtes structurés réutilisent les éléments fournis par notre RFC, ainsi que son modèle abstrait, et la sérialisation proposée pour envoyer le résultat sur le réseau. Le RFC recommande d'appliquer strictement ces règles, le but étant de favoriser l'interopérabilité, au contraire du classique principe de robustesse, qui mène trop souvent à du code compliqué (et donc dangereux) car voulant traiter tous les cas et toutes les déviations. L'idée est que s'il y a la moindre erreur dans un en-tête structuré, celui-ci doit être ignoré complètement.
La syntaxe est malheureusement spécifiée sous forme d'algorithmes d'analyse. L'annexe C fournit toutefois aussi une grammaire en ABNF (RFC 5234).
Voici un exemple fictif d'en-tête structuré très simple
(tellement simple que, si tous étaient comme lui, on n'aurait sans
doute pas eu besoin de ce RFC) :
Foo-Example:
est défini comme ne prenant qu'un
élément comme valeur, un entier compris entre 0 et 10. Voici à quoi
il ressemblera en HTTP :
Foo-Example: 3
Il accepte des paramètres après un
point-virgule, un seul paramètre est défini,
foourl
dont la valeur est un URI. Cela pourrait
donner :
Foo-Example: 2; foourl="https://foo.example.com/"
Donc, ces solutions pour les en-têtes structurés ne serviront que pour les futurs en-têtes, pas encore définis, et qui seront ajoutés au registre IANA. Imaginons donc que vous soyez en train de mettre au point une norme qui inclut, entre autres, un en-tête HTTP structuré. Que devez-vous mettre dans le texte de votre norme ? La section 2 de notre RFC vous le dit :
Foo-Example:
, la valeur
était un élément, de type entier.Foo-Example:
imposait une valeur entière
entre 0 et 10.Une spécification d'un en-tête structuré ne peut que rajouter des contraintes à ce que prévoit ce RFC 9651. S'il en retirait, on ne pourrait plus utiliser du code générique pour analyser tous les en-têtes structurés.
Rappelez-vous que notre RFC est strict : si une erreur est présente
dans l'en-tête, il est ignoré. Ainsi, s'il était spécifié que la
valeur est un élément de type entier et qu'on trouve une chaîne de
caractères, on ignore l'en-tête. Idem dans l'exemple ci-dessus si on
reçoit Foo-Example: 42
, la valeur excessive
mène au rejet de l'en-tête.
Les valeurs peuvent inclure des paramètres (comme le
foourl
donné en exemple plus haut), et le RFC
recommande d'ignorer les paramètres inconnus, afin de permettre
d'étendre leur nombre sans tout casser.
On a vu qu'une des plaies du Web était le laxisme trop grand dans l'analyse des données reçues (c'est particulièrement net pour HTML). Mais on rencontre aussi des cas contraires, des systèmes (par exemple les pare-feux applicatifs) qui, trop fragiles, chouinent lorsqu'ils rencontrent des cas imprévus, parce que leurs auteurs avaient mal lu le RFC. Cela peut mener à l'ossification, l'impossibilité de faire évoluer l'Internet parce que des nouveautés, pourtant prévues dès l'origine, sont refusées. Une solution récente est le graissage, la variation délibérée des messages pour utiliser toutes les possibilités du protocole. (Un exemple pour TLS est décrit dans le RFC 8701.) Cette technique est recommandée par notre RFC.
La section 3 du RFC décrit ensuite les types qui sont les briques de base avec lesquelles on va pouvoir définir les en-têtes structurés. La valeur d'un en-tête peut être une liste, un dictionnaire ou un élément. Les listes et les dictionnaires peuvent à leur tour contenir des listes. Une liste est une suite de termes qui, dans le cas de HTTP, sont séparés par des virgules :
ListOfStrings-Example: "foo", "bar", "It was the best of times."
Et si les éléments d'une liste sont eux-mêmes des listes, on met ces listes internes entre parenthèses (et notez la liste vide à la fin, et l'espace comme séparateur) :
ListOfListsOfStrings-Example: ("foo" "bar"), ("baz"), ("bat" "one"), ()
Un en-tête structuré dont la valeur est une liste a toujours une valeur, la liste vide, même si l'en-tête est absent.
Un dictionnaire est une suite de termes nom=valeur. En HTTP, cela donnera :
Dictionary-Example: en="Applepie", fr="Tarte aux pommes"
Et nous avons déjà vu les éléments simples dans le premier
exemple. Les éléments peuvent être de type
entier,
chaîne de
caractères, booléen,
identificateur, valeur binaire, date, et un dernier type plus exotique
(lisez le RFC pour en savoir plus). L'exemple avec
Foo-Example:
utilisait un entier. Les exemples
avec listes et dictionnaires se servaient de chaînes de
caractères. Ces chaînes sont encadrées de
guillemets (pas
d'apostrophes).
Compte-tenu des analyseurs HTTP
existants, les chaînes de caractères ordinaires
doivent être en ASCII (RFC 20). Si on veut envoyer de l'Unicode, il faut utiliser
un autre type, Display String. Quant aux booléens, notez qu'il faut
écrire 1 et 0 (pas true
et
false
), et préfixé d'un point
d'interrogation.
Toutes ces valeurs peuvent prendre des paramètres, qui sont eux-mêmes une suite de couples clé=valeur, après un point-virgule qui les sépare de la valeur principale (ici, on a deux paramètres) :
ListOfParameters: abc;a=1;b=2
J'ai dit au début que ce RFC définit un modèle abstrait, et une sérialisation concrète pour HTTP. La section 4 du RFC spécifie cette sérialisation, et son inverse, l'analyse des en-têtes (pour les programmeur·ses seulement).
Ah, et si vous êtes fana de formats structurés, ne manquez pas l'annexe A du RFC, qui répond à la question que vous vous posez certainement depuis plusieurs paragraphes : pourquoi ne pas avoir tout simplement décidé que les en-têtes structurés auraient des valeurs en JSON (RFC 8259), ce qui évitait d'écrire un nouveau RFC, et permettait de profiter du code JSON existant ? Le RFC estime que le fait que les chaînes de caractères JSON soient de l'Unicode est trop risqué, par exemple pour l'interopérabilité. Autre problème de JSON, les structures de données peuvent être emboîtées indéfiniment, ce qui nécessiterait des analyseurs dont la consommation mémoire ne peut pas être connue et limitée. (Notre RFC permet des listes dans les listes mais cela s'arrête là : on ne peut pas poursuivre l'emboîtement.)
Une partie des problèmes avec JSON pourrait se résoudre en se limitant à un profil restreint de JSON, par exemple en utilisant le RFC 7493 comme point de départ. Mais, dans ce cas, l'argument « on a déjà un format normalisé, et mis en œuvre partout » tomberait.
Enfin, l'annexe A note également qu'il y avait des considérations d'ordre plutôt esthétiques contre JSON dans les en-têtes HTTP.
Toujours pour les programmeur·ses, l'annexe B du RFC donne quelques conseils pour les auteur·es de bibliothèques mettant en œuvre l'analyse d'en-têtes structurés. Pour les aider à suivre ces conseils, une suite de tests est disponible. Quant à une liste de mises en œuvre du RFC, vous pouvez regarder celle-ci.
Actuellement, il y a dans le registre
des en-têtes plusieurs en-têtes structurés, comme le
Accept-CH:
du RFC 8942 (sa valeur est une liste d'identificateurs), le
Cache-Status:
du RFC 9211
ou le Client-Cert:
du RFC 9440.
Si vous voulez un exposé synthétique sur ces en-têtes structurés, je vous recommande cet article par un des auteurs du RFC.
Voyons maintenant un peu de pratique avec une des mises en œuvre citées plus haut, http_sfv, une bibliothèque Python. Une fois installée :
% python3 >>> import http_sfv >>> my_item=http_sfv.Item() >>> my_item.parse(b"2") >>> print(my_item) 2
On a analysé la valeur « 2 » en déclarant que cette valeur devait être un élément et, pas de surprise, ça marche. Avec une valeur syntaxiquement incorrecte :
>>> my_item.parse(b"2, 3") Traceback (most recent call last): File "<stdin>", line 1, in <module> File "/home/stephane/.local/lib/python3.12/site-packages/http_sfv/util.py", line 61, in parse raise ValueError("Trailing text after parsed value") ValueError: Trailing text after parsed value
Et avec un paramètre (il sera accessible après l'analyse, via le
dictionnaire Python params
) :
>>> my_item.parse(b"2; foourl=\"https://foo.example.com/\"") >>> print(my_item.params['foourl']) https://foo.example.com/
Avec une liste :
>>> my_list=http_sfv.List() >>> my_list.parse(b"\"foo\", \"bar\", \"It was the best of times.\"") >>> print(my_list) "foo", "bar", "It was the best of times."
Et avec un dictionnaire :
>>> my_dict=http_sfv.Dictionary() >>> my_dict.parse(b"en=\"Applepie\", fr=\"Tarte aux pommes\"") >>> print(my_dict) en="Applepie", fr="Tarte aux pommes" >>> print(my_dict["fr"]) "Tarte aux pommes"
L'annexe D résume les principaux changements depuis le RFC 8941. Rien de trop crucial, à part le fait que l'ABNF est reléguée à une annexe. Sinon, il y a deux nouveaux types de base, pour les dates et les chaines de caractères en Unicode (Display Strings).
Date de publication du RFC : Novembre 2024
Auteur(s) du RFC : J. Zern, P. Massimino, J. Alakuijala
(Google)
Pour information
Première rédaction de cet article le 19 novembre 2024
Un RFC sur un
format d'image : il décrit le format WebP et
enregistre officiellement le type image/webp
.
Bon, des images au format WebP, vous en avez forcément vu un peu partout sur le Web. Mais le format n'était pas encore documenté par un organisme de normalisation. C'est désormais fait. WebP s'appuie sur le format RIFF. Plus précisément, RIFF est un cadre générique qui peut se décliner en divers formats. C'est pour cela que les fichiers WebP commencent par les quatre codes ASCII correspondant à "RIFF".
WebP permet de la compression avec perte ou sans perte, et, d'une manière générale, tout ce qu'on attend d'un format graphique. C'est donc un concurrent de JPEG, PNG (RFC 2083), GIF… L'encodage lors de la compression avec perte est celui de VP8 (RFC 6386), produisant des images plus petites que ses prédécesseurs, ce qui est bon pour le réseau. La compression sans perte est faite en LZ77 et Huffman. WebP permet également le stockage de métadonnées codées en EXIF ou en XMP. Ah, et WebP permet des animations.
Cette image, sur un sujet politique qui est toujours d'actualité, est au format WebP :
La section 2 du RFC décrit le format en détail. Les trois premiers champs sont la chaine "RIFF", la taille du fichier en binaire et la chaine "WEBP", chacun sur quatre octets. Affichons ces trois champs :
% dd if=how-do-you-like-it-wrapped.webp bs=4 count=3 RIFFXWEBP 3+0 records in 3+0 records out 12 bytes copied…
La section 3 décrit ensuite le stockage des pixels sans perte.
Comme avec tout format, les logiciels qui lisent les fichiers WebP (qu'on télécharge souvent depuis l'Internet, via des sources pas forcément de confiance) doivent être prudents dans l'analyse des fichiers. Les fichiers peuvent être invalides, par accident ou délibérément, et mener le logiciel négligent à lire en dehors d'un tableau ou déréférencer un pointeur invalide. La paranoïa est recommandée quand on lit ces fichiers. Plusieurs failles ont touché la libwebp, l'implémentation de référence, dont la grave CVE-2023-4863 en 2023 qui avait fait, à juste titre, beaucoup de bruit.
Toujours question sécurité, WebP n'a pas de contenu exécutable (contrairement à, par exemple, TrueType). Mais les métadonnées EXIF ou en XMP peuvent poser des problèmes de sécurité et doivent donc être interprétées avec prudence.
Le type
image/webp
est désormais dans
le registre des types de médias (cf. le
formulaire rempli).
Si on regarde des fichiers WebP, on a ce genre d'informations :
% file resolution-dns.webp resolution-dns.webp: RIFF (little-endian) data, Web/P image % file --mime-type resolution-dns.webp resolution-dns.webp: image/webp
Pour finir, produisons un peu d'images WebP pour voir. Si on utilise Asymptote, c'est facile :
% asy -f webp -o resolution-dns.webp resolution-dns.asy
On peut aussi convertir depuis les formats existants, ici avec ImageMagick :
% convert -verbose resolution-dns.png resolution-dns.webp resolution-dns.png PNG 823x508 823x508+0+0 8-bit sRGB 31713B 0.020u 0:00.011 resolution-dns.png=>resolution-dns.webp PNG 823x508 823x508+0+0 8-bit sRGB 16580B 0.040u 0:00.047
Si on fait des graphiques depuis ses programmes en Python avec Matplotlib, il suffit, depuis la version 3.6, de :
plot.savefig(fname="test.webp") # No need to indicate the format, it # is taken from the file extension.
(Voir aussi ce beau résultat sur Wikimedia Commons.)
Date de publication du RFC : Décembre 2024
Auteur(s) du RFC : M.Q.C. van
Beurden, A. Weaver
Chemin des normes
Réalisé dans le cadre du groupe de travail IETF cellar
Première rédaction de cet article le 19 décembre 2024
Le format FLAC, normalisé dans ce RFC, permet de stocker et de transmettre du son sans perte (contrairement à des mécanismes de compression comme MP3). Il est donc utile, par exemple pour l'archivage à long terme (mais aussi pour la haute fidélité).
Le multimédia, c'est compliqué. Le RFC fait près de 100 pages, pour un format audio qui se veut pourtant simple. FLAC fait de la compression pour limiter l'espace de stockage nécessaire mais en prenant soin de permettre une décompression qui redonne exactement le fichier originel (d'où le « sans perte » dans son nom, qu'il partage avec d'autres formats, comme le FFV1 du RFC 9043). Je ne connais pas assez le domaine de l'audio pour faire un résumé intelligent du RFC donc je vous invite à le lire si vous voulez comprendre comment FLAC fonctionne (les sections 4 à 6 vous font une description de haut niveau).
Si on prend un fichier FLAC, un programme comme file peut le comprendre :
% file Thélème.flac Thélème.flac: FLAC audio bitstream data, 24 bit, stereo, 44.1 kHz, length unknown
Le fait que l'échantillonage soit à 44,1 kHz est encodé dans les métadonnées (cf. section 9.1.2 du RFC), que file a pu lire (section 9.1.3 pour le fait que le fichier soit en stéréo).
Il y a quelques fichiers FLAC sur Wikimedia Commons. Mais un bon exemple de l'utilisation de FLAC est donné par l'enregistrement des Variations Goldberg par Kimiko Ishizaka, que vous pouvez télécharger en FLAC et sous une licence libre (CC0).
La mise en œuvre de référence de FLAC est en logiciel libre et se nomme libFLAC. Mais il existe beaucoup d'autres programmes qui savent gérer FLAC (j'ai écouté plusieurs fichiers FLAC avec vlc pour cet article), le lire, l'écrire, l'analyser, etc. Une liste est disponible sur le site du groupe de travail IETF. Il faut dire que FLAC, bien qu'il vienne seulement d'être normalisé, est un format ancien, dont le développement a commencé en 2000, et il n'est donc pas étonnant que les programmeur·ses aient eu le temps de travailler.
Si vous voulez ajouter du code à cette liste, lisez bien la section 11 du RFC, sur la sécurité. Un programme qui lit du FLAC doit être paranoïaque (comme avec n'importe quel autre format, bien sûr) et se méfier des cas pathologiques. Ainsi, le RFC note qu'on peut facilement créer un fichier FLAC de 49 octets qui, décomprimé, ferait 2 mégaoctets, ce qui pourrait dépasser la mémoire qui avait été réservée. Ce genre de surprises peut arriver à plusieurs endroits. L'annexe D du RFC contient plusieurs fichiers FLAC intéressants (et vous pouvez les retrouver en ligne) et il existe également une collection de fichiers FLAC, dont certains sont délibérement anormaux, pour que vous puissiez tester la robustesse de votre programme. Autre piège (mais lisez toute la section 11, il y en a beaucoup), FLAC permet de mettre des URL dans les fichiers, URL qu'il ne faut évidemment pas déréférencer bêtement.
Ce RFC est également la documentation pour l'enregistrement
du type MIME audio/flac
(section 12.1).
Pour une analyse technique de la prétention « sans perte » de FLAC, voir cet article de Guillaume Seznec.
Date de publication du RFC : Août 2024
Auteur(s) du RFC : G. Huston (APNIC), N. Buraglio (Energy Sciences Network)
Pour information
Réalisé dans le cadre du groupe de travail IETF v6ops
Première rédaction de cet article le 28 août 2024
Le préfixe IPv6 normalisé pour les
documentations, 2001:db8::/32
était trop petit
pour vous ? Vous aviez du mal à exprimer des architectures réseau
complexes, avec beaucoup de préfixes ? Ne pleurez plus, un nouveau
préfixe a été alloué, c'est désormais un /20, le 3fff::/20
.
Ce RFC modifie
légèrement le RFC 3849, qui normalisait ce
préfixe de documentation. Le but d'un préfixe IP de documentation est
d'éviter que les auteur·es de ces documentations ne prennent des
adresses IP qui existent
par ailleurs, au risque que des administrateurices
réseaux maladroit·es ne copient ces adresses IP (songez
au nombre d'articles qui parlent d'IPv4 en
utilisant des exemples comme les adresses
1.1.1.1
ou 1.2.3.4
, qui
existent réellement). On doit donc utiliser les noms de domaine du RFC 2606, les adresses IPv4 du RFC 5737, et les numéros d'AS du RFC 5398. Pour IPv6, l'espace de
documentation est désormais 3fff::/20
(l'ancien
préfixe 2001:db8::/32
reste réservé et valable donc pas besoin de modifier les
documentations existantes).
Cette nouvelle taille permet de documenter des réseaux réalistes,
par exemple où deux /32
se parlent.
Si ce préfixe est désormais dans le registre des adresses spéciales, il ne semble pas (encore ?) décrit dans la base d'un RIR, contrairement à son prédécesseur.
Date de publication du RFC : Octobre 2024
Auteur(s) du RFC : A.D. Olson, P. Eggert
(UCLA), K. Murchison (Fastmail)
Chemin des normes
Première rédaction de cet article le 31 octobre 2024
Ce nouveau RFC
documente un format déjà ancien et largement déployé, TZif, un
format de description des fuseaux
horaires. Il définit également des types MIME pour ce format,
application/tzif
et
application/tzif-leap
. Il remplace le premier
RFC de normalisation de ce format, le RFC 8536, mais il y a très peu de changements. Bienvenue donc
à la version 4 du format, spécifiée dans ce RFC.
Ce format existe depuis quarante ans (et a pas mal évolué pendant ce temps) mais n'avait apparemment jamais fait l'objet d'une normalisation formelle avant le RFC 8536 en 2019. La connaissance des fuseaux horaires est indispensable à toute application qui va manipuler des dates, par exemple un agenda. Un fuseau horaire se définit par un décalage par rapport à UTC, les informations sur l'heure d'été, des abréviations pour désigner ce fuseau (comme CET pour l'heure de l'Europe dite « centrale ») et peut-être également des informations sur les secondes intercalaires. Le format iCalendar du RFC 5545 est un exemple de format décrivant les fuseaux horaires. TZif, qui fait l'objet de ce RFC, en est un autre. Contrairement à iCalendar, c'est un format binaire.
TZif vient à l'origine du monde Unix et est apparu dans les années 1980, quand le développement de l'Internet, qui connecte des machines situées dans des fuseaux horaires différents, a nécessité que les machines aient une meilleure compréhension de la date et de l'heure. Un exemple de source faisant autorité sur les fuseaux horaires est la base de l'IANA décrite dans le RFC 6557 et dont l'usage est documenté à l'IANA. Pour la récupérer, voir par exemple le RFC 7808.
La section 2 de notre RFC décrit la terminologie du domaine :
time()
, à savoir le nombre de
secondes depuis l'epoch, donc depuis le 1
janvier 1970. Il ne tient pas compte des secondes intercalaires,
donc il existe aussi un « temps Unix avec secondes intercalaires »
(avertissement : tout ce qui touche au temps et aux calendriers
est compliqué).
C'est ce dernier qui est utilisé dans le format TZif, pour
indiquer les dates et heures des moments où se fait une transition
entre heure d'hiver et heure d'été.La section 3 de notre RFC décrit le format lui-même. Un fichier TZif est composé d'un en-tête (taille fixe de 44 octets) indiquant entre autres le numéro de version de TZif. La version actuelle est 4. Ensuite, on trouve les données. Dans la version 1 de TZif, le bloc de données indiquait les dates de début et de fin des passages à l'heure d'été sur 32 bits, ce qui les limitait aux dates situées entre 1901 et 2038. Les versions ultérieures de TZif sont passées à 64 bits, ce qui permet de tenir environ 292 milliards d'années mais le bloc de données de la version 1 reste présent, au cas où il traine encore des logiciels ne comprenant que la version 1. Notez que ces 64 bits permettent de représenter des dates antérieures au Big Bang, mais certains logiciels ont du mal avec des valeurs situées trop loin dans le passé.
Les versions 2, 3 et 4 ont un second en-tête de 44 octets, et un bloc de données à elles. Les vieux logiciels arrêtent la lecture après le premier bloc de données et ne sont donc normalement pas gênés par cet en-tête et ce bloc supplémentaires. Les logiciels récents peuvent sauter le bloc de données de la version 1, qui ne les intéresse a priori pas (voir section 4 et annexe A). C'est au créateur du fichier de vérifier que les blocs de données destinés aux différentes versions sont raisonnablement synchrones, en tout cas pour les dates antérieures à 2038.
Nouveauté apparue avec la version 2, il y a aussi un pied de page à
la fin. Les entiers sont stockés en gros boutien, et en complément à deux. L'en-tête
commence par la chaîne magique « TZif » (U+0054 U+005A U+0069
U+0066), et comprend la longueur du bloc de données (qui dépend du
nombre de transitions, de secondes intercalaires et d'autres
informations à indiquer). Le bloc de données contient la liste des
transitions, le décalage avec UT, le nom du fuseau horaire, la liste des
secondes intercalaires, etc. Vu par le mode hexadécimal
d'Emacs, voici le début d'un fichier Tzif
version 2 (pris sur une Ubuntu, dans
/usr/share/zoneinfo/Europe/Paris
). On voit bien
la chaîne magique, puis le numéro de version, et le début du bloc de
données :
00000000: 545a 6966 3200 0000 0000 0000 0000 0000 TZif2........... 00000010: 0000 0000 0000 000d 0000 000d 0000 0000 ................ 00000020: 0000 00b8 0000 000d 0000 001f 8000 0000 ................ 00000030: 9160 508b 9b47 78f0 9bd7 2c70 9cbc 9170 .`P..Gx...,p...p 00000040: 9dc0 48f0 9e89 fe70 9fa0 2af0 a060 a5f0 ..H....p..*..`.. ...
Avec od, ça donnerait :
% od -x -a /usr/share/zoneinfo/Europe/Paris 0000000 5a54 6669 0032 0000 0000 0000 0000 0000 T Z i f 2 nul nul nul nul nul nul nul nul nul nul nul 0000020 0000 0000 0000 0d00 0000 0d00 0000 0000 nul nul nul nul nul nul nul cr nul nul nul cr nul nul nul nul 0000040 0000 b800 0000 0d00 0000 1f00 0080 0000 nul nul nul 8 nul nul nul cr nul nul nul us nul nul nul nul 0000060 6091 8b50 479b f078 d79b 702c bc9c 7091 dc1 ` P vt esc G x p esc W , p fs < dc1 p ...
Des exemples détaillés et commentés de fichiers TZif figurent en annexe B. À lire si vous voulez vraiment comprendre les détails du format.
Le pied de page indique notamment les extensions à la variable d'environnement TZ. Toujours avec le mode hexadécimal d'Emacs, ça donne :
00000b80: 4345 542d 3143 4553 542c 4d33 2e35 2e30 CET-1CEST,M3.5.0 00000b90: 2c4d 3130 2e35 2e30 2f33 0a ,M10.5.0/3.
On a vu que le format TZif avait une histoire longue et compliquée. La section 4 du RFC est entièrement consacrée aux problèmes d'interopérabilité, liés à la coexistence de plusieurs versions du format, et de beaucoup de logiciels différents. Le RFC conseille (sections 4 et 5) :
application/tzif-leap
ou
application/tzif
(s'il n'indique pas les
secondes intercalaires). Ces types MIME
sont désormais dans le registre
officiel (cf. section 9 du RFC).L'annexe A du RFC en rajoute, tenant compte de l'expérience accumulée ; par exemple, certains lecteurs de TZif n'acceptent pas les noms de fuseaux horaires contenant des caractères non-ASCII et il peut donc être prudent de ne pas utiliser ces caractères. Plus gênant, il existe des lecteurs assez bêtes pour planter lorsque des temps sont négatifs. Or, les entiers utilisant dans TZif sont signés, afin de pouvoir représenter les moments antérieurs à l'epoch. Donc, attention si vous avez besoin de données avant le premier janvier 1970, cela perturbera certains logiciels bogués.
En parlant des noms de fuseaux horaires, le RFC rappelle (section 5) que le sigle utilisé dans le fichier Tzif (comme « CET ») est en anglais et que, si on veut le traduire, cela doit être fait dans l'application, pas dans le fichier. Le RFC donne comme exemple CST, qui peut être présenté comme « HNC », pour « Heure Normale du Centre ».
Autre piège avec ces sigles utilisés pour nommer les fuseaux horaires : ils sont ambigus. PST peut être la côte Ouest des USA mais aussi l'heure du Pakistan ou des Philippines.
La section 7 du RFC donne quelques conseils de sécurité :
Enfin, l'annexe C du RFC liste les changements depuis le RFC 8536, changements qui mènent à cette nouvelle version, la 4. Rien de crucial mais :
Une bonne liste de logiciels traitant ce format figure à l'IANA.
Date de publication du RFC : Septembre 2024
Auteur(s) du RFC : G. Grover, N. ten Oever (University of Amsterdam)
Pour information
Réalisé dans le cadre du groupe de recherche IRTF hrpc
Première rédaction de cet article le 17 septembre 2024
Voici un RFC explicitement politique, puisqu'il documente la façon dont les concepteur·rices de protocoles à l'IETF devraient examiner les conséquences de leurs protocoles sur l'exercice des droits humains. Si vous concevez un protocole réseau (IETF ou pas), c'est une lecture recommandée.
Les protocoles ne sont pas neutres puisqu'ils ont des conséquences concrètes sur les utilisateurices, conséquences positives ou négatives. S'ils n'en avaient pas, on ne passerait pas du temps à les développer, et on ne dépenserait pas d'argent à les déployer. Ces conséquences ne sont pas forcément faciles à déterminer, surtout avant tout déploiement effectif, mais ce RFC peut guider la réflexion et permettre d'identifier les points qui peuvent avoir des conséquences néfastes. Il comprend de nombreux exemples tirés de précédents RFC. Il met à jour partiellement le RFC 8280, dont il reprend la section 6, et s'inspire de la méthode du RFC 6973, qui documentait les conséquences des protocoles sur la vie privée.
La question est évidemment complexe. Les protocoles n'ont pas forcément un pouvoir direct sur ce que les utilisateurices peuvent faire ou pas (c'est l'argument central de ceux qui estiment que les techniques sont neutres : HTTP transférera aussi bien un article de la NASA qu'un texte complotiste sur les extra-terrestres). Leur rôle est plutôt indirect, en ce qu'ils encouragent ou découragent certaines choses, plutôt que d'autoriser ou interdire. Et puis, comme le note le RFC, cela dépend du déploiement. Par exemple, pour le courrier électronique, des faits politiques importants ne s'expliquent pas par le protocole. Ce ne sont pas les particularités de SMTP (RFC 5321) qui expliquent la domination de Gmail par exemple. Il ne faut en effet pas tomber dans le déterminisme technologique (comme le font par exemple les gens qui critiquent DoH) : l'effet dans le monde réel d'un protocole dépend de bien d'autres choses que le protocole.
Ah, et autre point dans l'introduction, la définition des droits humains. Notre RFC s'appuie sur la déclaration universelle des droits humains et sur d'autres documents comme le International Covenant on Economic, Social and Cultural Rights.
Parmi les droits humains cités dans cette déclaration universelle, nombreux sont ceux qui peuvent être remis en cause sur l'Internet : liberté d'expression, liberté d'association, droit à la vie privée, non-discrimination, etc. L'absence d'accès à l'Internet mène également à une remise en cause des droits humains, par exemple parce que cela empêche les lanceurs d'alerte de donner l'alerte. Les atteintes aux droits humains peuvent être directes (censure) ou indirectes (la surveillance des actions peut pousser à l'auto-censure, et c'est souvent le but poursuivi par les acteurs de la surveillance ; cf. « Chilling Effects: Online Surveillance and Wikipedia Use »). Et les dangers pour les individus ne sont pas seulement « virtuels », ce qui se passe en ligne a des conséquences physiques quand, par exemple, une campagne de haine contre des gens accusés d'attaques contre une religion mène à leur assassinat, ou quand un État emprisonne ou tue sur la base d'informations récoltées en ligne.
C'est pour cela que, par exemple, le conseil des droits humains de l'ONU mentionne que les droits qu'on a dans le monde physique doivent aussi exister en ligne. [La propagande des médias et des politiciens en France dit souvent que « l'Internet est une zone de non-droit » et que « ce qui est interdit dans le monde physique doit aussi l'être en ligne », afin de justifier des lois répressives. Mais c'est une inversion complète de la réalité. En raison des particularités du numérique, notamment la facilité de la surveillance de masse, et de l'organisation actuelle du Web, avec un petit nombre d'acteurs médiant le trafic entre particuliers, les droits humains sont bien davantage menacés en ligne.] Sur la question de l'application des droits humains à l'Internet, on peut aussi lire les « 10 Internet Rights & Principles> » et le « Catalog of Human Rights Related to ICT Activities ».
Comme les droits humains sont précieux, et sont menacés sur l'Internet, l'IETF doit donc veiller à ce que son travail, les protocoles développés, n'aggravent pas la situation. D'où la nécessité, qui fait le cœur de ce RFC, d'examiner les protocoles en cours de développement. Ces examens (reviews, section 3 du RFC) devraient être systématiques et, évidemment, faits en amont, pas une fois le protocole déployé. [En pratique, ces examens ont assez vite été arrêtés, et ce RFC ne reflète donc pas la situation actuelle.]
Vu la façon dont fonctionne l'IETF, il n'y a pas besoin d'une autorité particulière pour effectuer ces examens. Tout·e participant·e à l'IETF peut le faire. Ce RFC 9620 vise à guider les examinateurs (reviewers). L'examen peut porter sur le contenu d'un Internet Draft mais aussi être complété, par exemple, avec des interviews d'experts de la question (les conséquences de tel ou tel paragraphe dans un Internet Draft ne sont pas forcément évidentes à la première lecture, ou même à la seconde) mais aussi des gens qui seront affectés par le protocole en question. Si un futur RFC parle d'internationalisation, par exemple, il ne faut pas interviewer que des anglophones, et pas que des participants à l'IETF, puisque l'internationalisation concerne tout le monde.
Une grosse difficulté, bien sûr, est que le protocole n'est pas tout. Les conditions effectives de son déploiement, et son évolution dans le temps, sont cruciales. Ce n'est pas en lisant le RFC 5733 (ou le RFC 9083) qu'on va tout comprendre sur les enjeux de la protection des données personnelles des titulaires de noms de domaine ! Le RFC 8980 discute d'ailleurs de cette importante différence entre le protocole et son déploiement.
La plus importante section de notre RFC est sans doute la section 4, qui est une sorte de check-list pour les auteur·es de protocoles et les examinateurices. Idéalement, lors de la phase de conception d'un protocole, il faudrait passer toutes ces questions en revue. Bien évidemment, les réponses sont souvent complexes : la politique n'est pas un domaine simple.
Premier exemple (je ne vais pas tous les détailler, rassurez-vous), les intermédiaires. Est-ce que le protocole permet, voire impose, des intermédiaires dans la communication ? C'est une question importante car ces intermédiaires, s'ils ne sont pas sous le contrôle des deux parties qui communiquent, peuvent potentiellement surveiller la communication (risque pour la confidentialité) ou la perturber (risque pour la liberté de communication). Un exemple est l'interception HTTPS (cf. « The Security Impact of HTTPS Interception »). Le principe de bout en bout (RFC 1958 ou bien « End-to-End Arguments in System Design ») promeut plutôt une communication sans intermédiaires, mais on trouve de nombreuses exceptions dans les protocoles IETF (DNS, SMTP…) ou en dehors de l'IETF (ActivityPub), car les intermédiaires peuvent aussi rendre des services utiles. En outre, ces intermédiaires tendent à ossifier le protocole, en rendant plus difficile le déploiement de tout changement (cf. RFC 8446 pour les problèmes rencontrés par TLS 1.3).
Le RFC fait aussi une différence entre intermédiaires et services. Si vous êtes utilisateurice de Gmail, Gmail n'est pas un intermédiaire, mais un service car vous êtes conscient·e de sa présence et vous l'avez choisi. [L'argument me semble avoir des faiblesses : ce genre de services pose exactement les mêmes problèmes que les intermédiaires et n'est pas forcément davantage maitrisé.]
Un bon moyen de faire respecter le principe de bout en bout est
de chiffrer au
maximum. C'est pour cela que QUIC (RFC 9000) chiffre davantage de données que
TLS. C'est aussi pour cela que l'IETF
travaille au chiffrement du SNI (draft-ietf-tls-esni
).
Autre exemple, la connectivité (section 4.2). Car, si on n'a pas accès à l'Internet du tout, ou bien si on y a accès dans des conditions très mauvaises, tout discussion sur les droits humains sur l'Internet devient oiseuse. L'accès à l'Internet est donc un droit nécessaire (cf. la décision du Conseil Constitutionnel sur Hadopi, qui posait le principe de ce droit d'accès à l'Internet). Pour le protocole, cela oblige à se pencher sur son comportement sur des liens de mauvaise qualité : est-ce que ce protocole peut fonctionner lorsque la liaison est mauvaise ?
Un sujet délicat (section 4.4) est celui des informations que le protocole laisse fuiter (l'« image vue du réseau » du RFC 8546). Il s'agit des données qui ne sont pas chiffrées, même avec un protocole qui fait de la cryptographie, et qui peuvent donc être utilisées par le réseau, par exemple pour du traitement différencié (de la discrimination, pour dire les choses franchement). Comme recommandé par le RFC 8558, tout protocole doit essayer de limiter cette fuite d'informations. C'est pour cela que TCP a tort d'exposer les numéros de port, par exemple, que QUIC va au contraire dissimuler.
L'Internet est mondial, on le sait. Il est utilisé par des gens qui ne parlent pas anglais et/ou qui n'utilisent pas l'alphabet latin. Il est donc crucial que le protocole fonctionne pour tout le monde (section 4.5). Si le protocole utilise des textes en anglais, cela doit être de manière purement interne (le GET de HTTP, le Received: de l'IMF, etc), sans être obligatoirement montré à l'utilisateurice. Ce principe, formulé dans le RFC 2277, dit que tout ce qui est montré à l'utilisateur doit pouvoir être traduit. (En pratique, il y a des cas limites, comme les noms de domaine, qui sont à la fois éléments du protocole, et montrés aux utilisateurs.)
Si le protocole sert à transporter du texte, il doit évidemment utiliser Unicode, de préférence encodé en UTF-8. S'il accepte d'autres encodages et/ou d'autres jeux de caractère (ce qui peut être dangereux pour l'interopérabilité), il doit permettre d'étiqueter ces textes, afin qu'il n'y ait pas d'ambiguité sur leurs caractéristiques. Pensez d'ailleurs à lire le RFC 6365.
Un contre-exemple est le vieux protocole whois (RFC 3912), qui ne prévoyait que l'ASCII et, si on peut l'utiliser avec d'autres jeux de caractères, comme il ne fournit pas d'étiquetage, le client doit essayer de deviner de quoi il s'agit. (Normalement, whois n'est plus utilisé, on a le Web et RDAP, mais les vieilles habitudes ont la vie dure.)
Toujours question étiquetage, notre RFC rappelle l'importance de pouvoir, dans le protocole, indiquer explicitement la langue des textes (RFC 5646). C'est indispensable afin de permettre aux divers logiciels de savoir quoi en faire, par exemple en cas de synthèse vocale.
Le RFC parle aussi de l'élaboration des normes techniques (section 4.7). Par exemple, sont-elles dépendantes de brevets (RFC 8179 et RFC 6701) ? [Personnellement, je pense que c'est une question complexe : les brevets ne sont valables que dans certains pays et, en outre, la plupart des brevets logiciels sont futiles, brevetant des technologies banales et déjà connues. Imposer, comme le proposent certains, de ne normaliser que des techniques sans brevet revient à donner un doit de veto à n'importe quelle entreprise qui brevète n'importe quoi. Par exemple, le RFC 9156 n'aurait jamais été publié si on s'était laissé arrêter par le brevet.]
Mais un autre problème avec les normes techniques concerne leur disponibilité. Si l'IETF, le W3C et même l'UIT publient leurs normes, ce n'est pas le cas de dinosaures comme l'AFNOR ou l'ISO, qui interdisent même la redistribution de normes qu'on a légalement acquises. Si les normes IETF sont de distribution libre, elles dépendent parfois d'autres normes qui, elles, ne le sont pas.
Un peu de sécurité informatique, pour continuer. La section 4.11 traite de l'authentification des données (ce que fait DNSSEC pour le DNS, par exemple). Cette possibilité d'authentification est évidemment cruciale pour la sécurité mais le RFC note qu'elle peut aussi être utilisée négativement, par exemple avec les menottes numériques.
Et il y a bien sûr la confidentialité (section 4.12 mais aussi RFC 6973), impérative depuis toujours mais qui était parfois sous-estimée, notamment avant les révélations Snowden. Les auteur·es de protocoles doivent veiller à ce que les données soient et restent confidentielles et ne puissent pas être interceptées par un tiers. Il y a longtemps que tout RFC doit contenir une section sur la sécurité (RFC 3552), exposant les menaces spécifiques à ce RFC et les contre-mesures prises, entre autre pour assurer la confidentialité. L'IETF refuse, à juste titre, toute limitation de la cryptographie, souvent demandée par les autorités (RFC 1984). Les exigences d'accès par ces autorités (en invoquant des arguments comme la lutte contre le terrorisme ou la protection de l'enfance) ne peuvent mener qu'à affaiblir la sécurité générale puisque ces accès seront aussi utilisés par les attaquants, ou par un État qui abuse de son pouvoir.
Le modèle de menace de l'Internet, depuis longtemps, est que tout ce qui est entre les deux machines situées aux extrémités de la communication doit être considéré comme un ennemi. Pas parce que les intermédiaires sont forcément méchants, loin de là, mais parce qu'ils ont des possibiliés techniques que certains exploiteront et il faut donc protéger la communication car on ne sait jamais ce que tel ou tel intermédiaire fera (RFC 7258 et RFC 7624). Bref, tout protocole doit chiffrer le contenu qu'il transporte (RFC 3365). Aujourd'hui, les principales exceptions à ce principe sont le très vieil whois (RFC 3912) et surtout le DNS qui a, certes, des solutions techniques pour le chiffrement (RFC 7858 et RFC 8484) mais qui sont loin d'être universellement déployées.
Ce chiffrement doit évidemment être fait de bout en bout, c'est-à-dire directement entre les deux machines qui communiquent, afin d'éviter qu'un intermédiaire n'ait accès à la version en clair. Cela pose un problème pour les services store-and-forward comme le courrier électronique (RFC 5321). De même, chiffrer lorsqu'on communique en HTTPS avec Gmail ne protège pas la communication contre Google, seulement contre les intermédiaires réseau ! Relire le RFC 7624 est recommandé.
Question vie privée, le RFC recommande également de faire attention aux métadonnées et à l'analyse de trafic. Les conseils du RFC 6973, section 7, sont ici utiles.
Un sujet encore plus délicat est celui de l'anonymat et du pseudonymat. On sait qu'il n'y a pas réellement d'anonymat sur l'Internet (quoiqu'en disent les politiciens malhonnêtes et les journalistes avides de sensationnalisme), le numérique permettant au contraire de récolter et de traiter beaucoup de traces de la communication. Néanmoins, le protocole doit permettre, autant que possible, de s'approcher de l'anonymat. Par exemple, les identificateurs persistents sont évidemment directement opposés à cet objectif puisqu'ils rendent l'anonymat impossible (rappel important : anonymat ≠ pseudonymat). Au minimum, il faudrait permettre à l'utilisateur de changer facilement et souvent ces identificateurs. Et, bien sûr, ne pas imposer qu'ils soient liés à l'identité étatique. Des exemples d'identificateurs qui sont parfois utilisés sur le long terme sont les adresses IP, les Connection ID de QUIC (un bon exemple d'un protocole qui permet leur changement facilement), les biscuits de HTTP, et les adresses du courrier électronique, certainement très difficiles à changer. Comme le montre l'exemple de ces adresses, les identificateurs stables sont utiles et on ne peut pas forcément les remplacer par des identificateurs temporaires. Ici, le souci de vie privée rentre en contradiction avec l'utilité des identificateurs, une tension courante en sécurité. Le fait qu'on ne puisse en général pas se passer d'identificateurs, à relativement longue durée de vie, est justement une des raisons pour lesquelles il n'y a pas de vrai anonymat sur l'Internet.
Notons que les politiciens de mauvaise foi et les journalistes incompétents parlent parfois d'anonymat dès qu'un identificateur stable n'est pas l'identité étatique (par exemple quand je crée un compte Gmail « anonymous652231 » au lieu d'utiliser le nom qui est sur ma carte d'identité). Mais tout identificateur stable peut finir par se retrouver lié à une autre identité, peut-être aussi à l'identité étatique, par exemple si deux identificateurs sont utilisés dans le même message. Et certains identificateurs sont particulièrement communs, avec plusieurs usages, ce qui les rend encore plus dangereux pour la vie privée. Le numéro de téléphone, que certaines messageries instantanées imposent, est particulièrement sensible et est donc déconseillé.
Donc, s'il faut utiliser des identificateurs stables, ils doivent au moins pouvoir être des pseudonymes.
D'autres façons de désanonymiser existent, par exemple quand les gens ont bêtement cru que condenser un identificateur n'était pas réversible (cf. l'article « Four cents to deanonymize: Companies reverse hashed email addresses »).
Notre RFC rappelle ainsi les discussions animées qu'avait connu
l'IETF en raison d'un mécanisme d'allocation des adresses
IPv6, qui les faisaient dériver d'un
identificateur stable, l'adresse
MAC, qui permettait de suivre à la trace un
utilisateur mobile. Depuis, le RFC 8981 (et
le RFC 7217 pour les cas où on veut une
stabilité limitée dans l'espace) ont résolu ce problème (le RFC 7721 résume le débat). À noter que l'adresse MAC peut aussi
devenir variable (draft-ietf-madinas-mac-address-randomization
).
Autre exemple où un protocole IETF avait une utilisation imprudente des identificateurs, DHCP, avec ses identificateurs stables qui, certes, n'étaient pas obligatoires mais, en pratique, étaient largement utilisés (RFC 7844).
Une autre question très sensible est celle de la
censure. Le protocole en cours de
développement a-t-il des caractéristiques qui rendent la censure
plus facile ou au contraire plus difficile (section 4.16) ? Par
exemple, si le protocole fait passer par des points bien identifiés
les communications, ces points vont certainement tenter les censeurs
(pensez aux résolveurs DNS et à leur rôle dans la censure…). Les RFC 7754 et RFC 9505 décrivent les
techniques de censure. Elles sont très variées. Par exemple, pour le
Web, le censeur peut agir sur les résolveurs DNS mais aussi bloquer
l'adresse IP ou bien, en faisant du DPI,
bloquer les connexions TLS en regardant le
SNI. Certains systèmes d'accès au contenu,
comme Tor, ou de distribution du
contenu, comme BitTorrent, résistent mieux à
la censure que le Web mais ont d'autres défauts, par exemple en
termes de performance. L'exemple du SNI montre
en tout cas très bien une faiblesse de certains protocoles : exposer
des identificateurs aux tierces parties (qui ne sont aucune des
deux parties qui communiquent) facilite la censure. C'est pour cela
que l'IETF développe ECH
(cf. draft-ietf-tls-esni/
).
Le protocole, tel que normalisé dans un RFC, c'est bien joli, mais il faut aussi tenir compte du déploiement effectif. Comme noté au début, ce n'est pas dans le RFC 5321 qu'on trouvera les causes de la domination de Gmail ! Les effets du protocole dans la nature sont en effet très difficiles à prévoir. La section 4.17 se penche sur cette question et demande qu'on considère, non seulement le protocole mais aussi les effets qu'il aura une fois déployé. [Ce qui, à mon avis, relève largement de la boule de cristal, surtout si on veut tenir compte d'effets économiques. Et les exemples du RFC ne sont pas géniaux, comme de reprocher au courrier sa gratuité, qui encourage le spam. L'exemple de l'absence de mécanisme de paiement sur le Web, qui pousse à développer des mécanismes néfastes comme la publicité, est meilleur.] La section 4.21 traite également ce sujet des conséquences, parfois inattendues, du déploiement d'un protocole.
Le RFC a aussi un mot (section 4.18) sur les questions d'accessibilité, notamment aux handicapés. Cette question, très présente dans les discussions autour des couches hautes du Web (cf. les réunions Paris Web) semble plus éloignée de ce que fait l'IETF mais le RFC cite quand même l'exemple du RFC 9071, sur l'utilisation de RTP dans des réunions en ligne, avec une alternative en texte pour les personnes malentendantes.
L'Internet est aujourd'hui très, trop, centralisé, notamment pour ce qui concerne les services (la connectivité, quoique imparfaitement répartie, est moins dépendante d'un petit nombre d'acteurs). Il est donc utile, lors de la conception d'un protocole, de réfléchir aux caractéristiques du protocole qui risquent d'encourager la centralisation (par exemple par la création d'un, ou d'un petit nombre de points de contrôle). Le RFC 3935 donne explicitement à l'IETF un objectif de promotion de la décentralisation.
En conclusion, même si l'activité organisée d'examen des futurs RFC n'a pas pris, ce RFC reste utile pour réfléchir à l'impact de nos protocoles sur les droits des humains.
Date de publication du RFC : Juillet 2024
Auteur(s) du RFC : R. Bellis (ISC), J. Abley
(Cloudflare)
Chemin des normes
Réalisé dans le cadre du groupe de travail IETF dnsop
Première rédaction de cet article le 25 juillet 2024
Dans un message DNS, il y a quatre sections qui peuvent prendre un nombre variable d'enregistrements (resource records). Chaque section est précédée d'un chiffre qui indique ce nombre d'enregistrements. L'une de ces sections indique la question posée. Est-ce que cela a un sens de mettre plusieurs questions dans un message ? Non, répond, ce très court RFC, qui clarifie le RFC 1035 sur ce point.
Regardons une requête DNS avec tshark :
% tshark -V -r dns.pcap … Domain Name System (query) Transaction ID: 0x0dfd Flags: 0x0120 Standard query 0... .... .... .... = Response: Message is a query .000 0... .... .... = Opcode: Standard query (0) … Questions: 1 Answer RRs: 0 Authority RRs: 0 Additional RRs: 1 Queries wylag.de: type A, class IN Name: wylag.de [Name Length: 8] [Label Count: 2] Type: A (Host Address) (1) Class: IN (0x0001) Additional records <Root>: type OPT Name: <Root> Type: OPT (41) UDP payload size: 4096 Higher bits in extended RCODE: 0x00 EDNS0 version: 0 Z: 0x8000 1... .... .... .... = DO bit: Accepts DNSSEC security RRs .000 0000 0000 0000 = Reserved: 0x0000 Data length: 12 Option: COOKIE Option Code: COOKIE (10) Option Length: 8 Option Data: 93c545f2aaf3f12c Client Cookie: 93c545f2aaf3f12c Server Cookie: <MISSING>
Il s'agit d'une requête, pas d'une réponse, donc il est normal que
les sections Answer et Authority soient vides (taille à zéro). La
section Additional n'est pas vide car elle contient l'enregistrement
EDNS. Et la section Question contient un seul
enregistrement, la question (« quelle est l'adresse IPv4 de
wylag.de
? »).
Tout le problème traitée par ce RFC est : que se passe t-il si la section Question d'une requête contient plus d'un enregistrement ? Je divulgâche tout de suite : c'est interdit, il ne faut pas. (Pour le cas des requêtes/réponses ordinaires, avec l'opcode 0 ; d'autres messages DNS peuvent avoir des règles différentes.) La principale raison pour cette interdiction est que, dans la réponse, certains champs sont globaux à toute la réponse (comme le code de réponse, le rcode) et on ne peut donc pas se permettre d'accepter des questions qui risqueraient de nécessiter des réponses différentes. Désormais, un serveur DNS qui voit passer une requête avec un nombre de questions supérieur à 1 doit répondre avec le code de retour FORMERR (format error).
Le RFC 1035, la norme originelle, traitait ce point mais restait flou.
Il y avait une autre façon de régler le problème, en imposant que, s'il y a plusieurs questions, toutes portent sur le même nom de domaine. Cela aurait réglé le doute sur le code de retour et aurait pu être pratique pour des cas comme la demande simultanée de l'adresse IPv4 et IPv6. Mais cette solution a été écartée au profit de la solution plus simple qui était d'interdire les questions multiples. (Et, de toute façon, on ne peut pas garantir que le code de retour sera le même pour tous les types, même si le nom est le même. Pensez aux serveurs DNS générant dynamiquement les données.)
Date de publication du RFC : Septembre 2024
Auteur(s) du RFC : B. Jonglez (ENS Lyon), J. Chroboczek (IRIF, Université Paris Cité)
Chemin des normes
Réalisé dans le cadre du groupe de travail IETF babel
Première rédaction de cet article le 14 septembre 2024
Voici un RFC sur le protocole de routage Babel. Il normalise une extension au protocole pour tenir compte du RTT, le temps d'aller-retour sur un segment du réseau. C'est l'occasion de revenir sur ce critère de routage, souvent cité mais rarement utilisé (et pour de bonnes raisons).
On lit parfois des affirmations rapides comme « les routeurs Internet choisissent la route la plus rapide ». Outre que « rapide » n'a pas de définition précise (parle-t-on de la latence ? De la capacité ?), la phrase est fausse : le routage se fait sur des critères nettement moins dynamiques. Dans le cas de BGP, ce sont en bonne partie des critères de business. Das le cas d'un IGP, des critères de performance sont pris en compte, mais en se limitant à des critères assez statiques. En effet, si on prend en compte naïvement une variable très dynamique, qui change souvent, comme l'est le RTT, on risque d'avoir un routage très instable : un lien est peu utilisé, ses performances sont bonnes, les routeurs vont y envoyer tout le trafic, ses performances vont chuter, les routeurs vont tout envoyer ailleurs, les performances vont remonter, les routeurs vont encore changer d'avis, etc. Tenir compte des performances immédiates du lien est donc une fausse bonne idée ou, plus précisément, ne doit pas être fait naïvement.
C'est justement ce que fait ce RFC qui explique comment utiliser intelligemment un critère très mouvant, le RTT (la latence, mais mesurée en aller-retour), pour le choix des routes dans Babel. Babel est un protocole de routage de type IGP, normalisé dans le RFC 8966, surtout prévu pour des réseaux sans administration centrale.
Si par exemple (section 1 du RFC), une machine A à Paris a deux moyens de joindre une machine D également à Paris, une des routes passant par un routeur B à Paris et une autre par un routeur C à Tokyo, il est assez évident qu'il vaut mieux rester à Paris. Mais ce n'est pas forcément ce que feront les mises en œuvre de Babel, qui se disent simplement que les deux routes ont le même nombre de segments (deux). Bien sûr, attribuer manuellement des préférences aux segments résoudrait le problème (un lien vers le Japon est plus « cher ») mais l'un des buts de Babel est de ne pas requérir de configuration manuelle par un administrateur réseaux.
Utiliser le RTT, qui est relativement facile à évaluer (mais continuez la lecture : il y a des pièges) semble une solution évidente. Mais, comme dit plus haut, cela peut mener à de violentes instabilités du routage. Il faut donc évaluer le RTT, puis le traiter avant de l'utiliser, pour éviter des oscillations du routage. Et attention, la latence n'est pas toujours le meilleur critère de choix, d'autres, comme le coût monétaire, peuvent être pris en compte, par exemple pour privilégier un lien WiFi sur un lien 5G. Babel n'impose pas de critère unique de choix des routes.
Cette extension ne nécessite pas que les différents routeurs aient une horloge synchronisée (ce qui serait difficile à faire dans un réseau sans administration), juste que les horloges ne dérivent pas trop vite l'une par rapport à l'autre. D'autre part, comme l'algorithme essaie de limiter les oscillations de routes, il ne s'ajustera pas instantanément aux changements, et ne sera donc pas optimal dans tous les cas.
Commençons par l'évaluation du RTT. Il ne suffit pas de soustraire le temps de départ d'une question au temps d'arrivée d'une réponse, entre autre parce que la génération de la réponse peut prendre du temps (et puis un routeur Babel n'est pas obligé de répondre à tous les messages Hello). Et rappelez-vous que cette extension n'impose pas une synchronisation des horloges. L'algorithme est donc un peu plus compliqué, c'est celui créé par Mills et utilisé entre autres pour NTP. Un routeur nommé Alice qui envoie un message Hello (RFC 8966, section 3.4.1) ajoute à ces messages le temps de départ, appelé t1. Le routeur nommé Bob le reçoit à un temps t1'. Comme les horloges ne sont pas forcément synchronisées, on ne peut pas comparer directement t1 et t1'. Au lieu de cela, Bob, quand il enverra un message Babel IHU (I Heard yoU) indiquera à la fois t1 et t1' dans ce message, ainsi que le temps t2' d'émission. Alice, recevant ce message, n'aura qu'à calculer (t2 - t1) - (t2' - t1') et aura ainsi le RTT, même si les horloges sont différentes (tant qu'elles ne dérivent pas trop dans l'intervalle entre t1 et t2). Bon, j'ai simplifié, mais vous avez tous les détails dans le RFC, section 3.2.
Notez que cet algorithme impose aux deux routeurs de garder des informations supplémentaires sur leurs voisins, dans la table documentée dans la section 3.2.4 du RFC 8966. Celle-ci devra stocker les estampilles temporelles reçues.
Ces estampilles étant stockées sur 32 bits et ayant une
résolution en microsecondes, elles reviendront à zéro toutes les 71
minutes. Le routeur doit donc prendre garde à ignorer les
estampilles situées dans son futur (par exemple s'il a redémarré et
perdu la notion du temps) ou trop éloignées. Sur un système
POSIX, le routeur peut donc utiliser
clock_gettime(CLOCK_MONOTONIC)
.
Bon, désormais, nous avons le RTT. Qu'en faire ? Comme indiqué plus haut, il ne faut pas l'utiliser tel quel comme critère de sélection des routes, sous peine d'osccilations importantes des tables de routage. Il y a plusieurs opérations à faire (section 4). D'abord, il faut lisser le RTT, éliminant ainsi les cas extrêmes. Ainsi, on va utiliser la formule RTT ← α RTT + (1 - α) RTTn, où RTTn désigne la mesure qu'on vient juste de faire, et où la constante α a une valeur recommandée de 0,836. (Les détails figurent dans l'article « A delay-based routing metric ».)
Ensuite, pour nourrir l'algorithme de choix des routes, il faut convertir ce RTT en un coût. La fonction est simple : de 0 à une valeur rtt-min, le coût est constant, il augmente ensuite linéairement vers une valeur maximale, atteinte pour un RTT égal à rtt-max, et constante ensuite. Les valeurs par défaut recommandées sont de 10 ms pour rtt-min et 150 ms pour rtt-max. En d'autres termes, sur l'Internet, tout RTT inférieur à 10 ms est bon, tout RTT supérieur à 150 ms est à éviter autant que possible.
Enfin, car il reste encore des variations, la section 4 sur les traitements du RTT se termine en demandant l'application d'un mécanisme d'hystérésis, comme celui du RFC 8966, annexe A.3.
Le format des paquets, maintenant. Rappelons que Babel encode les données en TLV et permet des sous-TLV (la valeur d'un TLV est elle-même un TLV). Un routeur doit ignorer les sous-TLV qu'il ne comprend pas, ce qui permet d'ajouter des nouvelles informations sans risque (section 5).
Donc, notre RFC normalise le sous-TLV Timestamp (type 3), qui permet de stocker les estampilles temporelles, et peut apparaitre sous les sous-TLV Hello et IHU. Dans le premier cas, il stockera juste une valeur (t1 ou t2' dans l'exemple plus haut), dans le second, deux valeurs (t1 et t1' dans l'exemple plus haut).
La mise en œuvre « officielle » de Babel a, dans sa version de
développement, la capacité de déterminer ces RTT (option
enable-timestamps
dans la configuration).
Date de publication du RFC : Juin 2024
Auteur(s) du RFC : T. Reddy.K (Nokia), M. Boucadair
(Orange)
Chemin des normes
Réalisé dans le cadre du groupe de travail IETF add
Première rédaction de cet article le 18 août 2024
Traditionnellement, tous les résolveurs DNS fournissaient un service équivalent. Le client avait un résolveur configuré, soit statiquement, soit via DHCP, et ne se souciait pas vraiment du résolveur ainsi désigné, tous étaient pareils. Aujourd'hui, ce n'est plus vraiment le cas : les résolveurs fournissent des services très différents, par exemple en matière de chiffrement, ou de blocage de certains noms. Il est donc utile que le client puisse se renseigner sur son résolveur, ce que permet cette nouvelle technique, le type de données RESINFO, que le client va récupérer dans le DNS.
Voici une liste (non-limitative !) des caractéristiques d'un résolveur et qui peuvent être présentes ou pas :
Avant la solution de ce RFC, la seule manière pour le client DNS de savoir ce que son résolveur proposait était manuelle, en lisant des documentations (cf. par exemple celle de mon résolveur). Ce n'est pas pratique quand la configuration du résolveur est automatique ou semi-automatique, via DHCP, ou avec les solutions des RFC 9462 et RFC 9463.
Ce nouveau RFC propose donc un mécanisme qui permet au client de découvrir les caractéristiques d'un résolveur et de les analyser, avant de décider quel résolveur choisir. (Un point important est que ce RFC se veut neutre : il ne dit pas quelles sont les bonnes caractéristiques d'un résolveur, le client reçoit une information, il est libre de l'utiliser comme il veut.)
Place à la technique (section 3 du RFC) : un nouveau type de
données DNS est défini, RESINFO (code
261). Son contenu est l'information recherchée, sous forme de
couples clé=valeur. Le nom de domaine auquel il est rattaché est le
nom du résolveur, récupéré par les méthodes des RFC 9462 et RFC 9463, ou manuellement
configuré. Ce nom est désigné par le sigle ADN, pour
Authentication Domain Name (RFC 9463, section 3.1.1). Si on a utilisé le nom spécial
resolver.arpa
(RFC 9462,
section 4), on peut lui demander son RESINFO.
Le format du RESINFO (section 4 du RFC) est copié sur celui des enregistrements TXT. Chaque chaine de caractères suit le modèle clé=valeur du RFC 6763, section 6.3. Les clés inconnues doivent être ignorées, ce qui permettra dans le futur d'ajouter de nouvelles clés au registre des clés. Un exemple d'enregistrement RESINFO :
resolver IN RESINFO "qnamemin" "exterr=15,17" "infourl=https://resolver.example.com/guide"
Il indique (section 5, sur la signification des clés) que ce résolveur fait de la minimisation des requêtes (RFC 9156), et notez que cette clé n'a pas de valeur, c'est juste un booléen dont la présence indique la QNAME minimisation. L'enregistrement continue en donnant les codes EDE (Extended DNS Errors, RFC 8914) que peut renvoyer le résolveur. C'est surtout utile pour indiquer ce qu'il bloque (15 = bloqué par décision de l'administrateurice du résolveur, 17 = bloqué par demande de l'utilisateurice). Et enfin il donne un URL où on peut aller chercher davantage d'information en langue naturelle.
La section 7 du RFC donne quelques conseils de sécurité : avoir
un lien sécurisé avec le résolveur qu'on interroge (par exemple avec
DoT), pour éviter qu'un méchant ne modifie le
RESINFO, et valider la réponse avec DNSSEC (sauf pour
resolver.arpa
, qui est un cas spécial).
La section 8 précise le registre
des clés disponibles. Pour ajouter des clés (on note qu'à
l'heure actuelle, il n'y en a pas pour indiquer la disponibilité de
DoT ou DoH, ou pour la politique de conservation des
requêtes), la procédure est « spécification nécessaire » (RFC 8126). Si on veut des clés non normalisées, on
doit les préfixer par temp-
.
RESINFO est récent et donc pas forcément mis en œuvre dans tous les logiciels DNS que vous utilisez. Un dig récent fonctionne :
% dig dot.bortzmeyer.fr RESINFO … ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 34836 ;; flags: qr rd ra ad; QUERY: 1, ANSWER: 2, AUTHORITY: 0, ADDITIONAL: 1 … ;; ANSWER SECTION: dot.bortzmeyer.fr. 86369 IN CNAME radia.bortzmeyer.org. radia.bortzmeyer.org. 86369 IN RESINFO "qnamemin" "infourl=https://doh.bortzmeyer.fr/policy" ;; Query time: 0 msec ;; SERVER: 192.168.2.254#53(192.168.2.254) (UDP) ;; WHEN: Sun Aug 18 08:15:55 UTC 2024 ;; MSG SIZE rcvd: 142
Si vous avez un dig plus ancien, il faudra demander TYPE261 et pas RESINFO. Notez que, physiquement, un RESINFO est juste un TXT, ce qui facilite sa mise en œuvre (dans le futur dnspython, la classe RESINFO hérite simplement de TXT).
Trouve-t-on beaucoup de RESINFO dans la nature ? La plupart des grands résolveurs DNS publics ne semblent pas en avoir. Une exception est DNS4ALL :
% dig +short dot.dns4all.eu RESINFO "qnamemin exterr=0-1,3,5-12,18,20 infourl=https://dns4all.eu"
Et, comme vous le voyez plus haut, j'en ai mis un dans mon résolveur. Le logiciel du serveur primaire ne connaissant pas encore ce type, j'ai utilisé la technique des types inconnus du RFC 3597 :
; Pour le résolveur public : ; Type RESINFO (RFC 9606), enregistré à l'IANA mais pas encore connu des logiciels radia IN TYPE261 \# 50 08716e616d656d696e 28696e666f75726c3d68747470733a2f2f646f682e626f72747a6d657965722e66722f706f6c696379
Cette série de chiffres hexadécimaux ayant été produite à partir de
la version texte et du programme
. On note que, comme
ce résolveur public n'est pas menteur, je n'indique pas d'EDE
(Extended DNS Errors, RFC 8914).
text-to-unknown-txt-type.py
Date de publication du RFC : Août 2024
Auteur(s) du RFC : C. Bormann (Universität Bremen TZI), B. Gamari (Well-Typed), H. Birkholz (Fraunhofer SIT)
Chemin des normes
Réalisé dans le cadre du groupe de travail IETF cbor
Première rédaction de cet article le 17 août 2024
Ce RFC ajoute au format de données binaire CBOR la possibilité de stocker des données temporelles plus détaillées, incluant par exemple l'échelle utilisée (UTC ou TAI), ou bien ayant une précision supérieure.
Petit rappel : CBOR est normalisé dans le RFC 8949, il incluait deux étiquettes pour les données temporelles, permettant d'indiquer date et heure en format lisible par un humain, et sous forme d'un nombre de secondes depuis l'epoch, avec une résolution d'une seconde. Le RFC 8943 y ajoute les dates (sans indication de l'heure). Au passage, le concept d'étiquette en CBOR est normalisé dans la section 3.4 du RFC 8949.
Notre nouveau RFC 9581 ajoute :
Ces nouvelles étiquettes ont été ajoutées au registre des étiquettes CBOR. Les clés possibles pour les dictionnaires indiquant les temps étendus sont dans un nouveau registre à l'IANA. Ajouter une entrée à ce registre nécessite un RFC et un examen par un expert.
Un service sur ce blog,
permet d'obtenir plusieurs valeurs
utilisant ces formats. Si nous utilisons le gem
Ruby cbor-diag, nous
voyons :
https://www.bortzmeyer.org/apps/date-in-cbor
% curl -s https://www.bortzmeyer.org/apps/date-in-cbor | cbor2diag.rb ["Current date in CBOR, done with Python 3.11.2 (main, Mar 13 2023, 12:18:29) [GCC 12.2.0] and the flunn library <https://github.com/funny-falcon/flunn>", 0("2024-04-17T14:21:07Z"), 1(1713363667), 100(19830), 1004("2024-04-17"), 1001({1: 1713363667, -1: 0, -9: 193986759}), 1001({1: 1713363704, -1: 1}), "Duration since boot: ", 1002({1: 1742903})]
On voit alors successivement :
Question programmation, le service a été écrit en
Python. Ce langage a une fonction
time_ns
,
qui permet d'obtenir les nanosecondes. Pour le TAI, cela a été un
peu plus difficile, la machine doit être configurée spécialement.
Date de publication du RFC : Juillet 2024
Auteur(s) du RFC : P. Wouters (Aiven), D. Huigens
(Proton AG), J. Winter
(Sequoia-PGP), Y. Niibe (FSIJ)
Chemin des normes
Réalisé dans le cadre du groupe de travail IETF openpgp
Première rédaction de cet article le 1 août 2024
Dernière mise à jour le 2 août 2024
Le logiciel PGP est synonyme de cryptographie pour beaucoup de gens. Un des plus anciens et des plus utilisés pour les fonctions de confidentialité mais aussi d'authentification. Le format de PGP, OpenPGP, est normalisé dans ce RFC, qui remplace le RFC 4880 (il n'y a pas de changement crucial, juste une mise à jour longtemps attendue).
PGP a vu son
format de données normalisé pour la première fois en août 1996, dans
le RFC 1991. Cette norme a été révisée par la suite, dans
le RFC 2440, puis le RFC 4880, puis par des RFC ponctuels (comme les RFC 5581 et RFC 6637, désormais inclus)
et notre RFC est la dernière version, qui synthétise tout. Sa
gestation a été longue et douloureuse et a suscité des controverses,
menant à un projet concurrent, LibrePGP (qui prévoit son propre
RFC, actuellement draft-koch-librepgp
,
que j'avoue n'avoir pas lu).
Cette normalisation permet à diverses mises en œuvre de PGP d'interopérer. La plus connue aujourd'hui est la seule libre, GNU Privacy Guard (qui n'existait pas encore au moment de la publication du premier RFC). Il ne faut donc pas confondre le logiciel PGP, écrit à l'origine par Phil Zimmermann, et qui est non-libre, avec le format OpenPGP que décrit notre RFC (cf. section 1.1) et que des logiciels autres que PGP peuvent lire et écrire.
Le principe du chiffrement avec PGP est simple. Une clé de session (le terme est impropre puisqu'il n'y a pas de session au sens de TLS mais c'est celui utilisé par le RFC) est créée pour chaque destinataire, elle sert à chiffrer le message et cette clé est chiffrée avec la clé publique du destinataire (section 2.1 du RFC).
Pour l'authentification, c'est aussi simple conceptuellement. Le message est condensé et le condensé est chiffré avec la clé privée de l'émetteur (section 2.2 du RFC).
Le format OpenPGP permet également la compression (qui améliore la sécurité en supprimant les redondances) et l'encodage en Base64 (RFC 4648), baptisé ASCII armor, pour passer à travers des logiciels qui n'aiment pas le binaire (la section 6 détaille cet encodage).
La section 3 explique les éléments de base utilisés par le format PGP. L'un des plus importants est le concept d'entier de grande précision (MPI pour Multi-Precision Integers), qui permet de représenter des entiers de très grande taille, indispensables à la cryptographie, sous forme d'un doublet longueur + valeur.
Enfin les sections 4 et 5 expliquent le format lui-même. Un message PGP est constitué de paquets (rien à voir avec les paquets réseau). Chaque paquet a un type, une longueur et un contenu. Par exemple, un paquet de type 1 est une clé de session chiffrée, un paquet de type 2 une signature, un paquet de type 9 du contenu chiffré, etc.
La section 7 du RFC décrit un type de message un peu particulier, qui n'obéit pas à la syntaxe ci-dessus, les messages en clair mais signés. Ces messages ont l'avantage de pouvoir être lus sans avoir de logiciel PGP. Ils nécessitent donc des règles spéciales.
On notera que gpg
permet d'afficher les paquets présents dans un message PGP, ce qui
est pratique pour l'apprentissage ou le débogage. Voyons un exemple
avec un fichier test.txt
de 17 octets, signé
mais non chiffré (j'ai un peu simplifié la sortie du logiciel) :
% gpg --list-packets test.gpg :compressed packet: algo=2 :onepass_sig packet: keyid 3FA836C996A4A254 version 3, sigclass 0x00, digest 10, pubkey 1, last=1 :literal data packet: mode b (62), created 1721800388, name="test.txt", raw data: 17 bytes :signature packet: algo 1, keyid 3FA836C996A4A254 version 4, created 1721800388, md5len 0, sigclass 0x00 digest algo 10, begin of digest 2b d9 hashed subpkt 33 len 21 (issuer fpr v4 C760CAFC6387B0E8886C823B3FA836C996A4A254) hashed subpkt 2 len 4 (sig created 2024-07-24) subpkt 16 len 8 (issuer key ID 3FA836C996A4A254) data: [4096 bits]
Malheureusement, gpg n'affiche pas les valeurs numériques des types, telles que listées par le RFC. Mais les noms qu'il utilise sont les mêmes que dans le RFC, on peut donc facilement trouver la section qui explique ce qu'est un "onepass_sig packet" (section 5.4).
Avec un message chiffré, on obtient :
% gpg --list-packets test.txt.gpg gpg: encrypted with 4096-bit RSA key, ID 9045E02757F02AA1, created 2014-02-09 "Stéphane Bortzmeyer (Main key) <stephane@bortzmeyer.org>" gpg: encrypted with 2048-bit RSA key, ID 516CB37B336525BB, created 2009-12-15 "ISC Security Officer <security-officer@isc.org>" gpg: decryption failed: No secret key :pubkey enc packet: version 3, algo 1, keyid 516CB37B336525BB data: [2046 bits] :pubkey enc packet: version 3, algo 1, keyid 9045E02757F02AA1 data: [4096 bits] :encrypted data packet: length: 78 mdc_method: 2
Conformément au principe d'agilité cryptographique (RFC 7696), le format OpenPGP n'est pas lié à un algorithme cryptographique particulier, et permet d'en ajouter de nouveaux. La section 15 détaille comment demander à l'IANA d'ajouter de nouveaux paramètres dans les registres PGP.
L'annexe B, quant à elle, énumère les principaux changements depuis le RFC 4880. Ce dernier RFC a été publié il y a plus de seize ans mais son remplacement par notre nouveau RFC 9580 a été une opération longue et difficile, et les changements se sont accumulés (Daniel Huigens en a fait un bon résumé). Ceci dit, le format OpenPGP ne change pas radicalement, et l'interopérabilité avec les anciens programmes est maintenue. Parmi les principales modifications :
Enfin, un grand nombre d'errata ont été traités (la liste complète est dans l'annexe D).
Date de publication du RFC : Avril 2024
Auteur(s) du RFC : R. Arends (ICANN), M. Larson
(ICANN)
Chemin des normes
Première rédaction de cet article le 27 avril 2024
Lorsqu'un résolveur DNS détecte un problème avec une zone, l'empêchant de résoudre les noms dans cette zone, il n'avait pas de moyen simple et automatique de prévenir les gérants des serveurs faisant autorité pour cette zone. Leur envoyer un message en utilisant l'information dans l'enregistrement SOA ou les adresses classiques du RFC 2142 ? Mais, justement, si la zone ne marche pas, le courrier ne partira pas forcément. Ce nouveau RFC propose un nouveau système. Les serveurs faisant autorité annoncent un domaine (qui marche, espérons-le), qui acceptera des requêtes DNS spéciales signalant le problème.
Cela dépend évidemment du problème pratique qui se pose. Si la zone n'a aucun serveur faisant autorité qui marche, il n'y a évidemment rien à faire. Mais s'ils marchent, tout en servant des données problématiques (par exemple des signatures DNSSEC expirées), alors, le résolveur pourra agir. Les serveurs faisant autorité mettent dans leurs réponses une option EDNS qui indique le domaine qui recevra les rapports (cela doit être un autre domaine, qui n'a pas de problème), le résolveur fera alors une requête DNS se terminant par le nom du domaine de signalement, et encodant le problème. L'agent, le domaine de signalement, pourra alors récolter ces requêtes et savoir qu'il y a un problème. Cela ne traite pas tous les cas (il faudra toujours utiliser RDAP ou whois pour récolter des informations sur les contacts du domaine erroné, puis leur écrire depuis un autre réseau) mais c'est simple, léger et automatisable. Les gérants de domaine sérieux, qui prennent au sérieux les signalements de problèmes techniques (soit 0,00001 % des domaines) pourront alors agir. (Note si vous gérez un résolveur et que vous constatez un problème avec un domaine et que les contacts ne répondent pas : un message méchant sur Twitter est souvent plus efficace.)
Donc, les détails techniques : le domaine qui veut recevoir les éventuels signalements va devoir configurer ses serveurs faisant autorité pour renvoyer une option EDNS, de numéro 18 (section 5 du RFC), indiquant l'agent, c'est-à-dire le domaine qui va recevoir les signalements (il faut évidemment veiller à ce qu'il n'ait pas de point de défaillance commun avec le domaine surveillé). Notez que cette option est systématiquement envoyée, le client (le résolveur) n'a pas à dire quoi que ce soit (la question avait fait l'objet d'un sérieux débat à l'IETF).
En cas de problème, notamment DNSSEC, le résolveur qui a noté le problème va alors construire un nom de domaine formé, successivement (section 6.1.1) par :
_er
,_er
(oui, encore),
Par exemple, si le domaine dyn.bortzmeyer.fr
annonce comme agent report.dyn.sources.org
, et
qu'un résolveur découvre des signatures DNSSEC expirées (EDE 7) en
cherchant à résoudre hello.dyn.bortzmeyer.fr /
TXT
(TXT a la valeur 16), la
requête de signalement du résolveur sera
_er.16.hello.dyn.bortzmeyer.fr.7._er.report.dyn.sources.org
(ouf). Le type demandé est TXT. Lorsque cette requête
arrivera au serveur faisant autorité pour
report.dyn.sources.org
, il pourra enregistrer
qu'il y a eu un problème, et mettre cette information à la
disposition de son administrateur système.
Ce serveur faisant autorité est censé répondre au signalement avec une réponse de type TXT comme ici :
% dig _er.16.hello.dyn.bortzmeyer.fr.7._er.report.dyn.sources.org TXT … ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 12032 ;; flags: qr rd ra ad; QUERY: 1, ANSWER: 2, AUTHORITY: 0, ADDITIONAL: 1 … ;; ANSWER SECTION: _er.16.hello.dyn.bortzmeyer.fr.7._er.report.dyn.sources.org. 30 IN TXT "Thanks for the report of error 7 on hello.dyn.bortzmeyer.fr" …
L'agent peut ensuite être interrogé, par des méthodes propres à la mise en œuvre utilisée :
% echo report | socat - UNIX-CONNECT:/home/drink/drink.sock REPORT state: hello.dyn.bortzmeyer.fr, 7 ip.dyn.bortzmeyer.fr, 7
Ici, on voit que deux domaines ont été signalés comme ayant des signatures expirées (rassurez-vous, c'était juste des tests). Le nombre de signalements n'est pas indiqué, ni la source des signalements (travail futur).
Quelques petits points de sécurité à garder en tête (section 9 du RFC) :
Cette technique a été mise en œuvre dans Drink lors d'un hackathon IETF. Drink peut à la fois signaler un domaine agent, et être serveur pour un domaine agent.
Un exemple de signalisation EDNS de cette option, vu la version de développement de Wireshark (merci à Alexis La Goutte) :
Date de publication du RFC : Mai 2024
Auteur(s) du RFC : K. Davis (Cisco Systems), B. Peabody (Uncloud), P. Leach (University of Washington)
Chemin des normes
Réalisé dans le cadre du groupe de travail IETF uuidrev
Première rédaction de cet article le 12 mai 2024
Ce RFC normalise les UUID, une famille d'identificateurs uniques, obtenus sans registre central. Il remplace l'ancienne norme, le RFC 4122, avec pas mal de nouveautés et un RFC complètement refait.
Les UUID, également connus autrefois sous le nom de GUID (Globally Unique IDentifiers), sont issus à l'origine du système des Apollo, adopté ensuite dans la plate-forme DCE. Les UUID ont une taille fixe, 128 bits, et sont obtenus localement, par exemple à partir d'un autre identificateur unique comme un nom de domaine, ou bien en tirant au hasard, la grande taille de leur espace de nommage faisant que les collisions sont très improbables (section 6.7 du RFC). Ce RFC reprend la spécification (bien oubliée aujourd'hui) de DCE de l'Open Group (ex-OSF) et ajoute la définition d'un espace de noms pour des URN (sections 4 et 7). (Il existe aussi une norme ITU sur les UUID et un registre des UUID, pour ceux qui y tiennent.)
Les UUID peuvent donc convenir pour identifier une entité sur le réseau, par exemple une machine mais aussi, vu leur nombre, comme identificateur unique pour des transactions (ce qui était un de leurs usages dans DCE). En revanche, ils ne sont pas résolvables, contrairement aux noms de domaine. Mais ils sont présents dans beaucoup de logiciels (Windows, par exemple, les utilise intensivement). On les utilise comme clé primaire dans les bases de données, comme identificateur de transaction, comme nom de machine, etc.
Les UUID peuvent se représenter sous forme d'un nombre binaire de
128 bits (la section 5 du RFC décrit les différents champs qui
peuvent apparaitre) ou bien sous forme texte. Sur
Unix, on peut fabriquer un UUID avec la
commande uuidgen
, qui affiche la représentation
texte standard que normalise notre RFC (section 4) :
% uuidgen 317e8ed3-1428-4ef1-9dce-505ffbcba11a % uuidgen ec8638fd-c93d-4c6f-9826-f3c71436443a
Sur Linux, vous pouvez aussi simplement
faire cat /proc/sys/kernel/random/uuid
. Sur une machine FreeBSD, un UUID de la
machine est automatiquement généré (par le script
/etc/rc.d/hostid
) et stocké dans le fichier
/etc/hostid
.
Pour l'affichage sous forme d'URN (RFC 8141), on ajoute
juste l'espace uuid
par exemple
urn:uuid:ec8638fd-c93d-4c6f-9826-f3c71436443a
. Il
a été ajouté au registre
IANA des espaces de noms des URN.
La section 4 du RFC détaille le format de l'UUID. En dépit des apparences, l'UUID n'est pas plat, il a une structure, mais il est très déconseillé aux applications de l'interpréter (section 6.12). Un des champs les plus importants est le champ Version (qui devrait plutôt s'appeler Type) car il existe plusieurs types d'UUID :
Ces différents types / versions figurent dans un registre IANA. Ce registre ne peut être modifié que par une action de normalisation (cf. RFC 8126).
uuidgen, vu plus haut, peut générer des UUID de
version 1 option -t
, de version 3
(-m
), de version 4 (c'est son comportement par
défaut, mais on peut utiliser l'option -r
si on
veut être explicite) ou de version 5 (-s
). Ici,
on voit les UUID fondés sur une estampille temporelle (version 1)
augmenter petit à petit :
% uuidgen -t 42ff1626-0fc7-11ef-8162-49e9505fb2f3 % uuidgen -t 4361fae8-0fc7-11ef-8162-49e9505fb2f3 % uuidgen -t 45381d02-0fc7-11ef-8162-49e9505fb2f3
Ici, dans le cas d'un UUID fondé sur un nom (version 3), l'UUID est stable (essayez chez vous, vous devriez obtenir le même résultat que moi), une propriété importante des UUID de version 3 et 5 :
% uuidgen -m -n @dns -N foobar.example 8796bf1a-793c-3c44-9ec5-a572635cd3d4 % uuidgen -m -n @dns -N foobar.example 8796bf1a-793c-3c44-9ec5-a572635cd3d4
Les espaces de noms sont enregistrés dans un
registre IANA, d'autres peuvent être ajoutés si on écrit une spécification
(cf. RFC 8126). Notez que chaque espace a son
UUID (6ba7b810-9dad-11d1-80b4-00c04fd430c8
pour
l'espace DNS).
Les UUID de version 6 et 7, nouveautés de ce RFC 9562, ne sont pas mis en œuvre par uuidgen, ni d'ailleurs par beaucoup d'autres programmes.
Les sections 6.1 et 6.2, elles, décrivent le processus de génération d'un UUID à base temporelle. Idéalement, il faut utiliser une graine enregistrée sur le disque (pour éviter de générer des UUID identiques) ainsi que l'instant de la génération. Mais lire sur le disque prend du temps (alors qu'on peut vouloir générer des UUID rapidement, par exemple pour identifier des transactions) et l'horloge de la machine n'a pas toujours une résolution suffisante pour éviter de lire deux fois de suite le même instant. Ces sections contiennent donc également des avis sur la génération fiable d'UUID, par exemple en gardant en mémoire le nombre d'UUID générés, pour les ajouter à l'heure.
La section 8, consacrée à la sécurité, rappelle qu'un UUID ne doit pas être utilisé comme capacité (car il est trop facile à deviner) et qu'il ne faut pas demander à un humain de comparer deux UUID (ils se ressemblent trop pour un œil humain).
Il est évidemment recommandé d'utiliser les UUID de version 5 plutôt que de version 3 (RFC 6151) mais SHA-1 a aussi ses problèmes (RFC 6194) et, de toute façon, pour la plupart des utilisations des UUID, les faiblesses cryptographiques de MD5 et de SHA-1 ne sont pas gênantes.
La section 2.1 du RFC détaille les motivations pour la mise à jour du RFC 4122 et quels sont les changements effectués. Certaines utilisations des UUID ont remis en cause des suppositions originales. Ainsi, les UUID sont souvent utilisés dans un contexte réparti, où leur capacité à être uniques sans registre central est très utile. Mais quelques points manquaient au RFC 4122 :
Seize mises en œuvre des UUID ont été étudiées pour préparer le nouveau RFC (vous avez la liste dans la section 2.1), menant aux constations suivantes :
En Python, il existe un module UUID qui offre des fonctions de génération d'UUID de différentes versions (mais pas les plus récentes) :
import uuid myuuid = uuid.uuid1() # Version 1, Time-based UUID heruuid = uuid.uuid3(uuid.NAMESPACE_DNS, "foo.bar.example") # Version # 3, Name-based ("hash-based") UUID, a name hashed by MD5 otheruuid = uuid.uuid4() # Version 4, Random-based UUID yetanotheruuid = uuid.uuid5(uuid.NAMESPACE_DNS, "www.example.org") # Version 5, a name hashed by SHA1 if (myuuid == otheruuid or \ myuuid == heruuid or \ myuuid == yetanotheruuid or \ otheruuid == yetanotheruuid): raise Exception("They are equal, PANIC!") print(myuuid) print(heruuid) # Will always be the same print(otheruuid) print(yetanotheruuid) # Will always be the same
Et comme le dit la documentation, Note that uuid1() may compromise privacy since it creates a UUID containing the computer’s network address. (méthode de génération des UUID version 1 qui est désormais déconseillée).
Le SGBD PostgreSQL inclut un type UUID. Cela évite de stocker les UUID sous leur forme texte, ce qui est techniquement absurde et consomme 288 bits au lieu de 128 (section 6.13 du RFC).
essais=> CREATE TABLE Transactions (id uuid, value INT); CREATE TABLE essais=> INSERT INTO Transactions VALUES ('74738ff5-5367-5958-9aee-98fffdcd1876', 42); INSERT 0 1 essais=> INSERT INTO Transactions VALUES ('88e6441b-5f5c-436b-8066-80dca8222abf', 6); INSERT 0 1 essais=> INSERT INTO Transactions VALUES ('Pas correct', 3); ERROR: invalid input syntax for type uuid: "Pas correct" LINE 1: INSERT INTO Transactions VALUES ('Pas correct', 3); ^ -- PostgreSQL peut seulement générer la version 4, les aléatoires essais=> INSERT INTO Transactions VALUES (gen_random_uuid () , 0); INSERT 0 1 essais=> SELECT * FROM Transactions; id | value --------------------------------------+------- 74738ff5-5367-5958-9aee-98fffdcd1876 | 42 88e6441b-5f5c-436b-8066-80dca8222abf | 6 41648aef-b123-496e-8a4c-52e573d17b6a | 0 (3 rows)
Attention, le RFC (section 6.13) déconseille l'utilisation des UUID fondés sur un nom pour servir de clé primaire dans la base de données, sauf si on est absolument certain (mais c'est rare) que les noms ne changeront pas.
Il existe plusieurs exemples d'utilisation des UUID. Par exemple,
Linux s'en sert pour identifier les disques
attachés à la machine, de préférence à l'ancien système où l'ajout
d'un nouveau disque pouvait changer l'ordre des numéros sur le bus et
empêcher le système de trouver un disque. Un
/etc/fstab
typique sur Linux contient donc
des :
UUID=da8285a0-3a70-413d-baed-a1f48d7bf7b2 /home ext3 defaults ...
plutôt que les anciens :
/dev/sda3 /home ext3 defaults
car sda3
n'est pas un identificateur
stable. L'UUID, lui, est dans le système de fichiers et ne changera
pas avec les changements sur le bus. On peut
l'afficher avec dumpe2fs :
# dumpe2fs -h /dev/sda3 ... Filesystem UUID: da8285a0-3a70-413d-baed-a1f48d7bf7b2 ...
Un exemple d'utilisation de la nouvelle version 7 est décrit dans l'excellent article « Goodbye integers. Hello UUIDv7! ». Une mise en œuvre de cette version 7 apparait dans « UUIDv7 in 20 languages ».
La section 6 décrit les bonnes pratiques de génération d'un UUID. Ainsi :
Date de publication du RFC : Octobre 2024
Auteur(s) du RFC : S. Lhomme, M. Bunkus, D. Rice
Chemin des normes
Réalisé dans le cadre du groupe de travail IETF cellar
Première rédaction de cet article le 15 octobre 2024
Matroska est un format de conteneur pour du contenu multimédia (son, image, sous-titres, etc). Ce n'est pas un format de données (format utilisé par un codec), mais un moyen de regrouper de manière structurée des données différentes.
Je vous préviens tout de suite, le RFC fait 167 pages, et, en prime, le multimédia n'est pas ma spécialité. Donc, je vais faire court. Matroska est fondé sur un langage qui utilise le modèle de données XML, EBML (normalisé dans le RFC 8794). Notre nouveau RFC 9559 est donc surtout une très longue liste des éléments XML qu'on peut y trouver (Matroska est riche !), encodés en binaire. Matroska n'est pas nouveau (il en est à la version 4), mais c'est sa première normalisation. Notez que vous trouverez de l'information sur la page Web du projet.
Le caractère hiérarchique d'un fichier Matroska (des éléments dans d'autres éléments) est bien rendu par cet outil (qui fait partie de MKVToolNix, cité plus loin) :
% mkvinfo ./format/matroska/testdata/sweep-with-DC.mkvmerge13.mka + EBML head |+ EBML version: 1 |+ EBML read version: 1 |+ Maximum EBML ID length: 4 |+ Maximum EBML size length: 8 |+ Document type: matroska |+ Document type version: 4 |+ Document type read version: 2 + Segment: size 18507 |+ Seek head (subentries will be skipped) |+ EBML void: size 4031 |+ Segment information | + Timestamp scale: 22674 | + Multiplexing application: libebml v1.4.2 + libmatroska v1.6.2 | + Writing application: mkvmerge v52.0.0 ('Secret For The Mad') 64-bit | + Duration: 00:00:02.607714066 | + Date: 2022-08-28 18:32:21 UTC | + Segment UID: 0x3b 0x9f 0x28 0xc7 0xc4 0x90 0x8a 0xe0 0xcd 0x66 0x8f 0x11 0x8f 0x7c 0x2f 0x54 |+ Tracks | + Track | + Track number: 1 (track ID for mkvmerge & mkvextract: 0) | + Track UID: 3794791294650729286 | + Track type: audio | + Codec ID: A_FLAC | + Codec's private data: size 133 | + Default duration: 00:00:00.040634920 (24.609 frames/fields per second for a video track) | + Language: und | + Language (IETF BCP 47): und | + Audio track | + Sampling frequency: 44100 | + Bit depth: 16 |+ EBML void: size 1063 |+ Cluster
On voit un document de la version 4 (celle du RFC), commençant
(forcément, cf. section 4.5 du RFC) par un en-tête EBML, et
comportant une seule piste (l'élément
Tracks
est décrit en section 5.1.4), un son au
format FLAC
(RFC 9639). L'extension
.mka
est souvent utilisée pour des conteneurs
n'ayant que de l'audio (et .mkv
s'il y a de la
vidéo). La piste audio est dans une langue indéterminée,
und. Plusieurs éléments existent pour noter la
langue car Matroska n'utilisait autrefois que la norme
ISO 639 mais la version 4 recommande les bien
plus riches étiquettes de langue IETF (RFC 5646, alias « IETF BCP 47 »).
Un autre document a deux pistes, le son et l'image :
% mkvinfo ~/Videos/2022-01-22\ 13-17-28.mkv … |+ Tracks | + Track | + Track number: 1 (track ID for mkvmerge & mkvextract: 0) | + Track UID: 1 | + "Lacing" flag: 0 | + Language: und | + Codec ID: V_MPEG4/ISO/AVC | + Track type: video | + Default duration: 00:00:00.033333333 (30.000 frames/fields per second for a video track) | + Video track | + Pixel width: 852 | + Pixel height: 480 | + Display unit: 4 | + Codec's private data: size 41 (H.264 profile: High @L3.1) | + Track | + Track number: 2 (track ID for mkvmerge & mkvextract: 1) | + Track UID: 2 | + "Lacing" flag: 0 | + Name: simple_aac_recording | + Language: und | + Codec ID: A_AAC | + Track type: audio | + Audio track | + Channels: 2 | + Sampling frequency: 44100 | + Bit depth: 16 | + Codec's private data: size 5
L'arborescence des fichiers Matroska peut être très profonde et ça se reflète dans la numérotation des sections du RFC (il y a une section 5.1.4.1.28.18 !).
Les types MIME à utiliser sont audio/matroska
et video/matroska
(section 27.3 du RFC).
Il existe plusieurs mises en œuvre de Matroska en logiciel libre comme MKVToolNix, qui sait faire beaucoup de choses (comme afficher les éléments Matroska comme vu plus haut) et qui existe en ligne de commande ou GUI. Et VLC sait évidemment jouer les fichiers Matroska. OBS, quant à lui, permet d'en produire. Par exemple, cette vidéo sans intérêt a été faite avec OBS.
Date de publication du RFC : Avril 2024
Auteur(s) du RFC : B. Makarenko (The Technical center of Internet, LLC), V. Dolmatov (JSC "NPK Kryptonite")
Pour information
Première rédaction de cet article le 21 octobre 2024
Ce RFC marque l'arrivée d'un nouvel algorithme de signature dans les enregistrements DNSSEC, algorithme portant le numéro 23. Bienvenue au GOST R 34.10-2012 (alias ECC-GOST12), algorithme russe, spécifié en anglais dans le RFC 7091, une légère mise à jour de GOST R 34.10-2001.
La liste des algorithmes DNSSEC est un registre à l'IANA, https://www.iana.org/assignments/dns-sec-alg-numbers/dns-sec-alg-numbers.xml#dns-sec-alg-numbers-1
. Elle
comprend désormais GOST R 34.10-2012 (qui succède au R 34.10-2001 du
RFC 5933). Notez que GOST désigne en fait une
organisation de
normalisation, le terme correct serait donc de ne jamais
dire « GOST » tout court, mais plutôt « GOST R 34.10-2012 » pour
l'algorithme de signature et « GOST R 34.11-2012 » pour celui de
condensation,
décrit dans le RFC 6986 (voir la section 1 de notre RFC 9558).
La section 2 décrit le format des enregistrements
DNSKEY
avec GOST, dans lequel
on publie les clés GOST R 34.10-2012. Le champ Algorithme vaut 23,
le format de la clé sur le réseau suit le RFC 7091. GOST
est un algorithme à courbes elliptiques, courbes décrites par Q
= (x,y). Les 32 premiers octets de la clé sont x et les 32 suivants
y (en petit-boutien,
attention, contrairement à la majorité des protocoles Internet).
Parmi les bibliothèques cryptographiques existantes, au moins OpenSSL met en œuvre GOST R 34.10-2012 (testé avec la version 3.3.2). Voir RFC 9215 pour de l'aide à ce sujet. Sinon, on trouve parfois seulement l'ancienne version dans certains logiciels et certaines bibliothèques.
La section 2.2 donne un exemple de clé GOST publiée dans le DNS,
je n'ai pas trouvé d'exemple réel dans la nature, même en
.ru
.
La section 3 décrit le format des enregistrements RRSIG
, les signatures (avec un
exemple). On suit les RFC 5958 et RFC 7091.
Attention, une particularité de GOST fait que deux signatures des
mêmes données peuvent donner des résultats différents, car un
élément aléatoire est présent dans la signature.
La section 4
décrit le format des enregistrements DS
pour GOST. La clé publique de
la zone fille est condensée par GOST R 34.11-2012, algorithme de
numéro
5.
Les sections 5 et 6 couvrent des questions pratiques liées au développement et au déploiement de systèmes GOST, par exemple un rappel sur la taille de la clé (512 bits) et sur celle du condensat cryptographique (256 bits).
GOST peut se valider avec Unbound si la bibliothèque de cryptographie utilisée gère GOST. Et, comme indiqué plus haut, ce ne sera sans doute que l'ancienne version, celle du RFC 5933. Pour les programmeurs Java, DNSjava a le dernier GOST depuis la version 3.6.2. Pour le statut (recommandé ou non) de l'algorithme GOST pour DNSSEC, voir le RFC 8624. En Python, dnspython en version 2.7.0 n'a que l'ancien algorithme.
Date de publication du RFC : Avril 2024
Auteur(s) du RFC : U. Sharma (Igalia, S.L.), C. Bormann (Universität Bremen TZI)
Chemin des normes
Réalisé dans le cadre du groupe de travail IETF sedate
Première rédaction de cet article le 29 avril 2024
Ce RFC modifie légèrement le format des estampilles temporelles du RFC 3339, changeant à la marge une définition et, surtout, permettant d'y attacher des informations supplémentaires.
Le RFC 3339 décrit un des formats d'estampilles temporelles possibles sur l'Internet (car, malheureusement, toutes les normes Internet ne l'utilisent pas). Un exemple, sur Unix :
% date --rfc-3339=seconds 2024-04-29 06:22:33+00:00
Le format du RFC 3339 est du type « YYYY-MM-DD HH:MM:SS » avec, à la fin, ajout d'une information sur le décalage avec le temps de référence (on verra que notre nouveau RFC 9557 change un peu cette dernière information). Un format gros boutien, donc, qui permet notamment de trier les dates-et-heures uniquement en suivant l'ordre lexicographique des caractères.
Mais certaines applications voudraient en savoir plus, et ajouter
à ce format des détails. D'ailleurs, c'est déjà fait dans des
solutions non normalisées, comme le
format de Java, très populaire, qui permet des estampilles
comme 2011-12-03T10:15:30+01:00[Europe/Paris]
(fuseau horaire, ajouté après la date au
format du RFC 3339 ; lisez le RFC 6557 pour en savoir davantage sur les fuseaux
horaires).
Ce nouveau RFC prévoit donc une extension optionnelle (les dates-et-heures qui suivaient le format de l'ancien RFC restent parfaitement valdies), compatible avec celle de Java, et généraliste (on pourra indiquer autre chose que le fuseau horaire). Ce format étendu est baptisé IXDTF pour Internet Extended Date/Time Format. En revanche, le nouveau format ne gère pas des cas compliqués (la gestion du temps en informatique est toujours compliquée) comme les dates futures lorque la définition du fuseau horaire changera, par exemple en supprimant l'heure d'été, ou des échelles de temps différentes, comme TAI (le format IXDTF ne marche que pour l'échelle d'UTC).
Donc, concrètement, notre RFC commence par changer un peu la définition du décalage à la fin des estampilles. Dans le RFC 3339, il y avait trois cas subtilement différents pour une estampille indiquant l'absence de décalage :
+00:00
indiquait qu'on utilisait UTC
comme référence (identique au cas précédent),-00:00
indiquait qu'on n'avait
aucune idée sur la référence (typiquement parce qu'on voudrait
connaitre l'heure locale mais qu'on ne la
connait pas).
(Au passage, si vous ne connaissiez pas ces trois cas et leurs
différences, ne vous inquiétez pas, vous n'étiez pas seul·e.) Notre
RFC change cela (section 2) en décidant que Z est désormais synonyme
de -00:00
plutôt que de
+00:00
, un changement qui aura sans doute peu
d'importance en pratique.
L'autre nouveauté de ce RFC 9557 est plus marquante, c'est le format étendu IXDTF (section 3). Il consiste à ajouter à la fin de l'estampille une série (facultative) de couples {clé, valeur}, entre crochets. Si la clé est absente, c'est que la valeur est un fuseau horaire, suivant la syntaxe de la base TZ. Voici un exemple :
2022-07-08T12:14:37+02:00[Europe/Paris][u-ca=hebrew]
Cet exemple indique que le fuseau horaire était celui de
Paris (cf. le format de
ces noms de fuseaux) et que le calendrier préféré (clé
u-ca
), pour afficher cette date, serait le
calendrier hébreu (ce qui indiquera 9 Tammouz
5782, sauf erreur).
Un point d'exclamation avant la clé indiquerait que la clé doit être comprise par le lecteur, sinon, il faut qu'il ignore l'estampille temporelle (la plupart du temps, l'application peut ignorer les clés et leurs valeurs). Un trait bas indique une clé expérimentale, non officiellement enregistrée.
Car notre RFC crée aussi un registre
des clés. Pour l'instant, il ne compte qu'une seule clé,
u-ca
, pour indiquer le calendrier préféré. Pour
enregistrer une nouvelle clé, il faut une spécification écrite
(cf. RFC 8126), mais il y a aussi des
enregistrements temporaires, plus légers.
Et on termine par un petit mot sur la sécurité (section 7). Le RFC rappelle que plus on donne d'informations, plus on risque d'en donner trop. Ainsi, l'indication du calendrier préféré peut indiquer une origine ethnique ou religieuse, qui peut être considérée comme privée, surtout s'il y a des risques d'attaques racistes.
Date de publication du RFC : Mars 2024
Auteur(s) du RFC : R. Housley (Vigil Security)
Chemin des normes
Réalisé dans le cadre du groupe de travail IETF lamps
Première rédaction de cet article le 22 novembre 2024
Ce court RFC ajoute aux certificats PKIX du RFC 5280 la possibilité de contenir des adresses de courrier électronique dont la partie locale est en Unicode. Et il modifie légèrement les règles pour les noms de domaine en Unicode dans les certificats. Il remplace le RFC 8399, avec peu de changements.
Les certificats sur
l'Internet sont normalisés dans le RFC 5280, qui décrit un profil de
X.509 nommé PKIX
(définir un profil était nécessaire car la norme X.509 est bien trop
riche et complexe). Ce RFC 5280 permettait des
noms de domaine en
Unicode (sections 4.2.1.10 et 7 du RFC 5280) mais il suivait l'ancienne norme IDN, celle des
RFC 3490 et suivants. Depuis, les IDN sont
normalisés dans le RFC 5890 et suivants, et
notre nouveau RFC 9549 modifie très légèrement le RFC 5280 pour s'adapter à cette nouvelle norme de
noms de domaines en Unicode. Les noms de domaine dans un certificat
peuvent être présents dans les champs Sujet (titulaire du
certificat) et Émetteur (AC ayant signé le certificat) mais aussi
dans les contraintes sur le nom (une autorité de certification peut être
limitée à des noms se terminant en example.com
,
par exemple).
Notez que, comme avant, ces noms sont exprimés dans le certificat
en Punycode (RFC 3492,
xn--caf-dma.fr
au lieu de
café.fr
), appelé A-label
dans notre RFC. C'est un bon exemple du fait que les
limites qui rendaient difficiles d'utiliser des noms de domaine en
Unicode n'avaient rien à voir avec le DNS (qui n'a jamais été limité à
ASCII, contrairement à ce qu'affirme une légende
courante). En fait, le problème venait des applications (comme
PKIX), qui ne s'attendaient pas à des noms en
Unicode. Un logiciel qui traite des certificats aurait été bien
étonné de voir des noms de domaines non-ASCII, et
aurait peut-être planté. D'où ce choix du
Punycode (A-label) par opposition à la forme plus
lisible du U-label.
Nouveauté plus importante du RFC 8399, mis à jour par notre RFC 9549, les adresses de courrier électronique en Unicode (EAI pour Email Address Internationalization). Elles étaient déjà permises par la section 7.5 du RFC 5280, mais seulement pour la partie domaine (à droite du @). Désormais, elles sont également possibles dans la partie locale (à gauche du @). Le RFC 8398 donne tous les détails sur ce sujet.
Reste à savoir quelles AC acceptent
Unicode. En 2018, j'avais testé avec
Let's Encrypt (avec le
client Dehydrated, en
mettant le Punycode dans domains.txt
) et ça
marchait, regardez le certificat de
. Le voici, affiché
par GnuTLS :
https://www.potamochère.fr/
% gnutls-cli www.potamochère.fr ... - subject `CN=www.xn--potamochre-66a.fr', issuer `CN=R10,O=Let's Encrypt,C=US', serial 0x03a34cfc017b4311da0b21797cd250ebd3c0, RSA key 4096 bits, signed using RSA-SHA256, activated `2024-11-01 05:26:59 UTC', expires `2025-01-30 05:26:58 UTC', pin-sha256="6y…
D'autres AC acceptent ces noms en Unicode : Gandi le fait aussi, par exemple. On notera que le célèbre service de test de la qualité des configurations TLS, SSLlabs, gère bien les IDN :
Enfin, le registre du
.ru
a participé au développement de
logiciels pour traiter l'Unicode dans les certificats.
La section 1.2 résume les changements depuis le RFC 8399. Notamment :
♚.example
ne
serait pas légal. Ce point est désormais explicite.xn--potamochre-66a.fr
et pas
potamochère.fr
). Cela permet d'éviter de
déclencher certaines
failles de sécurité.Date de publication du RFC : Février 2024
Auteur(s) du RFC : J. Arkko, C. S. Perkins, S. Krishnan
Pour information
Première rédaction de cet article le 24 avril 2024
La question de l'empreinte environnementale du numérique suscite beaucoup de débats. L'IAB avait organisé en décembre 2022 un atelier sur le cas de l'empreinte environnementale de l'Internet dont ce RFC est le compte-rendu (tardif, oui, je sais, mais mon propre article de résumé du RFC est aussi en retard).
Le sujet est très complexe, relativement nouveau, et surtout noyé sous beaucoup d'approximations, voire de franches bêtises (l'ADEME s'en est fait une spécialité, avec des chiffres tirés du chapeau et à la méthodologie inconnue). Il y a donc du travail sur la planche. L'IAB commence par estimer que l'Internet a certes une empreinte environnementale mais peut aussi servir à diminuer l'empreinte environnementale globale, ce qui n'est franchement pas étayé (le RFC cite l'exemple de réunions physiques remplacées par des réunions en ligne, sans citer de calcul détaillé qui permettrait de voir s'il y a vraiment un gain, et en oubliant que de toute façon une réunion en ligne ne rend pas les mêmes services). Mais l'IAB note aussi que l'Internet a des effets indirects, et pas forcément positifs : il cite l'exemple de l'augmentation de la consommation de biens matériels que produit le commerce en ligne.
Clairement, l'Internet n'est pas virtuel, contrairement à ce que prétend le marketing qui abuse de termes comme cloud pour faire croire que le numérique est immatériel. A contrario, l'Internet dépend de machines, de l'électricité (et des humains qui font fonctionner ces machines). Que peut-on faire pour diminuer l'empreinte environnementale de l'Internet ? (Sans pour autant suivre les conseils débiles de l'ADEME, comme de supprimer ses messages.)
Comme tous les ateliers de l'IAB, celui-ci a fonctionné en demandant aux participants des position papers expliquant leur point de vue. Ne participent à l'atelier que des gens ayant écrit un de ces articles, ce qui garantit que tout le monde a dû travailler le sujet. Ces articles sont disponibles en ligne, plus exactement à cet endroit. (Tous les documents liés à cet atelier sont également disponibles.) Parmi les papiers acceptés :
La section 2 du RFC détaille les sujets qui étaient dans le programme de l'atelier :
Ah et, si vous vous le demandez, l'atelier a été entièrement en ligne (section 2.1 du RFC).
La première des quatre sessions de l'atelier essayait d'aborder le problème de manière générale. Le problème du réchauffement climatique est évidemment bien plus vaste que l'Internet seul et n'a pas de solution simple et unique. Et les solutions ne sont pas toutes techniques, donc il y a des limites à ce que l'IETF peut faire (ce qui ne veut pas dire qu'il ne faut rien faire !). Même la publicité est mentionnée dans cette section du RFC, avec un très prudent « davantage d'études sont nécessaires » (opinion personnelle : son attitude au sujet de la publicité est un bon moyen de savoir si votre interlocuteur veut sérieusement lutter contre le réchauffement climatique, ou bien s'il veut juste faire des discours).
Ensuite, deuxième session sur les mesures et la récolte des faits. Par exemple, où sont les gros postes de consommation électrique dans le numérique ? Les serveurs ? Les routeurs ? Les terminaux ? C'est d'autant plus important que le RFC note la quantité de fausses informations qui circulent (citant par exemple un article qui confondait MB/s et Mb/s, soit un facteur 8 de différence). De même, contrairement à ce qui est encore souvent dit, la session a mis en évidence le fait que la consommation électrique n'est pas du tout proportionnelle au trafic. Des phrases comme « envoyer un courrier dégage autant de dioxyde de carbone qu'un vol Paris-Quelquepart » n'ont donc aucun sens. (Un des papiers acceptés, « Towards a power-proportional Internet » expliquait pourquoi il fallait changer cela et comment le faire.) Par contre, les usages impactent la consommation car ils peuvent nécessiter des mises à jour du réseau.
La troisième session regardait du côté des pistes d'amélioration,
plus précisement de celles sur lesquelles l'IETF pouvait agir. Le
premier point est celui des mesures (insuffisantes et parfois
contradictoires). Le deuxième point concernait l'influence de
phénomènes comme la gigue (RFC 4689) ou l'élongation du
trajet (RFC 7980) sur la consommation énergétique (si on
réduit ces phénomènes grâce à de meilleurs protocoles, est-ce qu'on
diminue la consommation ?). Parmi les autres optimisations
possibles, le choix de meilleurs formats, plus optimisés
(CBOR - RFC 8949 - est
cité). Notez qu'un des articles acceptés pour l'atelier faisait le
point sur toutes les activités de l'IETF liées à l'énergie,
draft-eckert-ietf-and-energy-overview
.
Et la quatrième et dernière session portait sur les étapes suivantes du travail ; en résumé, il y a du travail et, même si l'IETF ne peut pas tout, elle doit en prendre sa part. Il faut toujours garder en tête que le but n'est pas de réduire l'empreinte environnementale de l'Internet mais de réduire celle de l'ensemble de la société. Éteindre l'Internet diminuerait certainement son empreinte environnementale mais pourrait avoir des effets négatifs sur d'autres secteurs, comme les transports. Pour améliorer l'Internet sans le supprimer, plusieurs axes ont été mis en avant :
Et les actions concrètes (section 2.4.3) ?
Si vous voulez participer au nouveau programme de l'IAB E-IMPACT, tout est là.
Puisqu'on parlait de la section sur la sécurité qui est obligatoire dans les RFC, notre RFC en a une, qui rappelle que :
Date de publication du RFC : Mars 2024
Auteur(s) du RFC : J. Gould, D. Smith (VeriSign), J. Kolker, R. Carney (GoDaddy)
Chemin des normes
Réalisé dans le cadre du groupe de travail IETF regext
Première rédaction de cet article le 14 juillet 2024
RDAP est le protocole recommandé pour accéder aux données sociales sur un nom de domaine, comme le nom du titulaire ou son adresse postale. Pour d'évidentes raisons de vie privée, certains registres ne renvoient pas la totalité de l'information dont ils disposent. Que doit-on mettre dans RDAP dans ce cas ? La question n'était pas tranchée et chaque registre faisait différemment. Désormais, il existe une solution normalisée.
Au passage, oui, d'accord, il n'y a pas que
RDAP pour obtenir les données sociales (cet
article vous en indiquera d'autres). Mais c'est le service le
plus moderne et le plus adapté
aux programmeur·ses. Contacté en HTTPS, le serveur RDAP va
renvoyer du JSON que le client n'aura plus qu'à filtrer et
formatter. Voici par exemple une partie de la réponse RDAP obtenue
en se renseignant sur
nouveaufrontpopulaire.fr
:
% curl -s https://rdap.nic.fr/domain/nouveaufrontpopulaire.fr | jq … [ "fn", {}, "text", "Parti Socialiste" ], [ "org", {}, "text", "Parti Socialiste" ], [ "adr", {}, "text", [ "", "", "99 Rue Moliere", "Ivry-sur-Seine", "", "94200", "FR" ] ], …
Ici, il s'agit d'une personne morale donc les données sont toutes envoyées. Et s'il s'agissait d'une personne physique, pour laquelle la loi Informatique & et Libertés s'applique, depuis 1978 ? La solution évidente est de ne pas envoyer les données qu'on ne veut pas diffuser mais attention, il y a un piège, il ne faut pas casser la syntaxe JSON. Par exemple, RDAP utilise (c'est en cours de changement, cf. RFC 9553) jCard pour formater les adresses (RFC 7095) et les champs dans jCard ne sont pas étiquetés, c'est leur position dans le tableau qui indique leur rôle (c'est un des nombreux inconvénients de jCard). On ne peut donc pas supprimer, par exemple, la rue, en indiquant :
[ "adr", {}, "text", [ "", "", "Ivry-sur-Seine", "", "94200", "FR" ] ], [ "email", {}, "text", "e5d92838d5f0268143ac47d86880b5f7-48916400@contact.gandi.net" ],
Car, alors, on ne saurait plus si "Ivry-sur-Seine" est la rue ou bien la ville.
Le principe de notre RFC est donc : si on peut, retirer le membre
JSON. Si on ne peut pas (cas du tableau de taille fixe), mettre une
valeur vide ou null
.
Petit point de terminologie : comment traduire le redacted du titre ? « Censuré » est inadapté (l'intervention ne vient pas d'un tiers mais d'une des deux parties). Je vais dire « élidé », mais « caviardé », « biffé » et « expurgé » sont également de bonnes solutions. Le nom correspondant est « élision ». Évidemment, il ne faut surtout pas dire « anonymisé », il n'y a rien d'anonyme ici, puisque le registre connait toute l'information, il refuse simplement de la diffuser.
La section 1 du RFC expose les grands principes de
l'élision. Elle explique notamment qu'en cas d'élision, il faut
ajouter un membre (nommé redacted
) à la réponse
JSON, expliquant les raisons et utilisant le langage
JSONPath (RFC 9535)
pour désigner de manière formelle la partie élidée.
Compte-tenu des contraintes sur la syntaxe de la réponse JSON (RFC 9083), le RFC normalise quatre façons d'élider (section 3) :
"label":"123 Maple
Ave\nSuite 901\nVancouver BC\nCanada"
(un
exemple du RFC 7095) peut être remplacée par une valeur
moins précise comme "label":"Vancouver\nBC\nCanada\n"
.e5d92838d5f0268143ac47d86880b5f7-48916400@contact.gandi.net
dans l'exemple plus haut.Et le RFC insiste qu'il ne faut pas utiliser de texte bidon (« XXX », « lorem ipsum dolor » ou « Ano Nymous ») car ce texte ne correspond pas forcément aux règles de syntaxe du champ (et, j'ajoute, peut être difficile à identifier pour le lecteur, qui peut ne pas avoir la référence).
Pour la première méthode, la suppression d'un champ, si on
supprime le titulaire, on aura un membre nommé
redacted
(élidé) ajouté ainsi :
"redacted": [ { "name": { "description": "Remove registrant" }, "prePath": "$.entities[?(@.roles[0]=='registrant')]", "method": "removal" } ]
Notez le (difficile à lire) code JSONPath
$.entities[?(@.roles[0]=='registrant')]
.
Le deuxième cas, celui d'une valeur vide, donnerait, pour le cas où on supprime juste le nom du titulaire (qui est en position 1 dans le jCard, et son nom en position 3 - sachant qu'on part de 0) :
[ "fn", {}, "text", "" ] … "redacted": [ { "name": { "description": "Registrant Name" }, "postPath": "$.entities[?(@.roles[0]=='registrant')]. vcardArray[1][?(@[0]=='fn')][3]", "pathLang": "jsonpath", "method": "emptyValue", "reason": { "description": "Server policy" } } ]
Troisième technique d'élision, réduire une valeur. Le
redacted
devient :
"redacted": [ { "name": { "description": "Home Address Label" }, "postPath": "$.vcardArray[1][?(@[0]=='adr')][1].label", "pathLang": "jsonpath", "method": "partialValue", "reason": { "description": "Server policy" } } ]
Et pour finir, la quatrième et dernière méthode, le remplacement :
"redacted": [ { "name": { "description": "Registrant Email" }, "postPath": "$.entities[?(@.roles[0]=='registrant')]. vcardArray[1][?(@[0]=='email')][3]", "pathLang": "jsonpath", "method": "replacementValue", } ]
Ce membre appelé redacted
est spécifié en
détail dans la section 4 du RFC. (Et il est enregistré à l'IANA parmi les extensions RDAP.) Pour signaler qu'il peut
apparaitre, le membre rdapConformance
de la
réponse JSON va l'indiquer :
{ "rdapConformance": [ "itNic", "redacted", "rdap_level_0" ], …
Dès qu'il y a élision, redacted
doit être
ajouté. Il contient un tableau JSON d'objets, dont les membres
peuvent être (seul le premier est obligatoire) :
name
: un terme qui décrit le champ
élidé,prePath
et
postPath
: des expressions JSONPath (RFC 9535) qui
dénotent le membre retiré ou modifié,method
: la technique d'élision
utilisée (suppression, nettoyage, remplacement, etc),reason
: texte libre décrivant la
raison de l'élision.Date de publication du RFC : Avril 2024
Auteur(s) du RFC : M. Loffredo, M. Martinelli (IIT-CNR/Registro.it)
Chemin des normes
Réalisé dans le cadre du groupe de travail IETF regext
Première rédaction de cet article le 28 octobre 2024
Ce RFC normalise une extension au protocole d'accès à l'information RDAP pour permettre des recherches inversées, des recherches par le contenu (« quels sont tous les noms de domaine de cette personne ? »).
Alors, tout d'abord, un avertissement. L'extension normalisée dans ce RFC est dangereuse pour la vie privée. Je détaille ce point plus loin mais ne réclamez pas tout de suite le déploiement de cette extension : lisez tout d'abord. C'est dès sa section 1 que le RFC met en garde contre un déploiement hâtif !
RDAP (normalisé dans les RFC 9082 et RFC 9083), comme son prédécesseur whois, permet d'obtenir des informations dites sociales (nom, adresse, numéro de téléphone, etc) sur les personnes (morales ou physiques) associées à une ressource Internet réservée, comme un nom de domaine ou un adresse IP. Une limite importante de ces deux protocoles est qu'il faut connaitre l'identifiant (nom de domaine, adresse IP) de la ressource. Or, certaines personnes seraient intéressés à faire l'inverse, découvrir les ressources à partir d'informations sociales. C'est le cas par exemple de juristes cherchant le portefeuille de noms de domaine de quelqu'un qu'ils soupçonnent de menacer leur propriété intellectuelle. Ou de chercheurs en sécurité informatique étudiant toutes les adresses IP utilisées par le C&C d'un botnet et voulant en découvrir d'autres. Ce RFC normalise justement un moyen de faire des recherches inverses. whois n'avait jamais eu une telle normalisation, à cause des risques pour la vie privée, risques qui sont peut-être moins importants avec RDAP.
(Notez quand même que le RFC 9082, section 3.2.1, prévoyait déjà certaines recherches inverses, d'un domaine à partir du nom ou de l'adresse d'un de ses serveurs de noms.)
Passons aux détails techniques (section 2). L'URL
d'une requête inverse va inclure dans son chemin
/reverse_search
et, bien sûr, des critères de
recherche. Par exemple,
/domains/reverse_search/entity?fn=Jean%20Durand&role=registrant
donnera tous les domaines dont le titulaire
(registrant) se nomme Jean Durand
(fn
est défini dans la section 6.2.1 du RFC 6350). Le terme après
/reverse_search
indique le type des données
auxquelles s'appliquent les critères de recherche (actuellement,
c'est forcément entity
, une personne morale ou
physique).
La plupart des recherches inverses porteront sans doute sur quelques champs comme l'adresse de courrier électronique ou le handle (l'identifiant d'une entité). La section 8 décrit les possibilités typiques mais un serveur RDAP choisit de toute façon ce qu'il permet ou ne permet pas.
La sémantique exacte d'une recherche inverse est décrite en
JSONPath (RFC 9535). Vous trouvez le JSONPath dans la réponse (section
5 du RFC), dans le membre
reverse_search_properties_mapping
. Par exemple :
"reverse_search_properties_mapping": [ { "property": "fn", "propertyPath": "$.entities[*].vcardArray[1][?(@[0]=='fn')][3]" } ]
L'expression JSONPath
$.entities[*].vcardArray[1][?(@[0]=='fn')][3]
indique que le serveur va faire une recherche sur le membre
fn
du tableau vCard
(RFC 7095). Oui, elle est compliquée, mais
c'est parce que le format vCard est compliqué. Heureusement, on
n'est pas obligé de connaitre JSONPath pour utiliser les recherches
inverses, uniquement pour leur normalisation (section 3).
Pour savoir si le serveur RDAP que vous interrogez gère cette
extension et quelles requêtes il permet, regardez sa réponse à la
question help
(RFC 9082,
section 3.1.6, et RFC 9083, section 7) ; s'il
accepte les requêtes inverses, vous y trouverez la valeur
reverse_search
dans le membre
rdapConformance
(section 9), par exemple :
"rdapConformance": [ "rdap_level_0", "reverse_search" ]
Pour les recherches acceptées, regardez le membre
reverse_search_properties
. Par exemple :
"reverse_search_properties": [ { "searchableResourceType": "domains", "relatedResourceType": "entity", "property": "fn" } ]
Ici, le serveur indique qu'il accepte les requêtes inverses pour trouver des noms de domaine en fonction d'un nom d'entité (ce que nous avons fait dans l'exemple plus haut). Voir la section 4 pour les détails. Si vous tentez une requête inverse sur un serveur, et que le serveur n'accepte pas les requêtes inverses, ou tout simplement n'accepte pas ce type particulier de recherche inverse que vous avez demandé, il répondra avec le code de retour HTTP 501 (section 7). Vous aurez peut-être aussi un 400 si votre requête déplait au serveur pour une raison ou l'autre.
L'extension est désormais placée dans le registre des extensions RDAP. En outre, deux nouveaux registres sont créés, RDAP Reverse Search, pour les recherches possibles et RDAP Reverse Search Mapping pour les règles JSONPath. Pour y ajouter des valeurs, la politique (RFC 8126) est « spécification nécessaire ».
Revenons maintenant aux questions de vie privée (RFC 6973), que je vous avais promises. La section 12 du RFC détaille le problème. La puissance des recherches inverses les rend dangereuses. Une entreprise concurrente pourrait regarder vos noms de domaine et ainsi se tenir au courant, par exemple, d'un nouveau projet ou d'un nouveau produit. Un malveillant pourrait regarder les noms de domaine d'une personne et identifier ainsi des engagements associatifs que la personne ne souhaitait pas forcément rendre très visibles. Les données stockées par les registres sont souvent des données personnelles et donc protégées par des lois comme le RGPD. Le gestionnaire d'un serveur RDAP doit donc, avant d'activer la recherche inverse, bien étudier la sécurité du serveur (section 13) mais aussi (et ce point n'est hélas pas dans le RFC) se demander si cette activation est vraiment une bonne idée.
En théorie, RDAP pose moins de problème de sécurité que whois pour ce genre de recherches, car il repose sur HTTPS (le chiffrement empêche un tiers de voir questions et réponses) et, surtout, il permet l'authentification ce qui rend possible, par exemple, de réserver les recherches inverses à certains privilégiés, avec un grand choix de contrôle d'accès (cf. annexe A). Évidemment, cela laisse ouvertes d'autres questions comme « qui seront ces privilégiés ? » et « comment s'assurer qu'ils n'abusent pas ? » (là, les journaux sont indispensables pour la traçabilité, cf. l'affaire Haurus).
Notons qu'outre les problèmes de vie privée, la recherche inverse pose également des problèmes de performance (section 10). Attention avant de la déployer, des requêtes apparemment innocentes pourraient faire ramer sérieusement le serveur RDAP. Si vous programmez un serveur RDAP ayant des recherches inverses, lisez bien les recommandations d'optimisation de la section 10, par exemple en ajoutant des index dans votre SGBD. Et le serveur ne doit pas hésiter, en cas de surcharge, à répondre seulement de manière partielle (RFC 8982 ou RFC 8977).
Je n'ai pas trouvé de code public mettant en œuvre ces recherches inverses. De même, je ne connais pas encore de serveur RDAP déployé qui offre cette possibilité.
Date de publication du RFC : Mars 2024
Auteur(s) du RFC : P. Hoffman (ICANN), K. Fujiwara
(JPRS)
Réalisé dans le cadre du groupe de travail IETF dnsop
Première rédaction de cet article le 3 mai 2024
Comme beaucoup de protocoles très utilisés sur l'Internet, le DNS est ancien. Très ancien (la première norme, le RFC 882, date de 1983). Les normes techniques vieillissent, avec l'expérience, on comprend mieux, on change les points de vue et donc, pour la plupart des protocoles, on se lance de temps en temps dans une révision de la norme. Mais le DNS est une exception : la norme actuelle reste fondée sur des textes de 1987, les RFC 1034 et RFC 1035. Ces documents ne sont plus à jour, modifiés qu'ils ont été par des dizaines d'autres RFC. Bref, aujourd'hui, pour comprendre le DNS, il faut s'apprêter à lire de nombreux documents. En attendant qu'un courageux et charismatique participant à l'IETF se lance dans la tâche herculéenne de faire un jeu de documents propre et à jour, ce RFC 9499, successeur du RFC 8499 (il y a peu de modifications), se limite à une ambition plus modeste : fixer la terminologie du DNS. Ce n'est pas un tutoriel : vous n'y apprendrez pas le DNS. C'est plutôt une encyclopédie.
En effet, chacun peut constater que les discussions portant sur le DNS sont difficiles : on manque de terminologie standard, et celle des RFC officielles ne suffit pas, loin de là. Souvent, le DNS a tellement changé que le RFC officiel est même trompeur : les mots ne veulent plus dire la même chose. D'autres protocoles ont connu des mises à jour de la norme. Cela a été le cas de SMTP, passé successivement du RFC 772 à l'actuel RFC 5321, en passant par plusieurs révisions successives. Ou de XMPP, qui a vu sa norme originale mise à jour dans le RFC 6120. Et bien sûr de HTTP, qui a connu plusieurs toilettages complets (cf. RFC 9110). Mais personne n'a encore osé faire pareil pour le DNS. Au moins, ce RFC 9499, comme son prédécesseur RFC 8499, traite l'un des problèmes les plus criants, celui du vocabulaire. Le RFC est évidemment en anglais, les traductions proposées dans cet article, et qui n'ont pas de valeur « officielle » sont de moi seul.
Notre RFC 9499 rassemble donc des définitions pour des termes qui étaient parfois précisément définis dans d'autres RFC (et il fournit alors un lien vers ce RFC original), mais aussi qui n'étaient définis qu'approximativement ou parfois qui n'étaient pas définis du tout (et ce RFC fournit alors cette définition). Du fait du flou de certains RFC anciens, et des changements qui ont eu lieu depuis, certaines définitions sont différentes de l'original. Le document a fait l'objet d'un consensus relatif auprès des experts DNS mais quelques termes restent délicats. Notez aussi que d'autres organisations définissent des termes liés au DNS par exemple le WHATWG a sa définition de ce qu'est un domaine, et le RSSAC a développé une terminologie.
Ironiquement, un des termes les plus difficiles à définir est
« DNS » lui-même (section 1 de notre RFC). D'accord, c'est le sigle
de Domain Name System mais ça veut dire quoi ?
« DNS » peut désigner le schéma de nommage (les noms de domaine comme
signal.eu.org
, leur syntaxe, leurs
contraintes), la base de données répartie (et faiblement cohérente)
qui associe à ces noms des informations (comme des certificats, des
adresses IP, etc), ou le
protocole
requête/réponse (utilisant le port 53) qui permet d'interroger cette
base. Parfois, « DNS » désigne uniquement le protocole, parfois,
c'est une combinaison des trois éléments indiqués plus haut
(personnellement, quand j'utilise « DNS », cela désigne uniquement
le protocole, un protocole relativement simple, fondé sur l'idée
« une requête => une réponse »).
Bon, et ces définitions rigoureuses et qui vont mettre fin aux discussions, ça vient ? Chaque section du RFC correspond à une catégorie particulière. D'abord, en section 2, les noms eux-même, ces fameux noms de domaine. Un système de nommage (naming system) a plusieurs aspects, la syntaxe des noms, la gestion des noms, le type de données qu'on peut associer à un nom, etc. D'autres systèmes de nommage que celui présenté dans ce RFC existent, et sont distincts du DNS sur certains aspects. Pour notre système de nommage, le RFC définit :
www.madmoizelle.com
), le vocabulaire s'en
ressent. Par exemple, on va dire que com
est « au-dessus de
madmoizelle.com
» (vision arborescente) ou
bien « à la fin de www.madmoizelle.com
»
(vision texte). Notez aussi que la représentation des noms de
domaine dans les paquets IP n'a rien à voir avec leur représentation
texte (par exemple, les points n'apparaissent pas). Enfin, il faut
rappeler que le vocabulaire « standard » n'est pas utilisé
partout, même à l'IETF, et qu'on voit parfois « nom de domaine »
utilisé dans un sens plus restrictif (par exemple uniquement pour
parler des noms pouvant être résolus avec le DNS, pour lesquels il
avait été proposé de parler de DNS
names).ldap.potamochère.fr.
est un FQDN alors que
ldap
tout court ne l'est pas). En toute
rigueur, un FQDN devrait toujours s'écrire avec un point à la fin
(pour représenter la racine) mais ce n'est pas toujours le
cas. (Notre RFC parle de « format de présentation » et de « format
courant d'affichage » pour distinguer le cas où on met
systématiquement le point à la fin et le cas où on
l'oublie.)www.laquadrature.net
, il y a trois
composants, www
,
laquadrature
et
net
.brienne.tarth.got.example
peut être un nom
de machine mais www.&#$%?.example
ne
peut pas l'être. Le terme de « nom de machine » est parfois
aussi utilisé pour parler du premier composant d'un nom de
domaine (brienne
dans
brienne.tarth.got.example
).fr
ou
name
sont des TLD. N'utilisez surtout pas le terme erroné
d'« extension ». Et ne dites pas que le TLD est le
composant le plus à droite, ce n'est pas vrai dans
l'alphabet arabe. La distinction courante
entre gTLD, gérés par l'ICANN, et
ccTLD, indépendants de l'ICANN, est
purement politique et ne se reflète pas dans le DNS.www.cl.cam.ac.uk
est un sous-domaine de
cl.cam.ac.uk
, qui est un sous-domaine de
cam.ac.uk
et ainsi de suite, jusqu'à la
racine, le seul domaine à n'être sous-domaine de
personne. Quand le RFC parle de suffixe, il s'agit d'un suffixe
de composants, pas de caractères :
foo.example.net
n'est pas un sous-domaine
de oo.example.net
.vader IN CNAME anakin
, l'alias est
vader
(et c'est une erreur de dire que
c'est « le CNAME »).anakin
est le CNAME,
le « nom canonique ».Fini avec les noms, passons à l'en-tête des messages DNS et aux codes qu'il peut contenir. Cet en-tête est défini dans le RFC 1035, section 4.1. Il donne des noms aux champs mais pas forcément aux codes. Ainsi, le code de réponse 3 indiquant qu'un domaine demandé n'existe pas est juste décrit comme name error et n'a reçu son mnémonique de NXDOMAIN (No Such Domain) que plus tard. Notre RFC définit également, dans sa section 3 :
Voici un renvoi depuis la racine vers .fr
:
% dig @l.root-servers.net A blog.imirhil.fr ... ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 16572 ;; flags: qr rd; QUERY: 1, ANSWER: 0, AUTHORITY: 5, ADDITIONAL: 11 ... ;; AUTHORITY SECTION: fr. 172800 IN NS d.ext.nic.fr. fr. 172800 IN NS d.nic.fr. fr. 172800 IN NS e.ext.nic.fr. fr. 172800 IN NS f.ext.nic.fr. fr. 172800 IN NS g.ext.nic.fr.
La section 4 s'intéresse aux transactions DNS. Un des termes définis est celui de QNAME (Query NAME), nouveauté du RFC 8499. Il y a trois sens possibles (le premier étant de très loin le plus commun) :
Le RFC 2308 est clairement en tort ici. Il aurait dû utiliser un terme nouveau, pour le sens nouveau qu'il utilisait.
Passons maintenant aux enregistrements DNS, stockés dans cette base de données répartie (section 5 du RFC) :
Voici un ensemble d'enregistrements (RRset), comptant ici deux enregistrements :
rue89.com. 600 IN MX 50 mx2.typhon.net. rue89.com. 600 IN MX 10 mx1.typhon.net.
(Depuis, ça a changé.)
Et voici un pseudo-enregistrement OPT, tel qu'affiché par dig, avec une indication de la taille maximale et l'option client subnet (RFC 7871) :
;; OPT PSEUDOSECTION: ; EDNS: version: 0, flags: do; udp: 512 ; CLIENT-SUBNET: 13.176.144.0/24/0
Ensuite, un sujet chaud où le vocabulaire est particulièrement peu défini, et très mal utilisé (voir les forums grand public sur le DNS où les discussions prennent un temps fou car les gens utilisent mal les mots) : les différents types de serveurs et clients DNS (section 6). Pour commencer, il est crucial de se méfier quand un texte parle de « serveur DNS » tout court. Si le contexte ne permet pas de savoir de quel genre de serveur DNS on parle, cela indique un manque de compréhension du DNS par l'auteur du texte. Serveurs faisant autorité et serveurs résolveurs, quoique utilisant le même protocole, sont en effet très différents.
getaddrinfo()
ou
getnameinfo()
. Sur
Unix, le résolveur minimum fait en
général partie de la libc et trouve l'adresse du ou des résolveurs
complets dans /etc/resolv.conf
.f.root-servers.net
fait autorité pour la
racine, d.nic.fr
fait autorité pour
pm
,
etc. Des logiciels comme NSD ou Knot assurent cette fonction. Les serveurs
faisant autorité sont gérés par divers acteurs, les registres, les
hébergeurs DNS (qui sont souvent en même temps bureaux
d'enregistrement), mais aussi parfois par
M. Michu. La commande dig NS $ZONE
vous
donnera la liste des serveurs faisant autorité pour la zone
$ZONE
. Ou bien vous pouvez utiliser un
service sur le Web en visitant
https://dns.bortzmeyer.org/DOMAIN/NS
où
DOMAIN est le nom de domaine qui vous intéresse.NS .
» à un des serveurs de sa
liste. Ainsi, tant qu'au moins un des serveurs de la vieille
liste répond, le résolveur est sûr d'apprendre la liste
actuelle./etc/resolv.conf
le
serveur primaire ») n'a pas de sens.forwarders
). La définition de notre RFC est
le premier sens.www.organisation.example
aille sur le site
public quand on vient de l'Internet mais sur un site interne de
la boîte quand on est sur le réseau local des employés.Voici, vu par tcpdump, un exemple d'initialisation d'un résolveur BIND utilisant la racineYeti (RFC 8483) :
15:07:36.736031 IP6 2a01:e35:8bd9:8bb0:21e:8cff:fe76:29b6.35721 > 2001:6d0:6d06::53.53: \ 21476% [1au] NS? . (28) 15:07:36.801982 IP6 2001:6d0:6d06::53.53 > 2a01:e35:8bd9:8bb0:21e:8cff:fe76:29b6.35721: \ 21476*- 16/0/1 NS yeti-ns.tisf.net., NS yeti-ns.lab.nic.cl., NS yeti-ns.wide.ad.jp., NS yeti.ipv6.ernet.in., NS yeti-ns.as59715.net., NS ns-yeti.bondis.org., NS yeti-dns01.dnsworkshop.org., NS dahu2.yeti.eu.org., NS dahu1.yeti.eu.org., NS yeti-ns.switch.ch., NS bii.dns-lab.net., NS yeti.bofh.priv.at., NS yeti-ns.conit.co., NS yeti.aquaray.com., NS yeti-ns.ix.ru., RRSIG (619)
La question était « NS .
» (quels sont les
serveurs de la racine) et la réponse contenait les noms des seize
serveurs racine qu'avait Yeti à l'époque.
Voici aussi des exemples de résultats avec un résolveur ou bien avec un serveur faisant autorité. Si je demande à un serveur faisant autorité (ici, un serveur racine), avec mon client DNS qui, par défaut, demande un service récursif (flag RD, Recursion Desired) :
% dig @2001:620:0:ff::29 AAAA www.iab.org ... ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 54197 ;; flags: qr rd; QUERY: 1, ANSWER: 0, AUTHORITY: 9, ADDITIONAL: 13 ;; WARNING: recursion requested but not available ... ;; AUTHORITY SECTION: org. 172800 IN NS b0.org.afilias-nst.org. ...
C'est pour cela que dig affiche WARNING: recursion requested but not available. Notez aussi que le serveur, ne faisant autorité que pour la racine, n'a pas donné la réponse mais juste un renvoi aux serveurs d'Afilias. Maintenant, interrogeons un serveur récursif (le service de résolveur public Yandex DNS) :
% dig @2a02:6b8::feed:0ff AAAA www.iab.org ... ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 63304 ;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0 ... ;; ANSWER SECTION: www.iab.org. 1800 IN AAAA 2001:1900:3001:11::2c
Cette fois, j'ai obtenu une réponse, et avec le
flag RA, Recursion
Available. Si je pose une question sans le
flag RD (Recursion Desired,
avec l'option +norecurse
de dig) :
% dig +norecurse @2a02:6b8::feed:0ff AAAA www.gq.com ... ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 59438 ;; flags: qr ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0 ... ;; ANSWER SECTION: www.gq.com. 293 IN CNAME condenast.map.fastly.net.
J'ai obtenu ici une réponse car l'information était déjà dans le cache (la mémoire) de Yandex DNS (on le voit au TTL, qui n'est pas un chiffre rond, il a été décrémenté du temps passé dans le cache). Si l'information n'est pas dans le cache :
% dig +norecurse @2a02:6b8::feed:0ff AAAA blog.keltia.net ... ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 19893 ;; flags: qr ra; QUERY: 1, ANSWER: 0, AUTHORITY: 0, ADDITIONAL: 0 ...
Je n'obtiens alors pas de réponse (ANSWER: 0, donc NODATA). Si je demande au serveur faisant autorité pour cette zone :
% dig +norecurse @2a01:e0d:1:3:58bf:fa61:0:1 AAAA blog.keltia.net ... ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 62908 ;; flags: qr aa; QUERY: 1, ANSWER: 2, AUTHORITY: 6, ADDITIONAL: 15 ... ;; ANSWER SECTION: blog.keltia.net. 86400 IN AAAA 2a01:240:fe5c:1::2 ...
J'ai évidemment une réponse et, comme il s'agit d'un serveur faisant autorité, elle porte le flag AA (Authoritative Answer, qu'un résolveur ne mettrait pas). Notez aussi le TTL qui est un chiffre rond (et qui ne change pas si on rejoue la commande).
Passons maintenant à un concept relativement peu connu, celui de zones, et le vocabulaire associé (section 7) :
gouv.fr
n'est pas une zone séparée, il
est dans la même zone que fr
(cela se teste
facilement : gouv.fr
n'a pas
d'enregistrement NS ou de SOA).wikipedia.org
est org
, le parent de
.org
est la racine.ns1.mazone.example
, le résolveur doit
passer par les serveurs de mazone.example
,
qui est déléguée à ns1.mazone.example
et
ainsi de suite... On rompt ce cercle vicieux en ajoutant, dans
la zone parente, des données qui ne font pas autorité sur les
adresses de ces serveurs (RFC 1034,
section 4.2.1). Il faut donc bien veiller à les garder
synchrones avec la zone fille. (Tanguy Ortolo me suggère
d'utiliser « enregistrement de raccord » plutôt que
« colle ». Cela décrit bien leur rôle, en effet.)ip6.arpa
ou sous les
domaines très longs de certains CDN. Cela se trouve aussi avec les
enregistrements de service : dans
_sip._tcp.example.com
,
_tcp.example.com
est probablement un
ENT. La réponse correcte à une requête DNS pour un ENT est
NODATA (code de réponse NOERROR, liste des répoonses vide) mais
certains serveurs bogués, par exemple, à une époque, ceux
d'Akamai, répondaient NXDOMAIN.
Voyons ici la colle retournée par un serveur faisant autorité (en
l'occurrence un serveur de .net
) :
% dig @a.gtld-servers.net AAAA labs.ripe.net ... ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 18272 ;; flags: qr rd; QUERY: 1, ANSWER: 0, AUTHORITY: 8, ADDITIONAL: 9 ... ;; AUTHORITY SECTION: ripe.net. 172800 IN NS ns3.nic.fr. ripe.net. 172800 IN NS sec1.apnic.net. ripe.net. 172800 IN NS sec3.apnic.net. ripe.net. 172800 IN NS tinnie.arin.net. ripe.net. 172800 IN NS sns-pb.isc.org. ripe.net. 172800 IN NS pri.authdns.ripe.net. ... ;; ADDITIONAL SECTION: sec1.apnic.net. 172800 IN AAAA 2001:dc0:2001:a:4608::59 sec1.apnic.net. 172800 IN A 202.12.29.59 sec3.apnic.net. 172800 IN AAAA 2001:dc0:1:0:4777::140 sec3.apnic.net. 172800 IN A 202.12.28.140 tinnie.arin.net. 172800 IN A 199.212.0.53 tinnie.arin.net. 172800 IN AAAA 2001:500:13::c7d4:35 pri.authdns.ripe.net. 172800 IN A 193.0.9.5 pri.authdns.ripe.net. 172800 IN AAAA 2001:67c:e0::5
On notera :
pri.authdns.ripe.net
: ce serveur étant
dans la zone qu'il sert, sans son adresse IP, on ne pourrait
jamais le joindre.sec1.apnic.net
. Ce n'est pas
strictement indispensable (on pourrait l'obtenir par une
nouvelle requête), juste une optimisation.ns3.nic.fr
et
sns-pb.isc.org
ne sont pas renvoyées. Le
serveur ne les connait probablement pas et, de toute façon,
elles seraient hors-bailliage, donc ignorées par un résolveur prudent.Voyons maintenant, un ENT,
gouv.fr
(notez que, depuis, ce domaine n'est
plus un ENT) :
% dig @d.nic.fr ANY gouv.fr ... ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 42219 ;; flags: qr aa rd; QUERY: 1, ANSWER: 0, AUTHORITY: 2, ADDITIONAL: 1
Le serveur fait bien autorité pour ce domaine (flag AA dans la réponse), le domaine existe (autrement, le status aurait été NXDOMAIN, pas NOERROR) mais il n'y a aucun enregistrement (ANSWER: 0).
Et, ici, une délégation boiteuse, pour
.ni
:
% check-soa ni dns.nic.cr. 2001:13c7:7004:1::d100: ERROR: SERVFAIL 200.107.82.100: ERROR: SERVFAIL ns.ideay.net.ni. 186.1.31.8: OK: 2013093010 ns.ni. 165.98.1.2: ERROR: read udp 10.10.86.133:59569->165.98.1.2:53: i/o timeout ns.uu.net. 137.39.1.3: OK: 2013093010 ns2.ni. 200.9.187.2: ERROR: read udp 10.10.86.133:52393->200.9.187.2:53: i/o timeout
Le serveur dns.nic.cr
est déclaré comme
faisant autorité pour .ni
mais il ne le sait
pas, et répond donc SERVFAIL.
Les jokers ont une section à eux, la section 8 du RFC. S'appuyant sur le RFC 4592, elle définit, entre autres :
*
dans
une zone déclenche la synthèse automatique de réponses pour les
noms qui n'existent pas dans la zone. Si la zone
foo.example
contient
bar.foo.example
et
*.foo.example
, une requête pour
thing.foo.example
renverra le contenu de
l'enregistrement avec le joker, une requête pour
bar.foo.example
donnera les données de
bar.foo.example
. Attention,
l'astérisque n'a son rôle particulier
que s'il est le composant le plus spécifique (le premier). Dans
foo.*.bar.example
, il n'y a pas de
joker.foo.bar.baz.example
n'existe pas, que
bar.baz.example
n'existe pas non plus,
mais que baz.example
existe, alors
baz.example
est l'ancêtre le plus proche
de foo.bar.baz.example
. Ce concept est
nécessaire pour le RFC 5155.Allez courage, ne faiblissons pas, il reste encore la question de l'enregistrement des noms de domaine (section 9) :
bortzmeyer.org
😁.
C'est le registre qui décide de la politique d'enregistrement,
qui peut être très variable selon les zones (sauf dans celles
contrôlées par l'ICANN, où une certaine
uniformité est imposée). Mes lecteurs français noteront que,
comme le terme « registre » est court et largement utilisé, le
gouvernement a inventé
un nouveau mot, plus long et jamais vu auparavant,
« office d'enregistrement »..aquarelle
se trouve dans la
Public Suffix List alors qu'il
s'agit d'un « .marque
», un de ces TLD où
une seule entreprise peut enregistrer des noms. Le terme est ancien mais est apparu
pour la première fois dans un RFC avec le RFC 6265, section 5.3. com
, co.uk
et
eu.org
sont des suffixes publics. Rien dans la syntaxe du nom n'indique
qu'un nom de domaine est un suffixe public, puisque ce statut ne
dépend que d'une politique d'enregistrement (qui peut
changer). Il est parfaitement possible qu'un domaine, et un de
ses enfants, soient tous les deux un suffixe public (c'est le
cas de .org
et
eu.org
).
Prenons par exemple le domaine eff.org
. Au
moment de la publication du RFC :
Enfin, pour terminer, les sections 10 et 11 de notre RFC couvrent DNSSEC. Pas grand'chose de nouveau ici, DNSSEC étant plus récent et donc mieux défini.
L'annexe A de notre RFC indique quelles définitions existaient dans de précédents RFC mais ont été mises à jour par le nôtre. (C'est rare, puisque le but de ce RFC de terminologie est de rassembler les définitions, pas de les changer.) Par exemple, la définition de QNAME du RFC 2308 est corrigée ici.
L'annexe B liste les termes dont la première définition formelle se trouve dans ce RFC (ou dans un de ses prédécesseurs, les RFC 7719 et "RFC 8499). Cette liste est bien plus longue que celle de l'annexe A, vu le nombre de termes courants qui n'avaient jamais eu l'honneur d'une définition stricte.
Notre RFC ne contient pas une liste exhaustive des changements depuis son prédécesseur, le RFC 8499, mais ils sont peu importants. Parmi les changements sérieux :
Date de publication du RFC : Janvier 2024
Auteur(s) du RFC : M. Knodel, W. Hardaker, T. Pauly
Pour information
Première rédaction de cet article le 9 mai 2024
Aujourd'hui, l'essentiel du trafic sur l'Internet est chiffré, et pour d'excellentes raisons. Pas question de revenir là-dessus, mais, ceci dit, il va falloir adapter certaines pratiques de gestion des réseaux. L'IAB a organisé en 2022 un atelier sur ce sujet, dont ce RFC est le compte-rendu.
Comme les autres ateliers de l'IAB, il s'agit de faire s'exprimer diverses personnes, pas forcément d'arriver à une position commune (surtout sur un sujet… sensible). Tenu entièrement en ligne, cet atelier commençait par la soumission d'articles, qui devaient être lus d'abord, et discutés lors de l'atelier. La liste des articles figure dans l'annexe A du RFC, et les articles sont disponibles en ligne.
D'abord, notre RFC réaffirme que la cryptographie est absolument nécessaire à la protection de la vie privée. Les difficultés réelles qu'elle peut poser ne doivent jamais être un prétexte pour réduire ou affaiblir le chiffrement. Qu'il y ait une tension entre l'impératif du chiffrement et certaines méthodes de gestion du réseau, OK, mais la priorité reste la lutte contre la surveillance (RFC 7258).
Ensuite (section 1), le RFC pose le paysage : le trafic étant largement chiffré, des méthodes traditionnelles de gestion du réseau ne fonctionnent plus. tcpdump ne montre plus les données, on ne peut donc pas distinguer différentes méthodes HTTP, par exemple. Avec QUIC (RFC 9000), c'est même une partie de la couche transport qui est chiffrée donc un observateur extérieur ne peut plus, par exemple, évaluer le RTT d'une session qu'il observe (ce qui était possible avec TCP). Il va donc falloir s'adapter notamment en coopérant davantage avec les applications qui, elles, ont accès à tout. Pour l'instant, on a vu surtout passer des récriminations d'acteurs habitués à surveiller le trafic et qui se plaignent que le chiffrement les en empêchent (le RFC 8404 est un bon exemple de ces récriminations). Au contraire, il faut chercher à résoudre une contradiction : permettre aux réseaux d'appliquer leur politique sans céder d'un millimètre sur le principe du chiffrement.
Outre le cas des opérateurs réseau qui veulent examiner le trafic, à des fins louables (améliorer le réseau) ou critiquables (défavoriser certains usages), on peut aussi citer, parmi les intermédiaires qui voudraient observer et/ou interférer avec le trafic réseau, les réseaux d'entreprise qui veulent, par exemple, empêcher les employés d'accéder à certains services, ou bien les mécanismes de contrôle des enfants (appelés à tort « contrôle parental », alors que leur but est justement de remplacer les parents).
Trois thèmes importants étaient discutés à l'atelier : la situation actuelle, les futures techniques et la façon dont on pourrait arriver à déployer ces futures techniques.
Pour la situation actuelle, on sait que, depuis longtemps, les administrateurices réseau comptent sur de l'observation passive, par exemple pour classer le trafic (tant de HTTP, tant de SSH, etc). C'est à la base de techniques comme IPFIX (RFC 7011), qui permet de produire de beaux camemberts. Outre la production de rapports, cette observation passive peut (mais n'est pas forcément) être utilisée pour prévoir les évolutions futures, prioriser certains types de trafic, détecter des comportements malveillants, etc. Les protocoles Internet étaient en effet traditionnellement trop bavards, faisant fuiter des informations vers des observateurs passifs (cf. le concept de « vue depuis le réseau », RFC 8546).
La lutte
de l'épée et de la cuirasse étant éternelle, il y a évidemment eu
des travaux sur des mécanismes pour empêcher ce genre d'analyses
comme l'article soumis pour l'atelier « WAN
Traffic Obfuscation at Line Rate ». (Au passage,
sur cette idée d'obfuscation, je recommande le
livre de Brunton et Nissenbaum.)
Le RFC mentionne aussi une discussion sur l'idée d'exiger une
permission explicite des utilisateurs pour les analyses du trafic
(je ne vous dis pas les difficultés pratiques…). Voir
l'Internet-Draft
draft-irtf-pearg-safe-internet-measurement
.
Dans un monde idéal, le réseau et les applications coopéreraient (et, dans un monde idéal, licornes et bisounours s'embrasseraient tous les jours) mais on voit plutôt une lutte, le réseau essayant d'en savoir plus, les applications de préserver leur intimité. Pourtant, le RFC note qu'une coopération pourrait être dans leur intérêt réciproque. Bien sûr, des applications comme Tor refuseraient certainement toute coopération puisque leur but est de laisser fuiter le moins d'information possible, mais d'autres applications pourraient être tentées, par exemple en échange d'informations du réseau sur la capacité disponible. Après tout, certaines techniques sont justement conçues pour cela, comme ECN (RFC 3168).
OK, maintenant, quelles sont les techniques et méthodes pour résoudre le problème de la gestion des réseaux chiffrés (section 2.2) ? Le RFC exprime bien le fait que le but du chiffrement est d'empêcher tout tiers d'accéder aux informations. Donc, sauf faille de sécurité, par exemple suite à une faiblesse du chiffrement, tout accès à l'information impose la coopération d'une des deux parties qui communiquent. Cette question du consentement est cruciale. Si on n'a pas le consentement d'une des deux extrémités de la communication, on n'a pas le droit d'accéder aux données, point. Cf. la contribution « What’s In It For Me? Revisiting the reasons people collaborate ». Comme le répète souvent Eric Rescorla « Sur l'Internet, tout ce qui n'est pas une des extrémités est un ennemi. » (Cela inclut donc tous les équipements réseaux et les organisations qui les contrôlent.) Bref, les utilisateurs doivent pouvoir donner un consentement éclairé, et juger si les bénéfices de la visibilité l'emportent sur les risques. Évidemment, ce principe correct va poser de sérieuses difficultés d'application, car il n'est pas évident d'analyser proprement les bénéfices, et les risques (songez aux bandeaux cookies ou aux permissions des application sur votre ordiphone). Développer des nouveaux mécanismes de communication avec l'utilisateurice sont nécessaires, sinon, comme le note le RFC, « les ingénieurs devront choisir pour les utilisateurs ». Le RFC estime que les utilisateurs donneront toujours la priorité à leur vie privée (notamment parce qu'ils ne comprennent pas l'administration de réseaux et ses exigences), mais cela ne me semble pas si évident que cela. Personnellement, il me semble que le choix par défaut est crucial, car peu d'utilisateurices le modifieront.
Une des techniques qui permettent d'avoir accès à de l'information sans avoir toute l'information est celle des relais (cf. « Relying on Relays: The future of secure communication ». Le principe est de séparer les fonctions : l'utilisateur parle à un relais qui parle au serveur final. Le relais ne connait pas le contenu de la demande, le serveur ne connait pas le demandeur. C'est utilisé dans des techniques comme Oblivious HTTP (RFC 9458) ou Oblivious DNS (RFC 9230). La préservation de la vie privée est donc bien meilleure qu'avec, par exemple, un VPN, où l'opérateur du VPN voit tout, le demandeur et la demande (contrairement à ce que prétendent les publicités pour NordVPN que de nombreux youtubeurs transmettent avec leurs vidéos). Le relais permet donc un accès à une information limitée, ce qui permet d'assurer certaines fonctions (comme le filtrage de contenu malveillant) sans avoir un accès complet.
Un exemple souvent cité par les opérateurs de l'intérêt d'accéder à la communication et de la modifier est celui de l'optimisation des flux TCP. Des PEP (Performance Enhancing Proxies) violant le modèle en couches (car, normalement, TCP est de bout en bout) sont souvent déployés, notamment sur les liaisons satellites, dont les performances sont plus mauvaises, et les caractéristiques techniques très spéciales. (Cf. l'exposé de Nicolas Kuhn, « QUIC for satellite communications » à la Journée du Conseil Scientifique de l'Afnic en 2021.) Le PEP va modifier les en-têtes TCP, voire parfois boucler la connexion TCP et en faire une autre lui-même. Cette technique ne marche évidemment plus avec QUIC, qui chiffre même la couche transport. Et elle mène à l'ossification de l'Internet puisqu'elle rend plus difficile de faire évoluer TCP, car toute évolution risque de perturber les PEP. QUIC avait en partie été explicitement développé pour empêcher ces intermédiaires de bricoler la session. Une autre approche est celle proposée dans « The Sidecar: "Opting in" to PEP Functions ».
Bon, maintenant, comment arriver à déployer de meilleures méthodes ? Dans un environnement fermé ou semi-fermé, cela doit être possible de faire en sorte que, par exemple, toutes les machines mettent en œuvre telle fonction (cf. RFC 8520 pour un exemple). Mais cela ne marche clairement pas pour l'Internet.
Parmi les moyens proposés de résolution de la contradiction entre visibilité par le réseau et protection de la vie privée, le RFC mentionne les Zero-Knowledge Middleboxes. Décrites dans l'article du même nom, cette méthode utilise les preuves à divulgation nulle de connaissance pour prouver à l'intermédiaire qui fait le filtrage qu'on a bien respecté sa politique de filtrage, sans lui dire ce qu'on a fait. L'article détaille par exemple comment cela peut s'appliquer au DNS, qui est le principal outil de censure de l'Internet dans l'Union européenne. Ces preuves à divulgation nulle de connaissance ayant toujours été mystérieuses pour moi, je ne vous expliquerai pas comment elles marchent, mais notez que les auteurs ont fait un article pédagogique.
Enfin, le RFC mentionne la proposition Red Rover, qui propose encore un arrangement bisounours où les utilisateurs et les opérateurs collaboreraient pour un filtrage géré en commun. Les auteurs disent que ça marcherait car les utilisateurs « ne veulent probablement pas violer les CGU » (ben si : ils veulent utiliser Sci-Hub même si c'est censuré).
En conclusion, le RFC note en effet qu'on peut être sceptique quant aux chances d'une solution négociée. Mais il met aussi en avant le fait qu'il va être très difficile de trouver une solution qui marche pour toute la variété des réseaux. Et que l'expérience prouve largement que toute nouvelle technique va avoir des effets inattendus et que, par exemple, une solution qui visait à donner aux opérateurs des informations utiles pour la gestion de réseaux, va parfois être détournée pour d'autres buts.
Sinon, sur la question du déboguage des applications dans un monde où tout est chiffré, j'avais fait un exposé à Capitole du Libre.
Date de publication du RFC : Septembre 2023
Auteur(s) du RFC : W. Kumari (Google), P. Hoffman (ICANN)
Chemin des normes
Première rédaction de cet article le 30 septembre 2023
Le TLD
.alt
a été réservé pour les utilisations
non-DNS. Si demain
je crée une chaine de blocs nommée Catena et
que j'enregistre des noms dessus, il est recommandé qu'ils se
terminent par catena.alt
(mais comme toujours
sur l'Internet, il n'y a pas de police mondiale chargée de faire
respecter les RFC).
Ce nouveau RFC
est l'occasion de rappeler que noms
de domaine et DNS, ce
n'est pas pareil. Les noms de domaine existaient avant le DNS
et, même aujourd'hui, peuvent être résolus par d'autres techniques
que le DNS (par exemple votre fichier local
/etc/hosts
ou équivalent).
Mais le DNS est un tel succès que cette « marque » est utilisée
partout. On voit ainsi des systèmes de résolution de noms n'ayant
rien à voir avec le DNS se prétendre « DNS pair-à-pair » ou « DNS
sur la blockchain », ce
qui n'a aucun sens. Ce nouveau RFC, lui, vise clairement uniquement
les systèmes non-DNS. Ainsi, des racines alternatives ou des
domaines privés comme le
.local
du RFC 6762 ne
rentrent pas dans son champ d'application. De plus, il ne s'applique
qu'aux noms de domaine ou en tout cas aux identificateurs qui leur
ressemblent suffisamment. Si vous créez un système complètement
disruptif où les identificateurs ne ressemblent pas à des noms de
domaine, ce RFC ne vous concerne pas non plus. Mais si, pour des
raisons techniques (être compatible avec les applications
existantes) ou marketing (les noms de domaine, c'est bien, tout le
monde les reconnait), vous choisissez des noms de domaine comme
identificateurs, lisez ce RFC.
En effet, il existe plusieurs de ces systèmes. Ils permettent, en
indiquant un nom de domaine (c'est-à-dire une
série de composants séparés par des points comme
truc.machin.chose.example
) d'obtenir des
informations techniques, permettant, par exemple, de trouver un
serveur ou de s'y connecter. Un exemple d'un tel système est le
mécanisme de résolution utilisé par Tor. Les
identificateurs sont construits à partir d'une clé
cryptographique et suffixés d'un
.onion
(réservé par le RFC 7686). Ainsi, ce blog est en
http://sjnrk23rmcl4ie5atmz664v7o7k5nkk4jh7mm6lor2n4hxz2tos3eyid.onion/
. N'essayez
pas ce nom dans le DNS : vous ne le trouverez pas, il se résout via
Tor.
Pendant longtemps, cette pratique de prendre, un peu au hasard,
un nom et de l'utiliser comme TLD a été la façon la plus courante de créer des
noms de domaine dans son espace à soi. C'est ainsi que
Namecoin a utilisé le
.bit
, ENS (Ethereum Name Service) le
.eth
et GNUnet le
.gnu
. Chacun prenait son nom comme il voulait,
sans concertation (il n'existe pas de forum ou d'organisation pour
discuter de ces allocations). Cela entraine deux risques, celui de
collision (deux systèmes de nommage utilisent le même TLD, ou bien
un système de nommage utilise un TLD qui est finalement alloué dans
le DNS, ce qui est d'autant plus probable qu'il n'existe pas de
liste de ces TLD non-DNS). Il y a plusieurs solutions à ce problème,
et l'IETF en a longuement discuté (cf. par exemple
l'atelier
EName de 2017, ou bien le très contestable RFC 8244). Ce RFC a donc mis des années à sortir. L'ICANN, par exemple, a essayé de diaboliser ces
noms, attirant surtout l'attention sur leurs dangers. Autre méthode,
il a été suggéré de créer un mécanisme de concertation pour éviter
les collisions, création qui n'a jamais eu lieu, et pas pour des
raisons techniques. Ce RFC 9476 propose simplement de
mettre les noms non-DNS sous un TLD unique, le
.alt
. Ce TLD est réservé dans le registre
des noms spéciaux (créé par le RFC 6761). Ainsi, si le système de résolution de Tor était
créé aujourd'hui, on lui proposerait d'être en
.onion.alt
. Est-ce que les concepteurs de
futurs systèmes de résolution non-DNS se plieront à cette demande ?
Je suis assez pessimiste à ce sujet. Et il serait encore plus
utopique de penser que les TLD non-DNS existants migrent vers
.alt
.
Comme .alt
, par construction, regroupe des
noms qui ne sont pas résolvables dans le DNS, un résolveur purement
DNS ne peut que répondre tout de suite NXDOMAIN (ce nom n'existe
pas) alors qu'un résolveur qui parle plusieurs protocoles peut
utiliser le suffixe du nom comme une indication qu'il faut utiliser
tel ou tel protocole. Si jamais des noms sous
.alt
sont réellement utilisés, ils ne devraient
jamais apparaitre dans le DNS (par exemple dans les requêtes aux
serveurs racine) mais, compte-tenu de
l'expérience, il n'y a aucun doute qu'on les verra fuiter dans le
DNS. Espérons que des techniques comme celles du RFC 8020, du RFC 8198 ou du RFC 9156 réduiront la
charge sur les serveurs de la racine; et préserveront un peu la vie
privée (section 4 du RFC).
Le nom .alt
est évidemment une référence à
alternative mais il rappelera des souvenirs aux utilisateurs
d'Usenet. D'autres noms avaient été
sérieusement discutés comme de préfixer
tous les TLD non-DNS par un tiret
bas. Mon catena.alt
aurait été
_catena
:-) Tout ce qui touche à la
terminologie est évidemment très sensible, et le RFC prend soin de
souligner que le terme de « pseudo-TLD », qu'il utilise pour
désigner les TLD non-DNS n'est pas péjoratif…
On note que le risque de collision existe toujours, mais sous
.alt
. Notre RFC ne prévoit pas de registre des
noms sous .alt
(en partie parce que
l'IETF
ne veut pas s'en mêler, son protocole, c'est le DNS, et en partie
parce que ce milieu des mécanismes de résolution différents est très
individualiste et pas du tout organisé).
Date de publication du RFC : Novembre 2023
Auteur(s) du RFC : B. Schwartz (Meta Platforms), M. Bishop, E. Nygren (Akamai Technologies)
Chemin des normes
Réalisé dans le cadre du groupe de travail IETF dnsop
Première rédaction de cet article le 8 avril 2024
Ces deux nouveaux types d'enregistrement DNS, SVCB et sa variante HTTPS, permettent de donner des informations supplémentaires à un client réseau avant qu'il ne tente de se connecter à un serveur. On peut envoyer ainsi des indications sur les versions des protocoles gérées, des clés cryptographiques ou des noms de serveurs supplémentaires.
Un client d'un service réseau a en effet plein de questions à se poser avant de tenter une connexion. Quelle adresse IP utiliser ? Quel port ? Chiffrement ou pas ? Les anciens mécanismes traitent la question de l'adresse IP (on la trouve par une requête DNS) et celle du port, si on se limite aux ports bien connus (comme 43 pour whois). Mais cela ne dit pas, par exemple, si le serveur HTTP distant accepte ou non HTTP/3 (RFC 9114). Par contre, cet enregistrement HTTPS de Cloudflare va bien nous dire que ce serveur accepte HTTP/2 et 3 :
% dig cloudflare.com HTTPS … ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 28399 ;; flags: qr rd ra ad; QUERY: 1, ANSWER: 2, AUTHORITY: 0, ADDITIONAL: 1 … ;; ANSWER SECTION: cloudflare.com. 300 IN HTTPS 1 . alpn="h3,h2" ipv4hint=104.16.132.229,104.16.133.229 ipv6hint=2606:4700::6810:84e5,2606:4700::6810:85e5 … ;; WHEN: Mon Apr 08 09:27:01 CEST 2024 ;; MSG SIZE rcvd: 226
Bon, en quoi consiste cet enregistrement SVCB ? Il a deux modes de fonctionnement, alias et service. Le premier mode sert à faire d'un nom une version canonique d'un autre, un peu comme le CNAME mais en étant utilisable à l'apex d'une zone. Le second mode sert à indiquer les paramètres techniques de la connexion. Un enregistrement SVCB (ou HTTPS) a trois champs dans ses données :
SvcPriority
: quand il vaut zéro, il
indique le mode alias. Autrement (par exemple dans le cas
ci-dessus), il indique la priorité de ces paramètres.TargetName
: en mode alias, il indique
le nom canonique, ou autrement un nom alternatif (pour un service
accessible via plusieurs noms). Dans l'exemple Cloudflare
ci-desssus, il valait la racine (un point) ce qui indique
l'absence de nom alternatif (section 2.5).SvcParams
: une liste de couples
{clé,valeur} pour les paramètres de connexion (uniquement en mode
service). Dans le cas avec Cloudflare, c'était
alpn="h3,h2" ipv4hint=104.16.132.229,104.16.133.229
ipv6hint=2606:4700::6810:84e5,2606:4700::6810:85e5
. (Si
vous vous intéressez aux débats à l'IETF, la question de la
syntaxe de ces paramètres avait suscité une
longue discussion.)Les enregistrements SVCB ont le type 64 (enregistré à l'IANA) et les HTTPS, qui ont la même syntaxe et le même contenu, mais sont spécifiques à HTTP, ont le 65 (SVCB est générique). Les enregistrements HTTPS (et de futurs enregistrements pour d'autres protocoles) sont dits « compatibles avec SVCB » car ils ont la même syntaxe et la même sémantique.
Notre RFC définit (section 7) une liste de paramètres possibles mais d'autres peuvent être ajoutés dans un registre IANA, via la procédure « Examen par un expert » (RFC 8126). Pour l'instant, il y a, entre autres :
L'enregistrement peut (cela dépend des protocoles qui
l'utilisent, HTTP ne le fait pas) être placé sur un sous-domaine
indiquant le service, par exemple
_8765._baz.api.example.com
(section
10.4.5).
Idéalement, un serveur faisant autorité devrait renvoyer les SVCB et les HTTPS, s'ils sont présents, dans la section additionnelle de la réponse, lorsque le type demandé était une adresse IP. Mais ceux de Cloudflare ne semblent pas le faire actuellement. (PowerDNS le fait.)
Si vous vous intéressez aux questions opérationnelles, et que vous voulez mettre des enregistrements SVCB/HTTPS dans votre zone, la section 10 du RFC est faite pour vous. J'ai des enregistrements HTTPS pour ce blog :
# Un alias à l'apex (la priorité 0 indique le mode alias) % dig +short +nodnssec bortzmeyer.org HTTPS 0 www.bortzmeyer.org. # J'ai HTTP/2 (mais pas encore HTTP/3) % dig +short +nodnssec www.bortzmeyer.org HTTPS 1 . alpn="h2"
Pour cela, j'ai mis dans le fichier de zone :
; Enregistrements SVCB (HTTPS). ; HTTP/2 (mais pas encore - au 2024-04-08 - de HTTP/3) www IN HTTPS 1 . alpn="h2" ; alias @ IN HTTPS 0 www.bortzmeyer.org.
Les clients HTTP récents, qui gèrent SVCB/HTTPS vont alors se
connecter directement en HTTP/2 à
https://www.bortzmeyer.org/
même si
l'utilisateur demandait originellement
http://bortzmeyer.org/
(le type
d'enregistrement HTTPS, comme son nom l'indique, sert aussi à
annoncer qu'on accepte HTTPS, ce qui permettra d'abandonner HSTS). Les clients
HTTP plus anciens, évidemment, ne connaissent pas le système
SVCB/HTTPS et il faut donc garder une configuration pour eux (par
exemple des adresses IP à l'apex). Il y a aussi les autres méthodes,
comme le Alt-Svc:
du RFC 7838. La section 9.3 du RFC décrit le comportement attendu
lorsque les différentes méthodes coexistent.
Faites attention toutefois, lorsque vous mettez ce type d'enregistrements dans votre zone, je ne connais pas encore d'outils de test permettant de vérifier la syntaxe des enregistrements, encore moins leur correspondance avec la réalité (par exemple, SSLLabs ne semble pas le faire). C'est un problème général de la signalisation sur l'Internet, quand on signale (notamment via le DNS) les capacités d'un serveur : le logiciel client doit de toute façon être prêt à tout, car il ne peut jamais être sûr que le signal est conforme aux faits.
En parlant d'anciens logiciels (clients et serveurs), vous pouvez trouver une liste de mises en œuvre de SVCB/HTTPS. Attention, elle est incomplète et pas à jour. Notez qu'il y a parfois des contraintes particulières, ainsi, il semble que Firefox ne demande des enregistrements HTTPS que s'il utilise DoH. iOS envoie des requêtes HTTPS depuis iOS 14, publié en septembre 2020, ce qui avait étonné, à l'époque.
En parlant de Firefox, s'il est assez
récent, et s'il est configuré
pour faire du DoH, vous pouvez tester le SVCB/HTTPS en allant dans
about:networking#dnslookuptool
. En entrant un
nom de domaine, le champ « RR HTTP » doit renvoyer l'enregistrement
HTTPS.
Avec un tcpdump récent, voici le trafic
DNS utilisant le nouvel enregistrement DNS, qu'on peut observer sur
un serveur faisant autorité pour
bortzmeyer.org
:
09:49:23.354974 IP6 2a04….31362 > 2001:4b98:dc0:41:216:3eff:fe27:3d3f.53: 13024% [1au] HTTPS? www.bortzmeyer.org. (47) 09:52:06.094314 IP6 2a00….56551 > 2001:4b98:dc0:41:216:3eff:fe27:3d3f.53: 40948% [1au] HTTPS? wWw.bOrTZmEyER.ORg. (62) 10:06:21.501437 IP6 2400….11624 > 2001:4b98:dc0:41:216:3eff:fe27:3d3f.53: 59956 [1au] HTTPS? doh.bortzmeyer.fr. (46) 10:06:21.999608 IP6 2400….36887 > 2001:4b98:dc0:41:216:3eff:fe27:3d3f.53: 17231 [1au] HTTPS? radia.bortzmeyer.org. (49) 10:25:53.947096 IP6 2001….54476 > 2001:4b98:dc0:41:216:3eff:fe27:3d3f.53: 26123% [1au] HTTPS? www.bortzmeyer.org. (47)
Si votre tcpdump est plus ancien, vous verrez Type65 au lieu de HTTPS.
Sinon, si vous aimez les bricolages (et celui-ci sera de moins en moins utile avec le temps, au fur et à mesure que les serveurs géreront ce type), pour fabriquer les enregistrements, vous pouvez utiliser cet outil, qui va fabriquer la forme binaire, directement chargeable par les serveurs faisant autorité :
% perl type65_https.pl 'example.net HTTPS 1 . alpn="h3,h2" ipv4hint="192.0.2.42" ipv6hint="2001:db8::42"' example.net. TYPE65 ( \# 41 00010000010006026833026832000400 04c000022a0006001020010db8000000 000000000000000042 )
(Il faut un Net::DNS
récent sinon « unknown type "HTTPS" at /usr/share/perl5/Net/DNS/RR.pm line 671.
in new Net::DNS::RR( www.bortzmeyer.org HTTPS 1 . alpn="h2" )
at type65_https.pl line 30. ».)
Quelques articles pas mal :
Date de publication du RFC : Juillet 2023
Auteur(s) du RFC : P. van Dijk (PowerDNS), L. Peltan
(CZ.NI), O. Sury (Internet Systems
Consortium), W. Toorop (NLnet
Labs), C.R. Monshouwer, P. Thomassen
(deSEC, SSE - Secure Systems
Engineering), A. Sargsyan (Internet Systems
Consortium)
Chemin des normes
Réalisé dans le cadre du groupe de travail IETF dnsop
Première rédaction de cet article le 29 mars 2024
L'idée de base de ces « zones catalogue » est d'automatiser la configuration d'une nouvelle zone DNS sur les serveurs secondaires, en publiant dans le DNS les caractéristiques des zones qu'ils devront servir. Cela concerne donc surtout les gros hébergeurs qui ont beaucoup de zones.
Petit rappel : une zone, dans le DNS, est une partie contigüe de l'arbre des
noms de domaine, gérée
comme un tout (mêmes serveurs faisant
autorité). Ainsi, si vous venez de louer le nom
machin.example
, un hébergeur DNS va configurer
ses serveurs pour faire autorité pour ce nom. Par exemple, avec le
logiciel NSD, sur
le primaire (serveur maitre) :
zone: name: "machin.example" zonefile: "primary/machin.example" # Les serveurs secondaires : notify: 2001:db8:1::53 provide-xfr: 2001:db8:1::53 …
Et, sur un serveur secondaire (serveur esclave), qui transférera la zone depuis le primaire (RFC 5936) :
zone: name: "machin.example" # Le primaire : allow-notify: 2001:db8:cafe::1 NOKEY request-xfr: AXFR 2001:db8:cafe::1 NOKEY
Si on gère beaucoup de zones, avec des ajouts et des retraits tout le temps, l'avitaillement manuel est long et risqué (et si on oublie un serveur ?). Éditer ces fichiers sur tous les serveurs secondaires devient vite pénible. Et si les logiciels sur les secondaires sont différents les uns des autres (ce qui est recommandé, pour la robustesse), il faut se souvenir des différentes syntaxes. Pourquoi faire manuellement ce qu'on peut automatiser ? C'est le principe des zones catalogue.
Le principe est simple : une zone catalogue est une zone comme
une autre, produite par les mêmes mécanismes (par exemple
emacs) et qui sera servie par un serveur
primaire à tous les secondaires, qui changeront alors
automatiquement leur configuration en fonction du contenu de la zone
catalogue. Chaque zone à configurer est un enregistrement de type
PTR, dont la partie gauche est une étiquette interne et la partie
droite indique le nom de la zone. Ici, on configure la zone
rutaba.ga
, l'étiquette (qui doit être unique)
label1
est à usage interne (section 4.1 du RFC) :
label1.zones.catalog.example. IN PTR rutaba.ga.
Le reste est listé sous forme de propriétés (section 4.2). Une
propriété évidente est l'adresse IP du primaire. Pour l'instant,
elle doit être indiquée via le composant ext
qui désigne les propriétés pas encore normalisées :
primaries.ext.catalog.example. IN AAAA 2001:db8:bad:dcaf::42
La liste des propriétés figure dans un registre IANA.
À l'heure actuelle, de nombreux logiciels gèrent ces zones catalogues. Le site Web du projet (pas mis à jour depuis très longtemps) en liste plusieurs.
Voici un exemple complet de zone catalogue :
; -*- zone -*- catalog.example. IN SOA ns4.bortzmeyer.org. stephane.bortzmeyer.org. 2023120902 900 600 86400 1 catalog.example. IN NS invalid. ; Le NS est inutile mais ; obligatoire et "invalid" est la valeur ; recommandée (section 4). version.catalog.example. IN TXT "2" ; Obligatoire, section 4.2.1 label1.zones.catalog.example. IN PTR rutaba.ga. primaries.ext.catalog.example. IN AAAA 2001:db8:bad:dcaf::42
La configuration de BIND pour l'utiliser :
# La zone catalogue se charge comme n'importe quelle zone. Ceci dit, # vu le caractère critique de la zone catalogue, la section 7 du RFC # insiste sur l'importance de sécuriser ce transfert, par exemple avec # TSIG (RFC 8945) : zone "catalog.example" { type slave; file "catalog.example"; masters { 2001:db8:666::; }; }; # Et, ici, on la désigne comme spéciale : options { ... catalog-zones { zone "catalog.example" in-memory no; }; };
Naturellement, comme toujours lorsque on automatise, on risque le syndrome de l'apprenti sorcier. Attention donc en générant la zone catalogue. Comme le note le RFC (section 6) : « Great power comes with great responsibility. Catalog zones simplify zone provisioning by orchestrating zones on secondary name servers from a single data source: the catalog. Hence, the catalog producer has great power and changes must be treated carefully. For example, if the catalog is generated by some script and this script generates an empty catalog, millions of member zones may get deleted from their secondaries within seconds, and all the affected domains may be offline in a blink of an eye. »
Date de publication du RFC : Mars 2023
Auteur(s) du RFC : W. Kozlowski, S. Wehner
(QuTech), R. Van Meter (Keio
University), B. Rijsman, A. S. Cacciapuoti, M. Caleffi
(University of Naples Federico II), S. Nagayama
(Mercari)
Pour information
Réalisé dans le cadre du groupe de recherche IRTF qirg
Première rédaction de cet article le 3 avril 2024
Voici un RFC assez futuriste qui explore à quoi pourrait ressembler un futur « Internet » quantique. Je divulgâche tout de suite : ce ne sera pas de si tôt.
Quelques avertissements s'imposent d'abord. Avant tout, rappelez-vous que la quantique produit des résultats qui sont parfaitement cohérents théoriquement et très bien vérifiés expérimentalement mais qui sont hautement non-intuitifs. Avant d'aborder le monde merveilleux de la quantique, n'oubliez pas d'oublier tout ce que vous croyez savoir sur le monde physique. Et ne comptez pas trop sur moi comme guide, on sort nettement ici de mon domaine de compétence. Ensuite, « quantique » est un terme à très forte charge marketing (moins que « IA » mais davantage que « métavers » ou même « blockchain », qui semblent bien passés de mode). Il faut donc être prudent chaque fois qu'un commercial ou un éditorialiste va dire que « c'est quantique » ou que « la quantique va bouleverser tel ou tel domaine ». Enfin, il y a loin de la coupe aux lèvres : de même qu'on n'a pas encore d'ordinateur quantique utile, on n'a pas encore de réseau quantique. Le RFC est à juste titre prudent, pointant les différents obstacles qui restent sur le chemin de l'Internet quantique.
Bon, ces précautions étant posées, qu'est-ce qu'un réseau quantique et pourquoi y consacrer un RFC ? La section 1 du RFC le résume : un réseau quantique est un réseau qui ferait communiquer des dispositifs quantiques pour faire des choses inimaginables avec un réseau classique. Il s'appuierait sur des propriétés spécifiques au monde quantique, notamment l'intrication, propriétés qui n'ont pas d'équivalent dans le monde classique. Attention, et le RFC insiste bien là-dessus, personne n'envisage de remplacer l'Internet classique par un Internet quantique (de la même façon que les futurs ordinateurs quantiques, étant loin d'être généralistes, ne remplaceront pas les ordinateurs classiques). Au contraire, le scénario envisagé est celui d'un réseau hybride, partiellement quantique. (Une lecture recommandée par le RFC est « The quantum internet ».)
Un exemple typique qui ne serait pas possible avec un réseau classique est celui de la distribution quantique de clés (parfois appelée du terme erroné de « cryptographie quantique »), dont l'utilité pratique est douteuse mais qui est assez spectaculaire et, contrairement à d'autres applications, est assez avancée techniquement. D'autres applications sont envisageables à plus long terme. C'est le cas par exemple du blind quantum computation, qui n'a pas encore d'article Wikipédia mais est expliqué dans cet article.
En laboratoire, beaucoup de résultats ont été obtenus. Les chercheurs et chercheuses ont déjà mis au point bien des dispositifs physiques étonnants. Mais à l'échelle du réseau, il n'y a pas encore eu beaucoup de travaux. Le RFC compare cette situation à celle d'un réseau classique où on aurait des fibres optiques et des lasers pour les illuminer mais aucun protocole de transport, aucun mécanisme de routage, encore moins de moyens de gérer le réseau. Développer une application pour un réseau quantique revient à toucher directement au matériel, comme, pour un réseau classique, s'il fallait que chaque application parle aux interfaces physiques, sans avoir d'interface de plus haut niveau comme les prises.
La section 2 du RFC est un rappel sur la quantique. Comme dit plus haut, c'est un domaine riche et complexe, où l'intuition ordinaire ne sert pas à grand'chose. Donc, lire ce rappel est une bonne idée mais n'espérez pas tout comprendre si vous n'êtes pas spécialiste de la question. Cette section est conçue pour des gens qui ne connaissent rien à la physique quantique, elle recommande, pour aller plus loin, le livre de Sutor Dancing with Qubits ou bien celui de Nielsen et Chuang, Quantum Computation and Quantum Information.
Le rappel commence avec la notion d'état quantique. Vous avez sans doute déjà entendu dire qu'un bit classique peut prendre deux valeurs, 0 ou 1, alors que son équivalent quantique, le qubit, a un état qui est une superposition de valeurs possibles, avec des probabilités. Lorsqu'on le mesure, on trouve un 0 ou un 1. (Oui, comme le célèbre chat qui est à la fois vivant et mort.) Attention, ces non-certitudes ne sont pas la conséquence d'un manque d'information mais sont une propriété fondamentale du monde quantique (Alain Aspect a eu un prix Nobel pour avoir prouvé cela). Notez que les versions HTML ou PDF du RFC sont recommandées ici, car il y a quelques équations. Comme un qubit est dans un état qui superpose les deux valeurs possibles, les opérations quantiques agissent sur tout l'état, par exemple l'équivalent quantique d'une porte NOT va inverser les probabilités du 0 et du 1 mais pas transformer un 0 en 1.
Le terme « qubit » (et cette distinction revient souvent dans le RFC) peut désigner aussi bien le concept abstrait que le truc physique qui va le mettre en œuvre (il existe plusieurs techniques pour fabriquer un engin qui gérera des qubits).
On peut ensuite assembler des qubits et, très vite, le nombre de possibilités croît. Mais l'intérêt de mettre des qubits ensemble est qu'on peut les intriquer et ce concept est au cœur de beaucoup de solutions quantiques, notamment du réseau quantique. Une fois intriqués, les deux qubits verront leur sort lié. Une mesure sur l'un affectera l'autre. (Rappel : la quantique n'est pas intuitive et l'intrication n'a pas d'équivalent dans le monde non-quantique, celui sur lequel a été bâtie notre intuition.) La mesure, comme toujours en quantique, est « destructive » au sens où elle ramène à un système classique (le qubit vaut 0 ou 1 quand on le mesure, pas un mélange des deux, et le chat est vivant ou mort quand on ouvre la boite).
Cette intrication est au cœur des réseaux quantiques (section 3 du RFC). Tous les projets de réseaux quantiques utilisent cette propriété (qui, rappelons-le, n'a pas d'équivalent non-quantique). L'intrication permet de corréler deux nœuds du réseau. Par exemple, pour se mettre d'accord sur une valeur, deux machines n'ont pas besoin de faire tourner des algorithmes de consensus, elles peuvent utiliser deux qubits intriqués, chacune en gardant un. Quand une machine lira la valeur de son qubit, elle sera certaine de la valeur lue par l'autre. Et l'intrication ne peut pas être partagée : un tiers ne peut pas s'intriquer avec une intrication existante, ce qui peut avoir des applications en sécurité.
Un réseau quantique est donc défini par notre RFC comme un ensemble de nœuds qui peuvent échanger des qubits intriqués.
Bon, tout ça, c'est très joli, mais comment on le réalise, ce réseau quantique ? La section 4 se penche sur les défis :
Distribuer sur le réseau des qubits quelconques n'est pas forcément facile, donc le RFC suggère de plutôt distribuer des paires de Bell. On peut alors plus facilement (tout est relatif) faire de la téléportation, c'est-à-dire « transporter » un qubit d'un point à un autre. Ce n'est pas une violation du théorème d'impossibilité du clonage puisque le qubit n'est pas copié (il disparait de son point de départ). Notez que le terme de « téléportation » est surtout marketing : vous ne pourrez pas déplacer votre chat ou vous-même de cette façon.
Dernier problème, amplifier le signal (sans le copier !) pour tenir compte de sa dégradation avec la distance. Il existe une astuce, l'échange d'intrication, que je ne vais pas essayer d'expliquer, mais qui permet des réseaux quantiques sur des distances importantes.
Revenons à la correction d'erreurs. Les réseaux quantiques ne sont pas complètement démunis, et ont des solutions possibles, comme les codes quantiques.
OK, on a vu que le monde quantique était très spécial. Donc, le réseau quantique va être bizarre aussi, aux yeux de quelqu'un qui a l'habitude des réseaux classiques (section 5 du RFC). Par exemple, il fera face à ces problèmes :
Répétons-le, chaque nœud du réseau quantique devra également être relié à un réseau classique. Le réseau sera donc complexe et son administration pas évidente.
Une fois qu'on a accepté cela, le réseau classique pourra s'occuper d'opérations comme la construction des tables de routage, pour laquelle les algorithmes et méthodes classiques semblent suffire. On n'aura donc peut-être qu'un seul plan de contrôle (le classique) mais deux plans de données, le classique et le quantique.
Que faut-il construire comme machines pour le plan de données quantique ? D'abord, des répéteurs quantiques qui vont pouvoir créer les intrications, les échanger et contrôler la fidélité. Ensuite :
Facile, me direz-vous ? Non, construire ces machines va nécessiter de s'attaquer à quelques problèmes physiques :
Si vous n'êtes pas découragé·e (mais il ne faut pas l'être : même si les difficultés sont colossales, le chemin est rigolo), il faut maintenant, en supposant qu'on aura les composants de base d'un réseau, les assembler. (À moins que le choix décrit dans le RFC des paires de Bell et de l'échange d'intrication ne soit remis en cause par les futurs progrès…) La section 6 se penche sur la question. Elle démarre par un bel excès d'optimisme, en expliquant que, contrairement à ce qui s'est passé avec l'Internet classique, on a de l'expérience sur la construction de réseau, et qu'on pourra donc ne pas faire d'erreur comme la taille trop réduite des adresses IPv4.
Des services essentiels pour un réseau réel seront difficiles à assurer sur un réseau quantique. Par exemple, l'impossibilité du clonage interdira d'utiliser un logiciel équivalent à tcpdump (remarquez, pour la sécurité, c'est un plus). Le RFC liste les principes de base d'un réseau quantique :
Et le RFC se termine par une exploration d'une architecture de réseau quantique possible, inspirée de MPLS. Dans ce réseau (pour l'instant) imaginaire, tout fonctionne en mode connecté (comme MPLS) : on doit d'abord créer un circuit virtuel (car créer les paires de Bell et les utiliser va nécessiter de la coordination, donc il vaut mieux établir d'abord une connexion). Ce QVC (Quantum Virtual Circuit) a des caractéristiques comme une qualité de service choisie, qui se décline en, par exemple, une capacité mesurée en nombre de paires de Bell par seconde et bien sûr une fidélité (toutes les applications des réseaux quantiques n'ont pas les mêmes exigences en terme de fidélité). La signalisation peut être décentralisée (comme avec RSVP) ou centralisée (comme avec OpenFlow). Comme vous le verrez en lisant cette conclusion du RFC, les détails sont encore approximatifs.
Ce RFC a mis longtemps à être écrit, vous pouvez trouver une description ancienne du projet sur le blog de l'IETF. Notez que l'écriture de ce RFC a été en partie financée par la Quantum Internet Alliance européenne.
N'hésitez pas à vous plonger dans la bibliographie très détaillée de ce RFC, vous y trouverez beaucoup de lectures passionnantes. Il y a même déjà des livres entiers sur les réseaux quantiques comme celui de Van Meter.
Date de publication du RFC : Septembre 2022
Auteur(s) du RFC : C. Eckel (Cisco Systems)
Pour information
Réalisé dans le cadre du groupe de travail IETF shmoo
Première rédaction de cet article le 31 octobre 2024
L'IETF, l'organisation qui normalise les protocoles de l'Internet a toujours prôné le pragmatisme et le réalisme : ce n'est pas tout d'écrire des normes, il faut encore qu'elles fonctionnent en vrai. Un des outils pour cela est le hackathon qui se déroule traditionnellement le week-end avant les réunions physiques de l'IETF. Ce RFC, écrit par le responsable de ces hackathons, décrit l'organisation de ces événements.
La salle (en cours d'installation) du hackathon à Yokohama en 2023 :
Paradoxalement, alors que cette ambition de réalisme (« we believe in rough consensus and running code ») est ancienne à l'IETF, les hackathons sont relativement récents. Le premier était en 2015 à Dallas. Mais les hackathons font désormais partie de la culture IETF et on n'imagine plus de réunion sans eux. Ils accueillent désormais systématiquement plusieurs centaines de participant·es (cf. section 6 du RFC). (Mon premier était à Chicago.) Au passage, si vous êtes programmeur·se et que vous allez à une réunion IETF, ne ratez pas le hackathon, il en vaut la peine. Le prochain commence ce samedi, le 2 novembre 2024, à Dublin.
Donc, le but du hackathon à l'IETF (section 1 du RFC) est de tester les normes en cours d'élaboration, de voir si la future spécification est claire et réaliste, et de faire en sorte que les gens qui écrivent les normes (qui ne sont pas forcément des développeur·ses) interagissent avec ceux et celles qui mettent en œuvre les normes. (Cela peut paraitre étonnant, mais beaucoup de SDO ne fonctionnent pas comme cela ; la norme est écrite par des gens qui sont très loin du terrain et qui se moquent du caractère réaliste et effectif de leurs textes.) Le code qui tourne est souvent plus significatif qu'une opinion exprimée pendant la réunion. Et le hackathon est aussi l'occasion de travailler en commun (surtout si on est présent·e physiquement), ce qui fait avancer les projets.
Ah, et puisqu'on parle de collaboration, contrairement à certains hackathons, ceux de l'IETF n'ont plus aucune forme de compétition : pas de classement, pas de prix. (Voir la section 7.6 sur le rôle qu'avaient les juges de la compétition et pourquoi cela a été vite supprimé.) En outre, la plupart du code écrit pendant les hackathons est sous une licence libre (la principale exception concerne les gens qui ont modifié un logiciel privateur, par exemple le code de certains routeurs). Du point de vue « juridique », notez que le hackathon est un événement de l'IETF et donc soumis aux règles IETF (voir aussi la section 5.3).
Les hackathons de l'IETF sont gratuits (et il n'est pas forcément nécessaire de payer son voyage, on peut le faire à distance, voir plus loin) et ouverts à tous (cf. section 5.3). On s'inscrit en ligne puis on y va. Si vous êtes débutant·e, ne vous inquiétez pas, on a tous été débutant·es et les hackathons sont justement l'occasion de mêler étudiant·es, expert·es, etc. Une large participation est encouragée.
Comment organise t-on un tel événement ? Charles Eckel, l'auteur du RFC, est pour l'instant le pilier de ces hackathons. Le RFC a été écrit, entre autres, pour transmettre son expérience et permette à d'autres de le seconder ou de lui succéder. La section 2 du RFC détaille les choix effectués. Le hackathon se tient le week-end pour faciliter la participation des gens qui travaillent, notamment de ceux et celles qui ne viennent pas à la réunion IETF la semaine suivante. Et puis ça renforce le côté informel.
Questions horaires, le hackathon de l'IETF n'essaie pas de faire travailler les gens 24 heures sur 24 tout le week-end. La majorité des participant·es au hackathon viennent à l'IETF ensuite et doivent donc rester frai·ches. Le hackathon dure typiquement le samedi de 09:00 à 22:00 et le dimanche de 09:00 à 16:00 (il y a déjà des réunions IETF le dimanche en fin d'après-midi). Si des gens veulent travailler toute la nuit, ielles doivent le faire dans leur chambre.
La nourriture est fournie sur place pour éviter qu'on doive sortir (mais on a évidemment le droit de sortir, les participant·es ne sont pas attaché·es et peuvent estimer plus important d'aller discuter avec d'autres participant·es en dehors du hackathon). Et, comme le note le RFC, c'est une motivation supplémentaire pour faire venir les gens (on mange bien).
Et pour les gens qui sont à distance ? Le problème avait commencé à se poser pour la réunion IETF 107 prévue à Vancouver et annulée au dernier moment pour cause de Covid. Il y avait eu un effort pour maintenir un hackathon en distanciel mais cela n'avait pas intéressé grand'monde. (Un des plus gros problèmes du distanciel est le décalage horaire. Les gens qui n'étaient pas en PST n'avaient montré aucun enthousiasme pour le hackathon.) Cela s'est mieux passé par la suite, avec des réunions entièrement en ligne pendant la pandémie. Aujourd'hui, les réunions ont repris en présentiel mais une partie des participant·es, aussi bien à l'IETF qu'au hackathon, sont à distance. (Mon opinion personnelle est que c'est peu utile ; l'intérêt du hackathon est la collaboration intense avec les gens qui sont juste là. En travaillant à distance, on perd cette collaboration. Autant travailler tout seul dans son coin.) Une approche intermédiaire, très utilisée à Maurice, est d'avoir une réunion en présentiel locale, où les gens travaillent à distance sur le hackathon. Cela permet de garder un côté collectif sympa.
Tout cela (à commencer par les repas, et bien sûr la salle, mais elle est parfois incluse dans le contrat global de l'IETF pour sa réunion) coûte de l'argent. La section 3 décrit les sources de financement, qui permettent de garder le hackathon gratuit pour les participant·es. Il y a, comme souvent dans les conférences techniques, des sponsors. (Pour le prochain hackathon, ce seront Ericsson, Meta et l'ICANN.) Mais il y a aussi un financement par l'IETF elle-même (car on ne trouve pas toujours des sponsors). Le RFC précise même quels repas seront sacrifiés s'il n'y a pas assez d'argent (le diner du samedi est considéré comme moins important que les déjeuners, car les participant·es pourront toujours diner en rentrant).
Ah, et les T-shirts ? Pas de hackathon sans T-shirt. Le RFC donne des statistiques intéressantes, comme la répartition par taille aux précédents hackathons.
La participation à distance dispense du coût des repas mais il faut ajouter celle des systèmes de communication utilisés, notamment Meetecho et Gather. (Avant de proposer un autre système, rappelez-vous qu'il y a des centaines de participant·es actif·ves au hackathon et qu'il faut une solution qui tienne la charge.)
À la fin du hackathon, les participant·es présentent leur travail, les résultats obtenus et les conclusions qu'on peut en tirer pour les normes en cours de développement (section 4). Au début, il y avait également une session de présentation des projets au début du hackathon mais elle a dû être abandonnée au fur et à mesure que le hackathon grossissait. La présentation initiale se fait désormais sur le Wiki (voir par exemple la prochaine et la page où on peut aller si on cherche une équipe ou bien des volontaires). C'est sur ce Wiki que les champions (les gens qui ont une idée et organisent une activité particulière au hackathon, cf section 7.4) essaient de recruter. Quant aux présentations finales, elles sont publiées sur Github. C'est aussi là qu'on trouvera le code produit lorsqu'il n'est pas sur un dépôt extérieur (section 5.6).
La liste des projets affichés dans la salle du hackathon de Yokohama en 2023 :
On a parlé des outils utilisés, lors de la section sur le financement. La section 5 dresse une liste plus complète des outils logiciels qui servent pour le hackathon. Il y a évidemment, comme pour tout travail IETF, le Datatracker (qui a une section sur le hackathon). Tout aussi évidemment, puisque l'IETF travaille largement avec des listes de diffusion, il y a une liste des participant·es au hackathon.
Pour profiter de tous ces outils, il faut un réseau à forte capacité, fiable, et évidemment sans aucun filtrage. Lors de la réunion de Beijing, les négociations avec les autorités avaient été serrées et l'accord final imposait un contrôle d'accès aux salles de l'IETF, pour éviter que des citoyens chinois n'en profitent. (Normalement, il n'y a aucun contrôle à l'entrée de l'IETF, personne ne vérifie les badges.) Le groupe qui s'occupe de monter et de démonter le réseau de l'IETF a donc en plus la charge du réseau du hackathon. Celui-ci peut en outre nécessiter des services spéciaux, afin de tester en présence de tels services (NAT64, prefix delegation, etc). Il y a même un accès VPN, pour celles et ceux qui travaillent à distance.
Date de publication du RFC : Septembre 2022
Auteur(s) du RFC : M. Koster, G. Illyes, H. Zeller, L. Sassman
(Google)
Chemin des normes
Première rédaction de cet article le 14 septembre 2022
Quand vous gérez un serveur Internet (pas forcément un serveur
Web) prévu pour des humains, une bonne partie du trafic, voire la
majorité, vient de robots. Ils ne sont pas
forcément malvenus mais ils peuvent être agaçants, par exemple s'ils
épuisent le serveur à tout ramasser. C'est pour cela qu'il existe
depuis longtemps une convention, le fichier
robots.txt
, pour dire aux robots gentils ce
qu'ils peuvent faire et ne pas faire. La spécification originale
était très limitée et, en pratique, la plupart des robots comprenait
un langage plus riche que celui du début. Ce nouveau RFC documente plus ou moins
le format actuel.
L'ancienne spécification, qui date de
1996, est toujours en ligne
sur le site de
référence (qui ne semble plus maintenu, certaines
informations ont des années de retard). La réalité des fichiers
robots.txt
d'aujourd'hui est différente, et
plus riche. Mais, comme souvent sur
l'Internet, c'est assez le désordre, avec
différents robots qui ne comprennent pas la totalité du langage
d'écriture des robots.txt
. Peut-être que la
publication de ce RFC aidera à uniformiser les choses.
Donc, l'idée de base est la suivante : le
robot qui veut ramasser des ressources sur un
serveur va d'abord télécharger un fichier situé sur le chemin
/robots.txt
. (Au passage, si cette norme était
faite aujourd'hui, on utiliserait le
/.well-known
du RFC 8615.) Ce fichier va contenir des instructions pour le
robot, lui disant ce qu'il peut récupérer ou pas. Ces instructions
concernent le chemin dans l'URI (et robots.txt
n'a
donc de sens que pour les serveurs utilisant ces URI du RFC 3986, par exemple les serveurs Web). Un exemple très simple
serait ce robots.txt
:
User-Agent: * Disallow: /private
Il dit que, pour tous les robots, tout est autorisé sauf les chemins
d'URI commençant par /private
. Un robot qui
suit des liens (RFC 8288) doit donc ignorer
ceux qui mènent sous /private
.
Voyons maintenant les détails pratiques (section 2 du RFC). Un
robots.txt
est composé de
groupes, chacun s'appliquant à un robot ou un
groupe de robots particulier. Les groupes sont composés de
règles, chaque règle disant que telle partie de
l'URI est interdite ou autorisée. Par défaut, tout est autorisé
(même chose s'il n'y a pas de robots.txt
du
tout, ce qui est le cas de ce blog). Un groupe commence par une
ligne User-Agent
qui va identifier le robot (ou
un astérisque pour désigner tous les
robots) :
User-Agent: GoogleBot
Le robot va donc chercher le ou les groupes qui le concernent. En
HTTP,
normalement, l'identificateur que le robot cherche dans le
robots.txt
est une sous-chaine de
l'identificateur envoyé dans le champ de l'en-tête HTTP
User-Agent
(RFC 9110,
section 10.1.5).
Le robot doit combiner tous les groupes qui s'appliquent à lui,
donc on voit parfois plusieurs groupes avec le même
User-Agent
.
Le groupe est composé de plusieurs règles, chacune commençant par
Allow
ou Disallow
(notez
que la version originale de la spécification n'avait pas
Allow
). Si plusieurs règles correspondent, le
robot doit utiliser la plus spécifique, c'est-à-dire celle dont le
plus d'octets correspond. Par exemple, avec :
Disallow: /private Allow: /private/notcompletely
Une requête pour /private/notcompletely/foobar
sera autorisée. L'ordre des règles ne compte pas (mais certains
robots sont bogués sur ce point). C'est du fait de cette notion de
correspondance la plus spécifique qu'on ne peut pas compiler un
robots.txt
en une simple expression
rationnelle, ce qui était possible avec la
spécification originelle. Si une règle Allow
et une Disallow
correspondent, avec le même
nombre d'octets, l'accès est autorisé.
Le robot a le droit d'utiliser des instructions supplémentaires, par exemple pour les sitemaps.
En HTTP, le robot peut rencontrer une redirection
lorsqu'il essaie de récupérer le robots.txt
(code HTTP 301, 302, 307 ou 308). Il devrait dans ce cas la suivre
(mais pas infinement : le RFC recommande cinq essais au
maximum). S'il n'y a pas de robots.txt
(en
HTTP, code de retour 404), tout est autorisé (c'est le cas de mon
blog). Si par contre il y a une erreur (par exemple 500 en HTTP), le
robot doit attendre et ne pas se croire tout permis. Autre point
HTTP : le robot peut suivre les instructions de mémorisation du
robots.txt
données, par exemple, dans le champ
Cache-control
de l'en-tête.
Avant de passer à la pratique, un peu de sécurité. D'abord, il
faut bien se rappeler que le respect du
robots.txt
dépend de la bonne volonté du
robot. Un robot malveillant, par exemple, ne tiendra certainement
pas compte du robots.txt
. Mais il peut y avoir
d'autres raisons pour ignorer ces règles. Par exemple, l'obligation
du dépôt légal fait que la
BNF
annonce explicitement qu'elle ignore ce
fichier. (Et un programme comme wget a
une option, -e robots=off
, pour débrayer la vérification
du robots.txt
.) Bref, un robots.txt
ne remplace pas
les mesures de sécurité, par exemple des règles d'accès aux
chemins définies dans la configuration de votre serveur HTTP. Le
robots.txt
peut même diminuer la sécurité, en
indiquant les fichiers « intéressants » à un éventuel attaquant.
Passons maintenant à la pratique. On trouve plein de mises en
œuvre de robots.txt
un peu partout mais en
trouver une parfaitement conforme au RFC est bien plus
dur. Disons-le clairement, c'est le bordel, et l'auteur d'un
robots.txt
ne peut jamais savoir comment il va
être interprété. Il ne faut donc pas être trop subtil dans son
robots.txt
et en rester à des règles
simples. Du côté des robots, on a un problème analogue : bien des
robots.txt
qu'on rencontre sont bogués. La
longue période sans spécification officielle est largement
responsable de cette situation. Et tout n'est pas clarifié par le
RFC. Par exemple, la grammaire en section 2.2 autorise un
Disallow
ou un Allow
à
être vide, mais sans préciser clairement la sémantique associée.
Pour Python, il y a un module standard, mais qui est loin de suivre le RFC. Voici un exemple d'utilisation :
import urllib.robotparser import sys if len(sys.argv) != 4: raise Exception("Usage: %s robotstxt-file user-agent path" % sys.argv[0]) input = sys.argv[1] ua = sys.argv[2] path = sys.argv[3] parser = urllib.robotparser.RobotFileParser() parser.parse(open(input).readlines()) if parser.can_fetch(ua, path): print("%s can be fetched" % path) else: print("%s is denied" % path)
Et, ici, on se sert de ce code :
% cat ultra-simple.txt User-Agent: * Disallow: /private % python test-robot.py ultra-simple.txt foobot /public/test.html /public/test.html can be fetched % python test-robot.py ultra-simple.txt foobot /private/test.html /private/test.html is denied
Mais ce module ne respecte pas la précédence des règles :
% cat precedence.txt User-Agent: * Disallow: / Allow: /bar.html % python3 test-robot.py precedence.txt foobot /bar.html /bar.html is denied
En application de la règle la plus spécifique,
/bar.html
aurait dû être autorisé. Si on
inverse Allow
et Disallow
,
on obtient le résultat attendu. Mais ce n'est pas normal, l'ordre
des règles n'étant normalement pas significatif.
Autre problème du module Python, il ne semble pas gérer les
jokers, comme l'exemple *.gif$
du
RFC. Il existe des modules Python alternatifs pour traiter les
robots.txt
mais aucun ne semble vraiment mettre
en œuvre le RFC.
La situation n'est pas forcément meilleure dans les autres langages de programmation. Essayons avec Elixir. Il existe un module pour cela. Écrivons à peu près le même programme qu'en Python :
usage = "Usage: test robotstxtname useragent path" filename = case Enum.at(System.argv(), 0) do nil -> raise RuntimeError, usage other -> other end content = case File.read(filename) do {:ok, data} -> data {:error, reason} -> raise RuntimeError, "#{filename}: #{reason}" end ua = case Enum.at(System.argv(), 1) do nil -> raise RuntimeError, usage other -> other end statuscode = 200 {:ok, rules} = :robots.parse(content, statuscode) path = case Enum.at(System.argv(), 2) do nil -> raise RuntimeError, usage other -> other end IO.puts( case :robots.is_allowed(ua, path, rules) do true -> "#{path} can be fetched" false -> "#{path} can NOT be fetched" end)
Et testons-le :
% mix run test-robot.exs ultra-simple.txt foobot /public/test.html /public/test.html can be fetched % mix run test-robot.exs ultra-simple.txt foobot /private/test.html /private/test.html can NOT be fetched
Il semble avoir moins de bogues que le module Python mais n'est quand même pas parfait.
Comme dit plus haut, le robots.txt
n'est pas
réservé au Web. Il est utilisé par exemple pour
Gemini. Ainsi, le robot de
collecte Lupa lit les robots.txt
et ne
va pas ensuite récupérer les URI interdits. En septembre 2022, 11 %
des capsules Gemini avaient un robots.txt
.
Date de publication du RFC : Octobre 2022
Auteur(s) du RFC : D. Farinacci (lispers.net), F. Maino (Cisco Systems), V. Fuller (vaf.net Internet Consulting), A. Cabellos (UPC/BarcelonaTech)
Chemin des normes
Réalisé dans le cadre du groupe de travail IETF lisp
Première rédaction de cet article le 23 août 2024
Comme pour tous les protocoles de séparation de l'identificateur et du localisateur, le protocole LISP, normalisé dans le RFC 9300, doit faire face au problème de la correspondance (mapping) entre les deux informations. Comment trouver un localisateur, en ne connaissant que l'identificateur ? LISP n'a pas de solution unique et plusieurs protocoles de correspondance peuvent être utilisés. La stabilité du logiciel des routeurs imposait une interface stable avec le système de résolution des identificateurs en localisateurs. C'est ce que fournit notre RFC 9301, qui spécifie l'interface, vue du routeur, et qui ne devrait pas changer, même si de nouveaux systèmes de correspondance/résolution apparaissent. Ce RFC remplace le RFC 6833. L'interface change assez peu mais le texte est sérieusement réorganisé, et la spécification a désormais le statut de norme et plus simplement d'expérimentation.
LISP prévoit deux sortes de machines impliquées dans la résolution d'identificateurs (les EID, Endpoint ID) en localisateurs (les RLOC, Routing Locator). Ces deux machines sont les Map-Resolver et les Map-Server. Pour ceux qui connaissent le DNS, on peut dire que le Map-Server est à peu près l'équivalent du serveur faisant autorité et le Map-Resolver joue quasiment le même rôle que celui du résolveur. Toutefois, il ne faut pas pousser la comparaison trop loin, LISP a ses propres règles. Pour résumer en deux phrases, un routeur LISP d'entrée de tunnel (un ITR, Ingress Tunnel Router), ayant reçu un paquet à destination d'une machine dont il connait l'identificateur (l'EID), va interroger un Map-Resolver pour connaître le localisateur (le RLOC, auquel l'ITR enverra le paquet). Pour accomplir sa tâche, le Map-Resolver fera suivre les requêtes au Map-Server, qui la transmettra finalement au routeur de sortie du tunnel (l'ETR, Egress Tunnel Router), qui est la vraie source faisant autorité.
C'est entre le Map-Resolver et le Map-Server que se trouvent les détails du système de correspondance. Ils peuvent être connectés par ALT (RFC 6836), par CONS (RFC jamais publié), par NERD (RFC 6837), par DDT (RFC 8111) ou bien par tout autre système de résolution, existant ou encore à créer (ils ne peuvent pas être connectés avec simplement LISP, puisqu'on aurait alors un problème d'œuf et de poule, LISP ayant besoin de ALT qui aurait besoin de LISP… cf. section 8.1). Rappelez-vous que notre RFC 9301 ne décrit qu'une interface, celle des ITR et ETR avec les Map-Resolver et Map-Server. Il est donc relativement court.
Comme avec toute technique nouvelle, il est prudent d'apprendre
le vocabulaire (section 3, puis section 4 pour un survol général du
système). Il y a deux types d'acteurs, les
Map-Server et les Map-Resolver
que nous avons déjà vu, et trois types
importants de messages, Map-Register
(un
ETR l'envoie au Map-Server pour indiquer les RLOC
des EID dont il est responsable), Map-Request
(un ITR l'envoie à un Map-Resolver pour obtenir
les RLOC ; le Map-Resolver fait suivre jusqu'au
Map-Server, puis à l'ETR) et enfin
Map-Reply
, la réponse au précédent. Notons que
ces types de messages ont leur description complète (avec leur
format) dans le RFC 9300. Notez aussi
que Map-Resolver et Map-Server
sont des fonctions, et que les deux peuvent être assurés par la même
machine, qui serait à la fois Map-Resolver et
Map-Server (dans le DNS, un tel mélange est
déconseillé).
Schéma général du système de correspondance LISP :
La section 8 de notre RFC plonge dans les
détails. Accrochez-vous. Voyons d'abord le premier routeur LISP que
rencontrera le paquet. On le nomme ITR pour Ingress Tunnel
Router. Les routeurs précédents traitaient l'adresse de
destination du paquet comme une adresse IP
ordinaire. L'ITR, lui, va la traiter comme un identificateur (EID
pour Endpoint IDentification). L'EID n'est pas
routable sur l'Internet. Il faut donc encapsuler le paquet en LISP
pour l'envoyer dans le tunnel. La nouvelle adresse IP de destination
est le localisateur (RLOC pour Routing
LOCator). Pour trouver le localisateur, l'ITR va demander
à un ou plusieurs Map-Resolver. Il a été
configuré (typiquement, à la main) avec leurs adresses IP (qui
doivent être des localisateurs, pour éviter un amusant problème
d'œuf et de poule; notez que plusieurs
Map-Resolver peuvent avoir la même adresse, grâce
à l'anycast). L'ITR ne
connait que le protocole de résolution, envoi d'une
Map-Request
et récupération d'une
Map-Reply
(en termes DNS, l'ITR est un
stub resolver). L'ITR ne connait donc
pas les protocoles utilisés en interne par le
système de correspondance, il ne connait pas ALT (ou ses
concurrents). Cette communication avec le
Map-Resolver peut être testée et déboguée avec
l'outil lig (RFC 6835).
La réponse du Map-Resolver ne sera pas
forcément positive. L'ITR recevra peut-être une
negative Map-Reply
, envoyée
en réponse si un Map-Resolver ne trouve pas de
localisateur pour l'identificateur qu'on lui a passé. Cela veut dire
que le site final n'utilise pas LISP, et qu'il faut alors router le
paquet avec les méthodes habituelles d'IP. (Il n'est évidemment pas prévu que tout
l'Internet passe à LISP du jour au lendemain, le routeur LISP doit
donc aussi pouvoir joindre les sites non-LISP.)
Si la réponse est positive, l'ITR peut alors encapsuler le paquet et le transmettre. Comment le Map-Resolver a-t-il trouvé la réponse qu'il a envoyé ? Contrairement aux routeurs LISP comme l'ITR, le Map-Resolver et le Map-Server connaissent le système de correspondance utilisé (si c'est ALT, ils sont tous les deux routeurs ALT) et c'est celui-ci (non traité dans ce RFC) qu'ils utilisent pour savoir s'il y a une réponse et laquelle.
Et, à l'autre bout du tunnel, que s'était-il passé ? Le routeur
de fin de tunnel (l'ETR, pour Egress Tunnel
Router), avait été configuré par un administrateur réseaux
avec une liste d'EID dont il est responsable. Pour que le reste du
monde connaisse ces EID, il les publie auprès d'un
Map-Server en envoyant à ce dernier des messages
Map-Register
. Pour d'évidentes raisons de
sécurité, ces messages doivent être authentifiés (champ
Authentication Data
du message
Map-Register
, avec clés gérées à la main pour
l'instant, avec SHA-256 au minimum), alors
que les Map-Request
ne l'étaient pas (la base
de données consultée par les routeurs LISP est publique, pas besoin
d'authentification pour la lire, seulement pour y écrire). Ces
Map-Request
sont renvoyés périodiquement (le
RFC suggère toutes les minutes) pour que le
Map-Server sache si l'information est toujours à
jour. Ainsi, si un ETR est éteint, l'information obsolète dans les
Map-Server disparaîtra en trois minutes maximum
(des messages peuvent être perdus, le RFC demande donc de patienter
un peu en cas de non-réception). Cela veut aussi dire que LISP ne
convient pas forcément tel quel pour les situations où on exige une
mobilité très rapide.
Notez que je ne décris pas tous les détails (comme la possibilité pour un ETR de demander un accusé de réception au Map-Server, chose que ce dernier ne fait pas par défaut), voyez le RFC si vous êtes curieux.
Arrivés là, nous avons un Map-Server qui
connait les EID que gère l'ETR. Désormais, si ce
Map-Server reçoit une demande
Map-Request
, il peut la faire suivre à l'ETR
(si vous connaissez le DNS, vous avez vu que le
Map-Register
n'est pas tout à fait l'équivalent
des mises à jour dynamiques du RFC 2136 : avec
ces dernières, le serveur de noms qui a reçu la mise à jour répondra
ensuite lui-même aux requêtes). Le Map-Server ne
sert donc que de relais, il ne modifie pas la requête
Map-Request
, il la transmet telle quelle à
l'ETR. Le rôle des Map-Resolver et des
Map-Server est donc simplement de trouver l'ETR
responsable et de lui faire suivre (sans utiliser l'encapsulation
LISP) les requêtes, pas de répondre à sa place. Cela se fera
peut-être dans le futur lorsque des mécanismes de
cache seront ajoutés. Pour le moment, les
Map-Resolver n'ont pas de cache, de mémoire
(section 4), une
grosse différence avec le DNS (section 1).
La section 9 fait le tour des questions de sécurité liées au service de résolution. Comme les requêtes sont faites avec le format de paquets de LISP, elles héritent des services de sécurité de LISP comme le nonce qui permet de limiter les risques d'usurpation ou comme la sécurité LISP du RFC 9303. Par contre, comme pour les protocoles utilisés dans l'Internet actuel, il n'y a pas de vraie protection contre les annonces faites à tort (un Map-Server qui annoncerait un EID qui n'est pas à lui). C'est un problème très proche de celui de la sécurité de BGP et qui utilisera peut-être le même genre de solutions.
Notez qu'en théorie, l'interface spécifiée dans ce RFC pourrait servir à d'autres protocoles que celui du RFC 9300, comme par exemple GRE (RFC 2890) ou VXLAN (RFC 7348). Mais, pour l'instant, ce n'est pas le cas.
Il y a apparemment trois mises en œuvre. Outre l'outil de débogage lig (RFC 6835), il y a celle de Cisco pour ses routeurs, mais je ne connais pas les autres, sans doute dans des Unix.
Et les changements depuis le précédent RFC ? Ils sont résumés dans la section 11 :
Map-Notify-Ack
, un accusé de
réception,Date de publication du RFC : Octobre 2022
Auteur(s) du RFC : D. Farinacci (lispers.net), V. Fuller (vaf.net Internet Consulting), D. Meyer (1-4-5.net), D. Lewis (Cisco Systems), A. Cabellos (UPC/BarcelonaTech)
Chemin des normes
Réalisé dans le cadre du groupe de travail IETF lisp
Première rédaction de cet article le 14 décembre 2024
Le protocole LISP (qui n'a rien à voir avec le langage de programmation du même nom), vise à résoudre un problème documenté dans le RFC 4984 : les difficultés que rencontre le système de routage de l'Internet devant les pressions imposées par la croissance du réseau et par le désir des utilisateurs de ne pas être lié à un unique fournisseur d'accès. Actuellement, tout changement de routage sur n'importe quel site se propage à tous les autres routeurs de la DFZ. Un tel système ne passe pas réellement à l'échelle (il ne peut pas croitre indéfiniment). LISP propose une solution, dans un ensemble de RFC dont ce RFC 9300 (qui succède au RFC 6830) est le principal. LISP n'est désormais plus considéré comme expérimental et ce nouveau RFC est sur le chemin des normes (Standards Track) mais, en pratique, le projet LISP semble plutôt en panne.
Avant de plonger dans ce RFC, voyons les motivations pour LISP et ses principes essentiels (si vous préférez les lire dans le RFC et pas dans mon article, c'est en section 4 mais la lecture recommandée est le RFC 9299). Aujourd'hui, les adresses IP ont deux rôles, localisation (où suis-je connecté au réseau) et identité (qui suis-je). Une adresse IP est un localisateur car changer de point d'attachement (par exemple parce qu'on change de FAI) vous fait changer d'adresse IP, et c'est un identificateur car c'est ce qui est utilisé par les protocoles de transport comme TCP pour identifier une session en cours : changer d'adresse IP casse les connexions existantes.
Le principal problème de cet approche concerne le routage. Un routage efficace nécessiterait une cohérence entre les adresses et la topologie du réseau, par exemple que deux sites proches sur le réseau aient des adresses proches. Mais on n'a pas cette cohérence actuellement. On notera qu'IPv6 ne résolvait pas ce problème, et délibérement (le cahier des charges d'IPv6 ne prévoyait pas de changer le modèle de routage).
Résultat, les routeurs doivent gérer bien plus de routes que nécessaire, augmentant leur prix (en attendant le jour où, même en payant, on n'arrivera pas à manipuler autant de routes, avec leurs changements fréquents). Le RFC 4984 insistait sur ce problème en affirmant que « The workshop participants believe that routing scalability is the most important problem facing the Internet today and must be solved, although the time frame in which these problems need solutions was not directly specified. »
Cette croissance de la table de routage peut être suivie sur le célèbre site de Geoff Huston. Notez que la taille n'est pas le seul aspect, le rythme de changement (le nombre d'updates BGP par seconde) est au moins aussi important.
LISP vise donc à résoudre ce problème par une technique connue sous le nom de séparation du localisateur et de l'identificateur (son nom, « Locator/ID Separation Protocol », en dérive d'ailleurs, bien qu'il soit loin d'être le seul protocole dans cette catégorie). Au lieu que tous les préfixes IP aillent dans la DFZ, seuls les localisateurs, les RLOC (Routing LOCators) y iront. Les identificateurs, les EID (Endpoint IDentifiers) sont dans un nouveau système, le système de correspondance (mapping, RFC 9301), qui permettra de trouver un RLOC à partir d'un EID. LISP est donc l'application d'un vieux principe de l'informatique : « Tout problème peut être résolu en ajoutant un niveau d'indirection. »
À quoi ressemblent RLOC et EID ? Physiquement, ce sont juste des adresses IP (v4 ou v6), avec une nouvelle sémantique. Le RFC 8060 décrit plus en détail comment représenter les EID.
Par rapport aux autres solutions de séparation de l'identificateur et du localisateur (la plus achevée étant HIP), LISP s'identifie par les points suivants :
Comment se passe l'envoi d'un paquet avec LISP ? Supposons qu'une machine
veuille écrire à www.example.com
. Elle utilise
le DNS comme
aujourd'hui, pour trouver que l'adresse est
2001:db8:110:2::e9
(c'est un EID, un
identificateur, mais la machine n'a pas besoin de le savoir, les
machines terminales ne connaissent pas LISP et ne savent même pas
qu'elles l'utilisent). Elle envoie le paquet à son routeur
habituel. À un moment dans le réseau, le paquet va arriver dans un
routeur LISP (qui, lui, a été modifié pour gérer LISP). Il va alors
chercher le RLOC (le localisateur). Il demande au système de
correspondance (l'équivalent LISP du DNS) qui va lui dire « le RLOC de
2001:db8:110:2::e9
est
198.51.100.178
» (notez au passage que RLOC et
EID peuvent être des adresses v4 ou v6). (L'information est stockée
dans un cache du
routeur, pour le prochain paquet.) Le paquet est alors encapsulé dans un paquet
LISP qui est transmis en UDP (port 4341) à
198.51.100.178
. (En raison de ces deux étapes,
correspondance puis encapsulation, LISP fait partie des protocoles
nommés Map and Encap.)
198.51.100.178
décapsule et le paquet suit
ensuite un chemin normal vers la machine
2001:db8:110:2::e9
. Pendant le trajet dans le
tunnel,
le paquet aura donc deux en-têtes, l'interne
(celui placé par la machine d'origine et qui utilise des EID dans
les champs « Adresse IP source » et « Adresse IP destination ») et
l'externe (celui placé par le routeur d'entrée du tunnel, et qui
utilise des RLOC).
Si le système de correspondance avait répondu négativement « Ce
n'est pas un EID, je n'ai pas de RLOC pour
2001:db8:110:2::e9
» (Negative map
reply) ? Cela aurait voulu dire que le site cible
n'utilise pas LISP et, dans ce cas, on lui transmet le paquet par
les mécanismes habituels d'IP.
Ainsi, pour prendre le scénario d'usage principal de LISP, un site qui veut être multi-homé n'aura pas besoin de BGP et d'annoncer ses routes dans la DFZ. Il aura ses identificateurs, ses EID, et les paquets entrant ou sortant de son réseau seront encapsulés en LISP (le système de correspondance peut renvoyer plusieurs RLOC pour un EID, avec des préférences différentes, pour faire de l'ingénierie de trafic). Pour les routeurs de la DFZ, ce seront juste des paquets IP ordinaires. Seules les deux extrémités du tunnel sauront que LISP est utilisé.
Le système de correspondance de LISP n'est pas unique : plusieurs choix sont possibles comme ALT (RFC 6836). Comme le DNS, il fonctionne en tirant les informations nécessaires (pas en les poussant vers tout le monde, comme le fait BGP), ce qui devrait lui donner une bonne capacité de croissance. De toute façon, LISP spécifie une interface vers le système de correspondance (RFC 9301) et les différents systèmes ont juste à mettre en œuvre cette interface pour qu'on puisse en changer facilement. Ainsi, ALT pourra être remplacé par un de ses concurrents, CONS, EMACS ou NERD (leurs noms sont des références au langage de programmation). NERD est documenté dans le RFC 6837.
Ce RFC est un gros morceau (d'autant plus que d'autres parties de LISP sont dans d'autres RFC). Je ne vais pas le couvrir en entier. Mais quelques points méritent d'être gardés en tête :
Pour les fanas de format de paquets, la section 5 décrit l'encapsulation. LISP est indépendant de la famille d'adresses, donc on peut avoir un paquet IP où les EID sont IPv4 qui soit tunnelé avec des RLOC IPv6 ou bien le contraire. Devant le paquet normal, LISP va ajouter un en-tête IP standard pour les RLOC, où la source sera l'ITR (routeur d'entrée du tunnel) et la destination l'ETR (routeur de sortie du tunnel), puis un en-tête UDP (l'UDP a davantage de chances de passer les middleboxes que de l'IP mis directement dans l'IP, des protocoles comme QUIC ont le même problème), avec le port de destination à 4341, puis un en-tête spécifique à LISP et enfin le paquet original. Donc, pour résumer :
L'en-tête spécifique à LISP contient notamment (section 5 si vous voulez tout connaître) :
Comme toutes les solutions à base de tunnel, LISP va souffrir de la mauvaise gestion de la PMTUD dans l'Internet d'aujourd'hui (cf. RFC 4459), l'en-tête LISP supplémentaire réduisant la MTU (cf. section 7 pour des solutions possibles).
La section 5 décrivait les paquets de données, ceux encapsulant les données envoyées par le site original. La section 6 couvre les paquets de contrôle, utilisés par LISP pour ses propres besoins, notamment le système de correspondance (cf. RFC 9301 pour les détails). On y retrouve l'utilisation d'UDP :
Il est évidemment essentiel qu'on sache si son correspondant est joignable ou pas. Comment cette « joignabilité » est-elle vérifiée ? La section 6.3 énumère les mécanismes disponibles. Entre autres :
Mais on peut aussi tester explicitement, par le mécanisme Echo Nonce de la section 10.1. Le testeur émet un message LISP avec les bits N (numnique présent) et E (écho demandé), met le numnique à une valeur aléatoire (RFC 4086), et envoie le paquet. (La section 4.1 précise toutefois que cela ne doit pas être utilisé sur l'Internet public.) L'ETR à l'autre bout doit normalement renvoyer ce numnique dans son prochain paquet. Notons que cela teste la bidirectionnalité de la connexion. Si on n'obtient pas de réponse, cela peut signifier que la connexion est complètement coupée ou tout simplement qu'elle ne marche que dans un seul sens. Mais, surtout, l'absence de réponse peut indiquer le cas où l'ETR qui reçoit le trafic pour un site n'est pas le même routeur que l'ITR qui génère le trafic sortant. Dans ce cas, l'absence de réponse n'est pas un problème. Enfin, le routeur en face peut tout simplement être configuré pour ignorer les demandes d'écho.
Une autre solution pour tester est donc d'utiliser les messages du système de correspondance EID->RLOC, les Map Request et Map Reply. Ces messages ont un bit P (pour probe) qui indique que le demandeur est intéressé par la joignabilité du site demandé.
LISP impose donc des traitements supplémentaires, par rapport à ceux de l'IP classique. Est-ce que cela ne ralentit pas trop les routeurs ? La section 15 explore le problème et explique pourquoi LISP ne nécessite pas de changement du matériel de forwarding (les ASIC du routeur). La plupart des routeurs ont déjà du code prévu pour gérer des tunnels (encapsuler et décapsuler) rapidement.
Comment sera déployé LISP ? Le RFC 7215 décrit plusieurs scénarios possibles et les détails. Principal problème : combien d'ITR et d'ETR pour un opérateur ? Grâce aux tunnels, on peut n'avoir qu'un seul ITR et un seul ETR pour tout le trafic. Cela poserait évidemment des problèmes de redondance et de performance. Mais avoir beaucoup de xTR peut poser des problèmes d'administration. Si les ITR sont largement automatiques (leur cache des correspondance EID->RLOC est bâti dynamiquement), avoir beaucoup d'ETR risque d'être compliqué à maintenir (car l'ETR doit avoir dans sa configuration une liste des EID qu'il va gérerà).
Un des gros problèmes que pose la séparation de l'identificateur et du localisateur est la sécurité : nouvelles attaques (par exemple contre le système de correspondance), nouveaux pièges (une machine qui utiliserait le vrai RLOC mais mentirait sur l'EID, par exemple). La section 16 du RFC examine les risques théoriquement présents dans LISP, mais lisez aussi les RFC 7835 et RFC 9303.
Comme avec toutes les techniques de tunnel, un émetteur peut facilement tricher sur l'adresse source interne (celle qui sera utilisée après décapsulation par l'ETR). Pour se protéger, un ITR devrait n'encapsuler un paquet que si l'adresse source est un EID qu'il contrôle. Et un ETR ne devrait transmettre un paquet que si la destination est un EID sous sa responsabilité.
Le test de la réversibilité (via les numniques, cf. section 3) est essentiel contre ces risques. Sans ce test, un ETR pirate pourrait par exemple envoyer un Map Reply en réponse aveugle à un Map Request, et le voir accepté, avec des données mensongères (naturellement, l'ITR n'accepte que des Map Replies qui sont en réponse à un Map Request de sa part). Avec ce système de numnique que le récepteur doit mettre dans sa réponse, un attaquant aveugle (qui n'est pas situé sur le chemin des paquets et ne peut donc pas les observer) aura donc peu de chances de réussir à faire accepter ses paquets.
En revanche, un attaquant situé sur le chemin, et qui peut observer les paquets qui passent, a la possibilité de commettre bien plus de dégâts. Mais c'est déjà le cas avec les protocoles actuels (par exemple, les numéros de séquence difficiles à deviner du RFC 6528 ne protègent pas TCP contre des attaquants situés sur le chemin).
Les attaques par déni de service sont évidemment possibles avec LISP : une des précautions recommandées est de limiter le trafic des Map Requests et Map Replies. Autre attaque par déni de service, un ITR peut être victime d'une machine qui essaie de remplir la table des correspondances EID->RLOC du routeur. Il est donc important d'envisager ce cas, par exemple en permettant de garder dans le cache les entrées les plus fréquemment accédées (pour éviter qu'elles ne soient retirées automatiquement pour faire de la place). Mais il n'existe pas de solution miracle contre ce problème d'envahissement de cache.
Le fonctionnement de LISP est schématisé sur ce dessin : Alice a l'identificateur (EID)
2001:db8:1::1
et veut écrire à Bob qui a le
2001:db8:2::42
(dans la plupart des cas, Alice
aura trouvé l'adresse de Bob dans le DNS, comme aujourd'hui). Ni Alice, ni Bob n'ont
à connaître LISP, chacun croit utiliser de l'IP normal. Alice envoie
donc un paquet standard, à destination de
2001:db8:2::42
. Mais, sur le trajet, il y a un
ITR, un routeur LISP. Celui-ci va chercher (dans le système de
correspondance, non montré ici) le RLOC (le localisateur) de Bob
(ou, plus exactement, de son site). Une fois qu'il a trouvé
2001:db8:ff::666
, il encapsule le paquet
derrière un en-tête LISP et envoie ce paquet à l'ETR, l'autre
routeur LISP en face, 2001:db8:ff::666
. Les
routeurs de l'Internet, entre l'ITR et l'ETR, ne connaissent pas
LISP non plus et routent ce qui leur semble un paquet IP
normal. Arrivé à l'ETR, le paquet est décapsulé et poursuit son
chemin par du routage IP classique. Sur tout le schéma, seuls l'ITR
et l'ETR sont des nouveautés LISP.
Modifions légèrement le schéma pour y mettre le système de
correspondance : On y voir l'ITR
demander à son résolveur « Quel est le localisateur de
2001:db8:2::42
? » et son résolveur lui
répondre. Le résolveur avait demandé au serveur qui avait reçu de
l'ETR un enregistrement disans « Le localisateur de
2001:db8:2::42
est
2001:db8:ff::666
». Entre le résolveur et le
serveur se trouve le cœur du système de correspondance. LISP
en a plusieurs possibles, comme le ALT du RFC 6836.
Où trouve-t-on du code LISP aujourd'hui ?
Comme pour tous les protocoles fondés sur le principe de la séparation de l'identificateur et du localisateur, il est toujours utile, si on veut en comprendre les principes, de (re)lire l'article fondateur de Chiappa, « Endpoints and Endpoint names: A Proposed Enhancement to the Internet Architecture ». Autres articles à lire :
La section 18 de notre RFC décrit les changements depuis le RFC 6830. Le principal est bien sûr que LISP n'est plus considéré comme expérimental, on peut le déployer sur l'Internet public, modulo les précautions de la section 4.1. Sinon :
La section 4.1 décrit ce qu'il faut faire et ne pas faire lorsqu'on déploie LSIP « en vrai » sur l'Internet public. Quand on n'est plus expérimental, il faut faire attention. Bien des techniques de LISP (comme la mise à jour automatique des données des routeurs juste en regardant un paquet qui passe, le gleaning, clairement dangereux) ne sont sûres que dans un environnement fermé.
Date de publication du RFC : Octobre 2022
Auteur(s) du RFC : A. Cabellos
(UPC-BarcelonaTech), D. Saucez
(INRIA)
Pour information
Réalisé dans le cadre du groupe de travail IETF lisp
Première rédaction de cet article le 3 novembre 2022
Le protocole réseau LISP (Locator/ID Separation Protocol, rien à voir avec le langage de programmation du même nom) est normalisé dans le RFC 6830 et plusieurs autres qui l'ont suivi. Ce RFC 6830 est un peu long à lire et ce nouveau RFC propose donc une vision de plus haut niveau, se focalisant sur l'architecture de LISP. Cela peut donc être un bon point de départ vers les RFC LISP.
L'idée de base de LISP est de séparer l'identificateur du localisateur, comme recommandé par le RFC 4984. Un identificateur désigne une machine, un localisateur sa position dans l'Internet. Aujourd'hui, les adresses IP servent pour les deux, ne satisfaisant parfaitement aucun des deux buts : les identificateurs devraient être stables (une machine qui change de réseau ne devrait pas en changer), les localisateurs devraient être efficaces et donc être liés à la topologie, et agrégeables.
LISP a donc deux classes : les identificateurs, ou EID (End-host IDentifier) et les localisateurs, ou RLOC (Routing LOCators). Les deux ont la syntaxe des adresses IP (mais pas leur sémantique). Un système de correspondance permet de passer de l'un à l'autre (par exemple, je connais l'EID de mon correspondant, je cherche le RLOC pour lui envoyer un paquet). LISP est plutôt prévu pour être mis en œuvre dans les routeurs, séparant un cœur de l'Internet qui n'utilise que les RLOC, d'une périphérie qui utiliserait les EID (avec, entre les deux, les routeurs LISP qui feraient la liaison).
En résumé (accrochez-vous, c'est un peu compliqué) :
C'est ce côté « solution dans les routeurs » et donc le fait que les machines terminales ne savent même pas qu'elles font du LISP, qui distingue LISP des autres solutions fondées sur la séparation de l'identificateur et du localisateur, comme ILNP (RFC 6740).
Au passage, pourquoi avoir développé LISP ? Quel était le problème à résoudre ? Outre la beauté conceptuelle de la chose (il n'est pas esthétique de mêler les fonctions d'identificateur et de localisateur), le principal problème à traiter était celui du passage à l'échelle du système de routage de l'Internet, décrit dans le RFC 4984. En gros, le nombre de routes distinctes augmente trop vite et menace la stabilité des routeurs de la DFZ. Le but était donc, par une nouvelle architecture, de rendre inutile certains choix qui augmentent la taille de la table de routage (comme la désagrégation des préfixes IP, afin de faire de l'ingénierie de trafic). Le RFC 7215 décrit comment LISP aide dans ce cas.
La section 7 du RFC décrit les différents scénarios d'usage de LISP :
La section 3 du RFC décrit en détail l'architecture de LISP (après, vous serez mûr·e·s pour lire les RFC LISP eux-mêmes, en commençant par le RFC 6830). Elle repose sur quatre principes :
La séparation entre identificateur et localisateur n'est pas faite au niveau de la machine individuelle, comme avec ILNP, mais à la frontière entre la périphérie de l'Internet (the edge) et son cœur (the core, en gros, la DFZ et quelques routeurs en plus). La périphérie travaille avec des EID (elle ne sait même pas que LISP est utilisé), le cœur avec des RLOC. Contrairement à ILNP, il n'y a donc pas une stricte séparation entre identificateurs et localisateurs : ils ont la même syntaxe (qui est celle d'une adresse IP, v4 ou v6) et, à part les routeurs d'entrée et de sortie des tunnels LISP, personne ne sait s'il utilise un EID ou un RLOC : les machines terminales manipulent des EID, les routeurs du cœur des RLOC, tout en croyant que ce sont des adresses IP ordinaires. Seuls les routeurs à la frontière entre les deux mondes connaissent LISP (et auront donc besoin d'un logiciel adapté).
Un Internet LISP est donc une série de « sites LISP » (des réseaux de périphérie accessibles par LISP) connectés par des tunnels entre eux, au-dessus du cœur actuel. Le routeur d'entrée du tunnel se nomme ITR (pour Ingress Tunnel Router) et celui de sortie ETR (pour Egress Tunnel Router). Le terme de xTR (pour « ITR ou bien ETR ») est parfois utilisé pour désigner un routeur LISP, qu'il soit d'entrée ou de sortie
Le sous-système des données (data plane) se charge d'encapsuler et de décapsuler les paquets, puis de les transmettre au bon endroit. (Sa principale qualité est donc la rapidité : il ne faut pas faire attendre les paquets.) Les ITR encapsulent un paquet IP qui vient d'un site LISP (dans un paquet UDP à destination du port 4341), puis l'envoient vers l'ETR. À l'autre bout du tunnel, les ETR décapsulent le paquet. Dans le tunnel, les paquets ont donc un en-tête intérieur (un en-tête IP normal, contenant les EID source et destination), qui a été placé par la machine d'origine, et un en-tête extérieur (contenant le RLOC source, celui de l'ITR, et le RLOC de destination, celui de l'ETR puis, après l'en-tête UDP, l'en-tête spécifique de LISP). Rappelez-vous que les routeurs du cœur ne connaissent pas LISP, ils font suivre ce qui leur semble un paquet IP ordinaire. Les routeurs LISP utilisent des tables de correspondance entre EID et RLOC pour savoir à quel ETR envoyer un paquet.
Ces tables ont été apprises du sous-système de contrôle (control plane, qui contient la fonction de correspondance - mapping), le routeur ayant un cache des correspondances les plus récentes. Cette fonction de correspondance, un des points les plus délicats de LISP (comme de tout système de séparation de l'identificateur et du localisateur) est décrite dans le RFC 6833. Son rôle peut être comparé à celui du DNS et de BGP dans l'Internet classique.
Une correspondance est une relation entre un préfixe d'identificateurs (rappelez-vous que les EID sont, syntaxiquement, des adresses IP ; on peut donc utiliser des préfixes CIDR) et un ensemble de localisateurs, les RLOC des différents routeurs possibles pour joindre le préfixe convoité.
Le RFC 6833 normalise une interface avec ce système de correspondance. Il y a deux sortes d'entités, le Map Server, qui connait les correspondances pour certains préfixes (car les ETR lui ont raconté les préfixes qu'ils servent), et le Map Resolver, qui fait les requêtes (il est typiquement dans l'ITR, ou proche). Quatre messages sont possibles (les messages de contrôle LISP sont encpasulés en UDP, et vers le port 4342) :
Map-Register
: un ETR informe son
Map Server des préfixes EID qu'il sait
joindre,Map-Notify
: la réponse de l'ETR au
message précédent,Map-Request
: un ITR (ou bien un
outil de débogage comme lig, cf. RFC 6835) cherche les RLOC correspondant à un EID,Map-Reply
: un Map
Server ou un ETR lui répond.Un point important de LISP est qu'il peut y avoir plusieurs mécanismes de correspondance EID->RLOC, du moment qu'ils suivent les messages standard du RFC 6833. On pourra donc, dans le cadre de l'expérience LISP, changer de mécanisme pour voir, pour tester des compromis différents. Notre RFC rappele l'existence du système ALT (RFC 6836, fondé, comme BGP sur un graphe. Mais aussi celle d'un mécanisme utilisant une base « plate » (NERD, RFC 6837), un mécanisme arborescent nommé DDT (RFC 8111), des DHT, etc. On pourrait même, dans des déploiements privés et locaux, avoir une base centralisée avec un seul serveur.
ALT, normalisé dans le RFC 6836, est le système de correspondance « historique » de LISP, et il est souvent présenté comme le seul dans les vieux documents. Il repose sur BGP, protocole bien maitrisé par les administrateurs de routeurs, ceux qui auront à déployer LISP. L'idée de base est de connecter les serveurs ALT par BGP sur un réseau virtuel au-dessus de l'Internet.
DDT, dans le RFC 8111, lui, ressemble beaucoup plus au DNS, par sa structuration arborescente des données, et sa racine.
Évidemment, tout l'Internet ne va pas migrer vers LISP instantanément. C'est pour cela que notre RFC mentionne les problèmes de communication avec le reste du monde. Les EID ne sont typiquement pas annoncés dans la table de routage globale de l'Internet. Alors, comment un site pourra-t-il communiquer avec un site LISP ? Le mécanisme décrit dans le RFC 6832 utilise deux nouvelles sortes de routeurs : les PITR (Proxy Ingress Tunnel Router) et les PETR (Proxy Egress Tunnel Router). Le PITR annonce les EID en BGP vers l'Internet, en les agrégeant le plus possible (l'un des buts de LISP étant justement d'éviter de charger la table de routage globale). Il recevra donc les paquets envoyés par les sites Internet classiques et les fera suivre par les procédures LISP normales. A priori, c'est tout : le site LISP peut toujours envoyer des paquets vers l'Internet classiques en ayant mis un EID en adresse IP source. Mais cela peut échouer pour diverse raisons (uRPF, par exemple) donc on ajoute le PETR : il recevra le paquet du site LISP et le transmettra.
Voici pour les principes de LISP. Mais, si vous travaillez au quotidien comme administrateur d'un réseau, vous avez sans doute à ce stade plein de questions concrètes et opérationnelles. C'est le moment de lire la section 4 de ce RFC. Par exemple, la gestion des caches : un routeur LISP ne peut pas faire appel au système de correspondance pour chaque paquet qu'il a à transmettre. Le sous-système des données tuerait complètement le sous-système de contrôle, si un routeur s'avisait de procéder ainsi. Il faut donc un cache, qui va stocker les réponses aux questions récentes. Qui dit cache dit problèmes de cohérence des données, puisque l'information a pu changer entre la requête, et l'utilisation d'une réponse mise en cache. Pour gérer cette cohérence, LISP dispose de divers mécanismes, notamment un TTL (Time To Live) : l'ETR le définit, indiquant combien de temps les données peuvent être utilisées (c'est typiquement 24 h, aujourd'hui).
Autre problème pratique cruciale, la joignabilité des
RLOC. C'est bien joli de savoir que telle machine a tel RLOC mais
est-ce vrai ? Peut-on réellement lui parler ou bien tous les
paquets vont-ils finir dans un trou noir ? Un premier mécanisme
pour transporter l'information de joignabilité est les LSB
(Locator Status Bits). Transportés dans les
paquets LISP, ces bits indiquent si un RLOC donné est joignable
par l'ETR qui a envoyé le paquet. Évidemment, eux aussi peuvent
être faux, donc, s'il existe une communication bi-directionnelle,
il est préférable d'utiliser le mécanisme des numniques. Quand un ITR écrit à un ETR, il
met un numnique dans le paquet, que l'ETR renverra dans son
prochain paquet. Cette fois, plus de doute, l'ETR est bien
joignable. Si l'ITR est impatient et veut une réponse tout de
suite, il peut tester activement la joignabilité, en envoyant des Map-Request
.
LISP est souvent présenté avec un modèle simplifié où chaque site est servi par un seul ETR, qui connait les EID du site et les annonce au Map Server. Mais, dans la réalité, les sites sérieux ont plusieurs ETR, pour des raisons de résilience et de répartition de charge. Cela soulève le problème de leur synchronisation : ces ETR doivent avoir des configurations compatibles, pour annoncer les mêmes RLOC pour leurs EID. Pour l'instant, il n'existe pas de protocole pour cela, on compte sur une synchronisation manuelle par l'administrateur réseaux.
Enfin, comme LISP repose sur des tunnels, il fait face à la malédiction habituelle des tunnels, les problèmes de MTU. Du moment qu'on encapsule, on diminue la MTU (les octets de l'en-tête prennent de la place) et on peut donc avoir du mal à parler avec les sites qui ont une MTU plus grande, compte-tenu de la prévalence d'erreurs grossières de configuration, comme le filtrage d'ICMP. La section 4.4 de notre RFC décrit le traitement normal de la MTU dans LISP et ajoute que des mécanismes comme celui du RFC 4821 seront peut-être nécessaires.
Dans l'Internet d'aujourd'hui, une préoccupation essentielle est bien sûr la sécurité : d'innombrables menaces pèsent sur les réseaux (section 8 du RFC). Quelles sont les problèmes spécifiques de LISP en ce domaine ? Par exemple, certains systèmes de correspondance, comme DDT, sont de type pull : on n'a pas l'information à l'avance, on va la chercher quand on en a besoin. Cela veut dire qu'un paquet de données (sous-système des données) peut indirectement déclencher un événement dans le sous-système de contrôle (par la recherche d'une correspondance EID->RLOC afin de savoir où envoyer le paquet). Cela peut affecter la sécurité.
D'autant plus que le sous-système de contrôle sera typiquement
mis en œuvre dans le processeur généraliste des routeurs, beaucoup
moins rapide que les circuits électroniques spécialisés qui
servent à la transmission des données. Un attaquant qui enverrait
des tas de paquets vers des EID différents pourrait, à un coût
très faible pour lui, déclencher plein de demandes à DDT,
ralentissant ainsi sérieusement les routeurs LISP. Une mise en œuvre naïve de LISP où toute requête pour
un EID absent du cache déclencherait systématiquement une
MAP-Request
serait très vulnérable. Une
limitation du trafic est donc
nécessaire.
En parlant du système de correspondance, il représente évidemment un talon d'Achille de LISP. Si son intégrité est compromise, si des fausses informations s'y retrouvent, les routeurs seront trahis et enverront les paquets au mauvais endroit. Et si le système de correspondance est lent ou en panne, par exemple suite à une attaque par déni de service, le routage ne se fera plus du tout (à part pour les EID encore dans les caches des routeurs). On peut donc comparer ce système de correspondance au DNS dans l'Internet classique, par son côté crucial pour la sécurité.
Il faut donc des « bons » Map Server, qui suivent bien le RFC 6833 (notamment sa section 6) et, peut-être dans le futur, des Map Servers qui gèrent l'extension de sécurité LISP-Sec (si son RFC est publié un jour).
Dernier point de sécurité, le fait que LISP puisse faire du routage asymétrique (le chemin d'Alice à Bob n'est pas le même que celui de Bob à Alice). Rien d'extraordinaire à cela, c'est pareil pour lee routage Internet classique, mais il faut toujours se rappeler que cela a des conséquences de sécurité : par exemple, un pare-feu ne verra, dans certains cas, qu'une partie du trafic.
On trouvera plus de détails sur les attaques qui peuvent frapper LISP dans le RFC 7835.
Pour ceux qui sont curieux d'histoire des technologies, l'annexe A du RFC contient un résumé de LISP. Tout avait commencé à Amsterdam en octobre 2006, à l'atelier qui avait donné naissance au RFC 4984. Un groupe de participants s'était alors formé, avait échangé, et le premier Internet-Draft sur LISP avait été publié en janvier 2007. En même temps, et dans l'esprit traditionnel de l'Internet (running code), la programmation avait commencé et les premiers routeurs ont commencé à gérer des paquets LISP en juin 2007.
Le groupe de travail IETF officiel a été ensuite créé, en mars 2009. Les premiers RFC sont enfin sortis en 2013.
LISP n'a pas toujours été comme aujourd'hui ; le protocole initial était plutôt une famille de protocoles, désignés par des numéros, chacun avec des variantes sur le concept de base. Cela permettait de satisfaire tous les goûts mais cela compliquait beaucoup le protocole. On avait LISP 1, où les EID étaient routables dans l'Internet normal (ce qui n'est plus le cas), LISP 1.5 où ce routage se faisait dans un réseau séparé, LISP 2 où les EID n'étaient plus routables, et où la correspondance EID->RLOC se faisait avec le DNS, et enfin LISP 3 où le système de correspondance était nouveau (il était prévu d'utiliser une DHT...). Le LISP final est proche de LISP 3.
Date de publication du RFC : Août 2022
Auteur(s) du RFC : D. Schinazi (Google), L. Pardue (Cloudflare)
Chemin des normes
Réalisé dans le cadre du groupe de travail IETF masque
Première rédaction de cet article le 8 septembre 2022
Ce RFC est le premier du groupe de travail MASQUE qui développe des techniques pour relayer du trafic IP. Dans ce RFC, il s'agit de faire passer des datagrammes sur HTTP.
Pourquoi, sur HTTP ? Parce que c'est le seul protocole dont on est raisonnablement sûr qu'il passera partout. En bâtissant un service de datagrammes sur HTTP (et non pas directement sur IP), on arrivera à le faire fonctionner à tous les endroits où HTTP fonctionne.
En fait, ce RFC décrit deux mécanismes assez différents, mais qui visent le même but, la transmission de datagrammes sur HTTP. Le premier mécanisme est le plus simple, utiliser HTTP juste pour lancer une session qui utilisera les datagrammes de QUIC, normalisés dans le RFC 9221. (QUIC seul ne passe pas partout, d'où la nécessité d'insérer une couche HTTP.) Ce premier mécanisme dépend de QUIC donc ne marche qu'avec HTTP/3 (RFC 9114). Un second mécanisme est normalisé, dans notre RFC ; nommé Capsule, il permet de faire passer des datagrammes (ou à peu près) sur HTTP/2 (RFC 9113) et HTTP/1 (RFC 9112). Capsule est plus général, mais moins efficace.
Ces mécanismes ne sont pas vraiment prévus pour des utilisations par des applications, mais plutôt pour des extensions à HTTP (RFC 9110, section 16). Par exemple, le service CONNECT (RFC 9110, section 9.3.6) pourrait être doublé par un service équivalent, mais utilisant des datagrammes, au lieu du transfert fiable que fait CONNECT (cf. RFC 9298). Même chose pour les Web sockets du RFC 6455.
La section 2 de notre RFC explique de quels datagrammes on parle. Il s'agit de
paquets de données dont l'ordre
d'arrivée et l'acheminement ne sont pas garantis, et qui vont sans
doute consommer moins de ressources. Sur HTTP/3 (qui utilise QUIC),
ils vont utiliser les trames QUIC de type
DATAGRAM
(RFC 9221). Ce
sont les « meilleurs » datagrammes HTTP, ceux qui collent le plus à
la sémantique des datagrammes. Sur HTTP/2, tout acheminement de
données est garanti. Si on veut faire des datagrammes, il va falloir
utiliser le protocole Capsule, décrit dans la
section 3. (Notez que HTTP/2 ne garantit pas l'ordre d'arrivée si
les datagrammes sont transportés dans des ruisseaux différents.) Et
pour HTTP/1 ? Le protocole ne convient guère puisqu'il garantit
l'ordre d'arrivée et l'acheminement, justement les propriétés
auxquelles on était prêt à renoncer. Là aussi, on se servira de
Capsule.
HTTP utilise différentes méthodes pour faire ses requêtes (les deux plus connues sont GET et POST). Les datagrammes ne sont utilisables qu'avec des méthodes qui les acceptent explicitement. Donc, pas de GET ni de POST, plutôt du CONNECT.
Sur HTTP/3, le champ de données du datagramme QUIC aura le format :
HTTP/3 Datagram { Quarter Stream ID (i), HTTP Datagram Payload (..), }
Le quarter stream ID est l'identificateur du ruisseau QUIC du client où a été envoyée la requête HTTP, divisé par 4 (ce qu'on peut toujours faire, vu la façon dont sont générés ces identificateurs, RFC 9000, section 2.1).
Ah, et comme les datagrammes ne sont pas acceptés par défaut en
HTTP/3, il faudra envoyer au début de la session un paramètre
SETTINGS_H3_DATAGRAM
(enregistré à
l'IANA).
Les capsules, maintenant. Inutiles en HTTP/3, elles sont la seule
façon de faire du datagramme en HTTP/1 et HTTP/2. Le protocole
Capsule permet d'envoyer des données encodées en
TLV sur une connexion HTTP. On indique qu'on
va l'utiliser, en HTTP/1 avec le champ Upgrade:
(RFC 9110, section 7.8) et en HTTP/2 avec un
CONNECT étendu (cf. RFC 8441). Les
upgrade tokens (les identificateurs de protocoles
utilisés, enregistrés à
l'IANA) sont décrits dans la section 16.7 du RFC 9110.
Le format des capsules est décrit en section 3.2 :
Capsule { Capsule Type (i), Capsule Length (i), Capsule Value (..), }
Les différents types possibles de capsules figurent dans un registre IANA. Une fois le protocole Capsule sélectionné via un upgrade token, on passe du HTTP classique au protocole Capsule. Un premier type de capsule est DATAGRAM (type 0) dont le nom indique bien la sémantique. D'autres types pourront être ajoutés en suivant la procédure « spécification nécessaire » du RFC 8126, sauf les valeurs basses du type (de 0 à 63) qui exigent une action de normalisation, ou bien une approbation par l'IESG.
Si on utilise Capsule, la requête HTTP est accompagnée du champ
Capsule-Protocol
(un champ structuré, cf. RFC 9651), champ désormais enregistré
à l'IANA.
Il existe plusieurs mises en œuvre de ces datagramms HTTP. En logiciel libre, on a :
Notez que le protocole Capsule n'est a priori pas accessible au code JavaScript chargé dans le navigateur Web (il faut pouvoir accéder aux upgrade tokens). Mais rappelez-vous que tout ce mécanisme de datagrammes sur HTTP est conçu pour des extensions de HTTP, pas pour l'application finale.
Date de publication du RFC : Août 2022
Auteur(s) du RFC : W. Eddy (MTI Systems)
Chemin des normes
Réalisé dans le cadre du groupe de travail IETF tcpm
Première rédaction de cet article le 20 août 2022
Le protocole de transport TCP est l'un des piliers de l'Internet. La suite des protocoles Internet est d'ailleurs souvent appelée TCP/IP, du nom de ses deux principaux protocoles. Ce nouveau RFC est la norme technique de TCP, remplaçant l'antique RFC 793, qui était vieux de plus de quarante ans, plus vieux que la plupart des lecteurices de ce blog. Il était temps de réviser et de rassembler en un seul endroit tous les détails sur TCP.
TCP est donc un protocole de transport. Rappelons brièvement ce qu'est un protocole de transport et à quoi il sert. L'Internet achemine des paquets IP de machine en machine. IP ne garantit pas leur arrivée : les paquets peuvent être perdus (par exemple parce qu'un routeur n'avait plus de place dans ses files d'attente), peuvent être dupliqués, un paquet peut passer devant un autre, pourtant envoyé avant, etc. Pour la grande majorité des applications, ce n'est pas acceptable. La couche de transport est donc chargée de remédier à cela. Première (en partant du bas) couche à être de bout en bout (les routeurs et autres équipements intermédiaires n'y participent pas, ou plus exactement ne devraient pas y participer), elle se charge de suivre les paquets, de les remettre dans l'ordre, et de demander aux émetteurs de ré-émettre si un paquet s'est perdu. C'est donc un rôle crucial, puisqu'elle évite aux applications de devoir gérer ces opérations très complexes. (Certaines applications, par exemple de vidéo, n'ont pas forcément besoin que chaque paquet arrive, et n'utilisent pas TCP.) Le RFC 8095 contient une comparaison détaillée des protocoles de transport de l'IETF.
Avant de décrire TCP, tel que spécifié dans ce RFC 9293, un petit mot sur les normes techniques de l'Internet. TCP avait originellement été normalisé dans le RFC 761 en 1980 (les couches 3 - IP et 4 étaient autrefois mêlées en une seule). Comme souvent sur l'Internet, le protocole existait déjà avant sa normalisation et était utilisé. La norme avait été rapidement révisée dans le RFC 793 en 1981. Depuis, il n'y avait pas eu de révision générale, et les RFC s'étaient accumulés. Comprendre TCP nécessitait donc de lire le RFC 793 puis d'enchainer sur plusieurs autres. Un RFC, le RFC 7414, avait même été écrit dans le seul but de fournir un guide de tous ces RFC à lire. Et il fallait également tenir compte de l'accumulation d'errata. Désormais, la situation est bien plus simple, tout TCP a été consolidé dans un RFC central, notre RFC 9293. (Notez que ce travail de consolidation a été aussi fait pour HTTP et SMTP, qui ne sont plus décrits par les RFC d'origine mais par des versions à jour, mais pas pour le DNS, qui reste le principal ancien protocole qui aurait bien besoin d'une remise à jour complète des documents qui le spécifient.) Ne vous faites toutefois pas d'illusion : malgré ses 114 pages, ce RFC 9293 ne couvre pas tout, et la lecture d'autres RFC reste nécessaire. Notamment, TCP offre parfois des choix multiples (par exemple pour les algorithmes de contrôle de la congestion, une partie cruciale de TCP), qui ne sont pas décrits intégralement dans la norme de base. Voyez par exemple le RFC 7323, qui n'a pas été intégré dans le RFC de base, et reste une extension optionnelle.
Attaquons-nous maintenant à TCP. (Bien sûr, ce sera une présentation très sommaire, TCP est forcément plus compliqué que cela.) Son rôle est de fournir aux applications un flux d'octets fiable et ordonné : les octets que j'envoie à une autre machine arriveront dans l'ordre et arriveront tous (ou bien TCP signalera que la communication est impossible, par exemple en cas de coupure du câble). TCP découpe les données en segments, chacun voyageant dans un datagramme IP. Chaque octet est numéroté (attention : ce sont bien les octets et pas les segments qui sont numérotés) et cela permet de remettre dans l'ordre les paquets, et de détecter les pertes. En cas de perte, on retransmet. Le flux d'octets est bidirectionnel, la même connexion TCP permet de transmettre dans les deux sens. TCP nécessite l'établissement d'une connexion, donc la mémorisation d'un état, par les deux pairs qui communiquent. Outre les tâches de fiabilisation des données (remettre les octets dans l'ordre, détecter et récupérer les pertes), TCP fournit du démultiplexage, grâce aux numéros de port. Ils permettent d'avoir plusieurs connexions TCP entre deux machines, qui sont distinguées par ces numéros de port (un pour la source et un pour la destination) dans l'en-tête TCP. Le bon fonctionnement de TCP nécessite d'édicter un certain nombre de règles et le RFC les indique avec MUST-n où N est un entier. Par exemple, MUST-2 et MUST-3 indiqueront que la somme de contrôle est obligatoire, aussi bien en envoi qu'en vérification à la réception.
Après ces concepts généraux, la section 3 de notre RFC rentre dans les détails concrets, que je résume ici. D'abord, le format de cet en-tête que TCP ajoute derrière l'en-tête IP et devant les données du segment. Il inclut les numéros de port, le numéro de séquence (le rang du premier octet dans les données), celui de l'accusé de réception (le rang du prochain octet attendu), un certain nombre de booléens (flags, ou bits de contrôle, qui indiquent, par exemple, s'il s'agit d'une connexion déjà établie, ou pas encore, ou la fin d'une connexion), la taille de la fenêtre (combien d'octets peuvent être envoyés sans accusé de réception, ce qui permet d'éviter de surcharger le récepteur), une somme de contrôle, et des options, de taille variable (l'en-tête contient un champ indiquant à partir de quand commencent les données). Une option de base est MSS (Maximum Segment Size) qui indique quelle taille de segment on peut gérer. Il existe d'autres options comme les accusés de réception sélectifs du RFC 2018 ou comme le facteur multiplicatif de la taille de fenêtre du RFC 7323.
Avant d'expliquer la dynamique de TCP, le RFC définit quelques
variables indispensables, qui font partie de l'état de la connexion
TCP. Pour l'envoi de données, ce sont par exemple
SND.UNA
(ces noms sont là pour comprendre le
RFC, mais un programme qui met en œuvre TCP peut évidemment nommer
ces variables comme il veut), qui désigne le numéro de séquence du
début des données envoyées, mais qui n'ont pas encore fait l'objet
d'un accusé de réception, ou SND.NXT
, le numéro
de séquence du début des données pas encore envoyées ou encore
SND.WND
, la taille de la fenêtre
d'envoi. Ainsi, on ne peut pas envoyer d'octets dont le rang serait
supérieur à SND.UNA
+
SND.WND
, il faut attendre des accusés de
réception (qui vont incrémenter SND.UNA
) ou une
augmentation de la taille de la fenêtre. Pour la réception, on a
RCV.NXT
, le numéro de séquence des prochains
paquets si tout est normal, et RCV.WND
, la
taille de la fenêtre de réception.
TCP est un protocole à état et il a donc une machine à
états, décrite en section 3.3.2. Parmi les états,
LISTEN
, qui indique un TCP en train d'attendre
un éventuel pair, ESTAB
(ou ESTABLISHED
), où TCP envoie et reçoit des
données, et plusieurs états qui apparaissent lors de l'ouverture ou
de la fermeture d'une connexion.
Et les numéros de séquence ? Un point important de TCP est qu'on ne numérote pas les segments mais les octets. Chaque octet a son numéro et les accusés de réception référencent ces numéros. On n'accuse évidemment pas réception de chaque octet individuel. Les accusés de réception sont cumulatifs ; ils désignent un octet et, implicitement, tous les octets précédents. Quand un récepteur prévient qu'il a bien reçu l'octet N, cela veut dire que tous ceux avant N ont aussi été reçus.
Les numéros ne partent pas de 1 (ni de zéro), ils sont relatifs à un ISN (Initial Sequence Number, rappelez-vous qu'il y a un glossaire, en section 4 du RFC) qui est choisi aléatoirement (pour des raisons de sécurité, cf. RFC 6528) au démarrage de la session. Des logiciels comme Wireshark savent cela et peuvent calculer puis afficher des numéros de séquence qui partent de 1, pour faciliter la lecture.
Les numéros de séquence sont stockés sur 32 bits et il n'y a donc que quatre milliards (et quelques) valeurs possibles. C'était énorme quand TCP a été créé mais cela devient de plus en plus petit, relativement aux capacités des réseaux modernes. Comme TCP repart de zéro quand il atteint le numéro de séquence maximum, il y a un risque qu'un « vieux » paquet soit considéré comme récent. À 1 Gb/s, cela ne pouvait se produire qu'au bout de 34 secondes. Mais à 100 Gb/s, il suffit d'un tiers de seconde ! Les extensions du RFC 7323 deviennent alors indispensables.
Tout le monde sait bien que TCP est un protocole orienté connexion. Cela veut dire qu'il faut pouvoir créer, puis supprimer, les connexions. La section 3.5 de notre RFC décrit cet établissement de connexion, via la fameuse « triple poignée de mains ». En gros, un des TCP (TCP n'est pas client-serveur, et la triple poignée de mains marche également quand les deux machines démarrent la connexion presque en même temps) envoie un paquet ayant l'option SYN (pour synchronization), le second répond avec un paquet ayant les options ACK (accusé de réception du SYN) et SYN, le premier TCP accuse réception. Avec trois paquets, les deux machines sont désormais d'accord qu'elles sont connectées. En d'autres termes (ici, machine A commence) :
Pour mettre fin à la connexion, un des deux TCP envoie un paquet ayant l'option FIN (pour finish), auquel l'autre répond de même.
Conceptuellement, TCP gère un flux d'octets sans séparation. TCP
n'a pas la notion de message. Si une application envoie (en appelant
print
, write
ou autre
fonction du langage de programmation utilisé) les octets "AB" puis
"CD", TCP transmettra puis livrera à l'application distante les
quatre octets "ABCD" dans l'ordre, sans indiquer qu'il y avait eu
deux opérations d'écriture sur le réseau. Mais, pour envoyer les
données, TCP doit les segmenter en paquets IP distincts. La
fragmentation IP n'étant pas bonne pour les
performances, et étant peu fiable de toute façon, l'idéal est que
ces paquets IP aient une taille juste inférieure à la MTU du chemin. Cela
nécessite de découvrir cette MTU (section 3.7.2), ou bien d'adopter
la solution paresseuse de faire des paquets de 1 280 octets (la MTU
minimum, en IPv6). Comme la solution paresseuse ne serait pas
optimale en performances, les mises en œuvre de TCP essaient toutes
de découvrir la MTU du chemin. Cela peut se faire avec la méthode
PMTUD, décrite dans les RFC 1191 et RFC 8201, mais qui a l'inconvénient de nécessiter
qu'ICMP ne soit pas bloqué. Or, beaucoup de
middleboxes programmées
avec les pieds, ou configurées par des incompétents, bloquent ICMP,
empêchant le fonctionnement de PMTUD. Une alternative est PLPMTUD,
normalisée dans le RFC 4821, qui ne dépend
plus d'ICMP.
Une fois la connexion établie, on peut envoyer des données. TCP va les couper en segments, chaque segment voyageant dans un paquet IP.
Une mise en œuvre de TCP
n'envoie pas forcément immédiatement les octets qu'on lui a
confiés. Envoyer des petits paquets n'est pas efficace : s'il y a
peu de données, les en-têtes du paquet forment la majorité du trafic
et, de toute façon, le goulet d'étranglement dans le réseau n'est
pas forcément lié au nombre d'octets, il peut l'être au nombre de
paquets. Un des moyens de diminuer le nombre de paquets contenant
peu d'octets est l'algorithme de Nagle. Le
principe est simple : quand TCP reçoit un petit nombre d'octets à
envoyer, il ne les transmet pas tout de suite mais attend un peu
pour voir si l'application ne va pas lui confier des octets
supplémentaires. Décrit dans le RFC 896, et
recommandé dans le RFC 1122, cet algorithme
est aujourd'hui présent dans la plupart des mises en œuvres de
TCP. Notre RFC 9293 le recommande, mais en ajoutant que les
applications doivent pouvoir le débrayer. C'est notamment
indispensable pour les applications interactives, qui n'aiment pas
les délais, même courts. (Sur Unix, cela se
fait avec l'option TCP_NODELAY
passée à
setsockopt.)
Un des rôles les plus importants de TCP est de lutter contre la congestion. Il ne faut pas envoyer le plus d'octets possible, ou alors on risque d'écrouler le réseau (RFC 2914). Plusieurs mécanismes doivent être déployés : démarrer doucement (au début de la connexion, TCP ne connait pas le débit que peut supporter le réseau et doit donc être prudent), se calmer rapidement si on constate que des paquets sont perdus (cela peut être un signe de congestion en aval), etc. Ces mesures pour éviter la congestion sont absolument obligatoires (comme tous les biens communs, l'Internet est vulnérables aux parasites, un TCP égoïste qui ne déploierait pas ces mesures anti-congestion serait un mauvais citoyen du réseau). Elles sont décrites plus précisément dans les RFC 1122, RFC 2581, RFC 5681 et RFC 6298. Notez qu'ils énoncent des principes, mais pas forcément les algorithmes exacts à utiliser, ceux-ci pouvant évoluer (et ils l'ont fait depuis la sortie du RFC 793, voir les RFC 5033 et RFC 8961).
Un point qui surprend souvent les programmeur·ses qui écrivent
des applications utilisant TCP est que rien n'indique qu'une
connexion TCP est coupée, par exemple si un câble est
sectionné. C'est seulement lorsqu'on essaie d'écrire qu'on
l'apprendra (ce comportement est une conséquence de la nature sans
connexion d'IP ; pourquoi prévenir d'une coupure alors qu'IP va
peut-être trouver un autre chemin ?). Si on veut être mis au courant
tout de suite, on peut utiliser des keep-alives,
des segments vides qui seront envoyés de temps en temps juste pour
voir s'ils arrivent. Cette pratique est contestée (entre autre parce
qu'elle peut mener à abandonner une connexion qui n'est que
temporairement coupée, et aussi parce que, même si les
keep-alives passent, cela ne garantit pas que la
prochaine écriture de données passera). Notre RFC demande donc que
les keep-alives soient désactivés par défaut, et
que l'application puisse contrôler leur activation (sur Unix, c'est
l'option SO_KEEPALIVE
à utiliser avec
setsockopt).
Pendant les quarante ans écoulés depuis la sortie du RFC 793, bien des choses ont changé. Des options ont été ajoutées mais on a vu aussi certaines options qui semblaient être des bonnes idées être finalement abandonnées. C'est le cas du mécanisme de données urgentes, qui n'a jamais vraiment marché comme espéré. Une mise en œuvre actuelle de TCP doit toujours le gérer, pour pouvoir parler avec les anciennes, mais on ne peut pas compter dessus (RFC 6093).
On a parlé plusieurs fois de l'utilisation de TCP par les applications. Pour cela, il faut que TCP offre une API à ces applications. Notre RFC ne décrit pas d'API concrète (cela dépend de toute façon du langage de programmation, et sans doute du système d'exploitation). Il se contente d'une description fonctionnelle de l'interface : les services qu'elle doit offrir à l'application. On y trouve :
write
a écrit et seulement cela.Rappelez-vous que l'API « socket » n'est qu'un des moyens pour l'application de parler à TCP. Et, à propos de cette API, notez que le RFC utilise le terme de socket avec un sens très différent (cf. le glossaire en section 4).
Et l'autre interface, « sous » TCP, l'interface avec la couche réseau (section 3.9.2) ? TCP peut fonctionner avec des couches réseau variées mais, en pratique, c'est toujours IP, dans une de ses deux versions, v4 ou v6. Un des points délicats est le traitement des messages ICMP que TCP peut recevoir. Au minimum, ils doivent être assignés à une connexion existante. Mais cela ne veut pas dire qu'il faut systématiquement agir lors de la réception d'un de ces messages (RFC 5461). Par exemple, il faut ignorer les messages ICMP de répression de la source (RFC 6633). Et il ne faut pas fermer la connexion juste parce qu'on a reçu une erreur ICMP (MUST-56), car elles peuvent être temporaires.
TCP étant un protocole orienté connexion, la connexion va avoir un état, représenté par un automate fini. Ainsi, l'ouverture d'une connexion par le premier TCP va le faire passer par les états CLOSED, SYN-SENT puis enfin ESTABLISHED. Récupérée sur Wikimedia Commons, voici une représentation de cet automate :
La section 5 de notre RFC décrit les changements depuis le RFC 793. TCP reste le même et des TCP d'« avant » peuvent interopérer avec ceux qui suivent notre RFC récent. Les principaux changements sont :
Le RFC 793 avait été écrit par Jon Postel (comme tant de RFC de cette époque), et notre nouveau RFC 9293 lui rend hommage, en estimant que la longue durée de vie du RFC 793 montre sa qualité.
En application des changements de ce nouveau RFC sur TCP, un registre IANA, celui des bits de l'en-tête comme URG (dont on a vu qu'il était désormais démobilisé), a été mis à jour.
La section 7 de notre nouveau RFC 9293 n'avait pas d'équivalent dans l'ancien RFC 793. Elle concerne la sécurité de TCP. TCP, par lui-même, fournit peu de sécurité (il protège quand même un peu contre l'usurpation d'adresse IP et l'injection de trafic, surtout contre un attaquant qui n'est pas sur le chemin, cf. RFC 5961). Par exemple, il ne fournit pas de services cryptographiques, donc pas de confidentialité ou de vraie garantie d'intégrité. (Son concurrent QUIC, lui, intègre systématiquement la cryptographie.) Si on veut davantage de sécurité pour TCP, il faut mettre IPsec en dessous de TCP ou bien TLS au-dessus. TCP a aussi des mécanismes de sécurité qui lui sont propres, comme AO, normalisé dans le RFC 5925 (mais très peu déployé en pratique). Autre expérience qui n'a pas été un succès, tcpcrypt (RFC 8548).
Il reste des attaques contre lesquelles la cryptographie ne fournit pas vraiment de solution. C'est le cas du SYN flooding, ou d'autres attaques par déni de service.
Mëme si on chiffre les données applicatives avec TLS, TCP expose quand même au réseau tout son fonctionnement. Cette vue depuis le réseau (RFC 8546) soulève plusieurs problèmes (elle peut par exemple faciliter des actions contraires à la neutralité du réseau) et c'est pour cela que le plus récent QUIC masque une grande partie de son fonctionnement (ce qui n'a pas été sans entrainer des polémiques). Toujours côté vie privée, le comportement des mises en œuvre de TCP est suffisamment différent pour qu'il soit possible dans certains cas d'identifier le système d'exploitation d'une machine à distance (ce n'est pas forcément grave, mais il faut le savoir).
Pour les programmeurs et programmeuses, l'annexe A du RFC contient différentes observations utiles pour la mise en œuvre concrète de TCP. Par exemple, cette annexe explique que la validation des numéros de séquence des segments TCP entrants, validation qui est nécessaire pour empêcher un grand nombre d'attaques, peut rejeter des segments légitimes. Ce problème n'a pas de solution idéale.
Un autre point intéressant concerne l'algorithme de
Nagle. Cet algorithme représente un compromis entre la
réactivité de l'application (qui nécessite d'envoyer les données le
plus vite possibles) et l'optimisation de l'occupation du réseau
(qui justifie d'attendre un peu pour voir si on ne va pas recevoir
de nouvelles données à envoyer). Une modification de l'algorithme de
Nagle, décrite dans le document
draft-minshall-nagle
peut rendre le choix moins
douloureux.
Enfin, l'annexe B résume sous forme d'un tableau quelles sont les parties de la norme TCP qui doivent être mises en œuvre et quelles sont celles qu'on peut remettre à plus tard.
Si vous voulez sérieusement apprendre TCP, vous pouvez bien sûr lire le RFC en entier, mais il est sans doute plus sage de commencer par un bon livre comme le CNP3.
Un pcap d'une connexion TCP typique est
utilisé ici pour illustrer le fonctionnement de TCP (fichier tcp-typique.pcap
). Vu par tshark, cela donne :
8.445772 2a01:4f8:c010:7eb7::106 → 2605:4500:2:245b::42 TCP 94 33672 → 443 [SYN] Seq=0 Win=64800 Len=0 MSS=1440 SACK_PERM=1 TSval=285818465 TSecr=0 WS=128 8.445856 2605:4500:2:245b::42 → 2a01:4f8:c010:7eb7::106 TCP 94 443 → 33672 [SYN, ACK] Seq=0 Ack=1 Win=64260 Len=0 MSS=1440 SACK_PERM=1 TSval=3778761402 TSecr=285818465 WS=128 8.534636 2a01:4f8:c010:7eb7::106 → 2605:4500:2:245b::42 TCP 86 33672 → 443 [ACK] Seq=1 Ack=1 Win=64896 Len=0 TSval=285818554 TSecr=3778761402 8.534897 2a01:4f8:c010:7eb7::106 → 2605:4500:2:245b::42 SSLv3 236 Client Hello 8.534989 2605:4500:2:245b::42 → 2a01:4f8:c010:7eb7::106 TCP 86 443 → 33672 [ACK] Seq=1 Ack=151 Win=64128 Len=0 TSval=3778761491 TSecr=285818554 8.548101 2605:4500:2:245b::42 → 2a01:4f8:c010:7eb7::106 TLSv1.2 1514 Server Hello 8.548111 2605:4500:2:245b::42 → 2a01:4f8:c010:7eb7::106 TCP 1514 443 → 33672 [PSH, ACK] Seq=1429 Ack=151 Win=64128 Len=1428 TSval=3778761504 TSecr=285818554 [TCP segment of a reassembled PDU] 8.548265 2605:4500:2:245b::42 → 2a01:4f8:c010:7eb7::106 TLSv1.2 887 Certificate, Server Key Exchange, Server Hello Done 8.636922 2a01:4f8:c010:7eb7::106 → 2605:4500:2:245b::42 TCP 86 33672 → 443 [ACK] Seq=151 Ack=2857 Win=63616 Len=0 TSval=285818656 TSecr=3778761504 8.636922 2a01:4f8:c010:7eb7::106 → 2605:4500:2:245b::42 TCP 86 33672 → 443 [ACK] Seq=151 Ack=3658 Win=62848 Len=0 TSval=285818656 TSecr=3778761504 8.649899 2a01:4f8:c010:7eb7::106 → 2605:4500:2:245b::42 TCP 86 33672 → 443 [FIN, ACK] Seq=151 Ack=3658 Win=64128 Len=0 TSval=285818669 TSecr=3778761504 8.650160 2605:4500:2:245b::42 → 2a01:4f8:c010:7eb7::106 TCP 86 443 → 33672 [FIN, ACK] Seq=3658 Ack=152 Win=64128 Len=0 TSval=3778761606 TSecr=285818669 8.739117 2a01:4f8:c010:7eb7::106 → 2605:4500:2:245b::42 TCP 86 33672 → 443 [ACK] Seq=152 Ack=3659 Win=64128 Len=0 TSval=285818758 TSecr=3778761606
Ici, 2a01:4f8:c010:7eb7::106
(une sonde RIPE Atlas) veut faire du
HTTPS (donc, port 443)
avec le serveur 2605:4500:2:245b::42
. Tout
commence par la triple poignée de mains (les trois premiers paquets,
SYN, SYN+ACK, ACK). Une fois la connexion établie, tshark reconnait
que les machines font du TLS mais, pour TCP, ce ne sont que des données
applicatives (contrairement à QUIC, TCP sépare connexion et
chiffrement). On note qu'il n'y a pas eu
d'optimisation des accusés de réception. Par exemple, l'accusé de
réception du ClientHello TLS voyage dans un paquet séparé, alors
qu'il aurait pu être inclus dans la réponse applicative (le
ServerHello), probablement parce que celle-ci a mis un peu trop de
temps à être envoyée. À la fin de la connexion, chaque machine
demande à terminer (FIN). Une analyse automatique plus complète, avec
tshark -V
figure dans tcp-typique.txt
.
Ah, au fait, si vous avez pu lire cet article, c'est certainement grâce à TCP… (Ce blog ne fait pas encore de HTTP sur QUIC…).
Date de publication du RFC : Août 2022
Auteur(s) du RFC : M. Thomson (Mozilla)
Chemin des normes
Réalisé dans le cadre du groupe de travail IETF quic
Première rédaction de cet article le 30 août 2022
Dans un monde idéal, nos flux de données sur l'Internet passeraient sans problème et sans être maltraitées par les éléments intermédiaires du réseau. Mais dans le monde réel, de nombreuses middleboxes examinent le trafic et interfèrent avec lui. Les machines émettrices de trafic doivent donc se protéger et une des techniques les plus courantes est le chiffrement : si la middlebox ne peut pas comprendre ce qui est envoyé, elle ne pourra pas interférer. Le protocole de transport QUIC chiffre donc le plus possible. Mais une partie du trafic reste non chiffrée. Une des techniques pour empêcher que les middleboxes ne le lisent et ne prennent des décisions déplorables fondées sur ces informations transmises en clair est le graissage : on fait varier les données le plus possible. Ce RFC normalise une possibilité de graisser un bit de l'en-tête QUIC qui était fixe auparavant.
L'un des principes de conception de QUIC est de diminuer le plus possible la vue depuis le réseau (RFC 8546) ; les éléments intermédiaires, comme les routeurs ne doivent avoir accès qu'à ce qui leur est strictement nécessaire, le reste étant chiffré, pour leur éviter toute tentation. QUIC définit (dans le RFC 8999) des invariants, des informations qui seront toujours vraies même pour les futures versions de QUIC (le RFC 9000 définit QUIC version 1, pour l'instant la seule version). En dehors de ces invariants, tout peut… varier et les équipements intermédiaires du réseau ne doivent pas en tenir compte. Si tous suivaient ce principe, on préserverait la neutralité du réseau, et on éviterait l'ossification (cf. RFC 9170). Mais, en pratique, on sait que ce n'est pas le cas et que toute information visible depuis le réseau sera utilisée par les middleboxes, ce qui peut rendre difficile les évolutions futures (TLS a par exemple beaucoup souffert de ce problème, d'où le RFC 8701, le premier à avoir formalisé ce concept de graissage).
Or, QUIC a un bit qui n'est pas cité dans les invariants du RFC 8999 mais qui est fixe et peur servir à différencier QUIC d'autres protocoles. On l'appele d'ailleurs souvent le « bit QUIC » (même si ça n'est pas son nom officiel, qui est fixed bit, cf. RFC 9000, sections 17.2 et 17.3). Si les middleboxes s'en servent pour appliquer un traitement particulier à QUIC, les futures versions de QUIC ne pourront pas utiliser ce bit pour autre chose (rappelez-vous qu'il ne fait pas partie des invariants).
Notre RFC
ajoute donc à QUIC une option pour graisser ce bit, c'est-à-dire
pour le faire varier au hasard, décourageant ainsi les
middleboxes d'en tenir compte. Un nouveau
paramètre de transport QUIC (ces paramètres sont définis dans le
RFC 9000, section 7.4) est créé,
grease_quic_bit
(et ajouté au registre
IANA). Lorsqu'il est annoncé à l'ouverture de la connexion
QUIC, il indique que ce bit est ignoré et que le pair à tout intérêt
à le faire varier.
Les paquets initiaux de la connexion
(Initial
et Handshake
)
doivent garder le bit QUIC à 1 (puisqu'on n'a pas encore les
paramètres de la connexion), sauf si on reprend une ancienne
connexion (en envoyant un jeton, cf. RFC 9000,
section 19.7).
Tout le but de ce graissage est de permettre aux futures versions
de QUIC (v2, 3, etc) d'utiliser ce bit qui était originellement
fixe. Ces futures versions, ou tout simplement des extensions à QUIC
négociées au démarrage de la connexion, pourront donc utiliser ce
bit (lui donner une sémantique). Le RFC leur recommande d'annoncer
quand même le paramètre grease_quic_bit
, ce qui
permettra de graisser même si la future extension n'est pas gérée
par le partenaire.
Le RFC note enfin que ce graissage rend plus difficile d'identifier les flux de données QUIC au milieu de tout le trafic. C'est bien sûr le but, mais cela peut compliquer certains activités d'administration réseau. Un futur RFC explique plus en détail cette situation.
Apparemment, plusieurs mises en œuvre de QUIC gèrent déjà ce graissage.
Date de publication du RFC : Août 2022
Auteur(s) du RFC : P. Faltstrom (Netnod), F. Ljunggren (Kirei), D. van Gulik (Webweaving)
Pour information
Première rédaction de cet article le 10 août 2022
Ce RFC décrit un encodage en texte de données binaires, l'encodage Base45. Proche conceptuellement d'encodages comme Base64, il est, par exemple, beaucoup utilisé pour le code QR.
Ces codes QR ne permettent pas de stocker directement des données binaires quelconques car le contenu sera toujours interprété comme du texte. Il est donc nécessaire d'encoder et c'est le rôle de Base45. Il existe bien sûr d'innombrables autres mécanismes d'encodage en texte, comme Base16, Base32 et Base64, spécifiés dans le RFC 4648 mais Base45 est plus efficace pour les codes QR, qui réencodent ensuite.
Base45 utilise un sous-ensemble d'ASCII, de 45 caractères (d'où son nom). Deux octets du contenu binaire sont encodés en trois caractères de ce sous-ensemble. Par exemple, deux octets nuls donneront la chaine de caractères "000". Regardons tout de suite avec le module Python base45 :
% pip3 install base45 % python3 >>> import base45 >>> base45.b45encode(b'Bonjour, tout le monde') b'.H86/D34ENJES4434ESUETVDL44-3E6VC' >>>
Et décodons avec le module Elixir base45 :
% iex -S mix iex(1)> Base45.decode(".H86/D34ENJES4434ESUETVDL44-3E6VC") "Bonjour, tout le monde"
C'est parfait, tout le monde est d'accord. Ici, évidemment, la chaine originale n'avait pas vraiment besoin d'être encodée donc on va essayer avec du binaire, les trois octets 42, 1 et 6, encodage en Elixir, décodage en Python :
iex(1)> Base45.encode(<<42, 1, 6>>) "/D560" >>> base45.b45decode("/D560") b'*\x01\x06'
Encore une fois, tout va bien, Elixir a encodé les trois octets du binaire en quatre caractères, que Python a su décoder (l'astérisque est affiché car son code ASCII est 42).
Je vous laisse découvrir l'algorithme complet (il est assez simple) dans la section 3 du RFC. Si vous le programmez vous-même (ce qui n'est sans doute pas une bonne idée, il existe déjà de nombreuses mises en œuvre), attention aux cas limites comme un binaire d'un seul octet, ou comme une chaine à décoder qui compte des caractères invalides. Ici, un essai avec un caractère invalide, le signe égal :
% python3 >>> import base45 >>> base45.b45decode("AAAA=") Traceback (most recent call last): File "/home/stephane/.local/lib/python3.8/site-packages/base45/__init__.py", line 30, in b45decode buf = [BASE45_DICT[c] for c in s.rstrip("\n")] File "/home/stephane/.local/lib/python3.8/site-packages/base45/__init__.py", line 30, in <listcomp> buf = [BASE45_DICT[c] for c in s.rstrip("\n")] KeyError: '=' During handling of the above exception, another exception occurred: Traceback (most recent call last): File "<stdin>", line 1, in <module> File "/home/stephane/.local/lib/python3.8/site-packages/base45/__init__.py", line 54, in b45decode raise ValueError("Invalid base45 string") ValueError: Invalid base45 string
Pensez à tester votre code avec de nombreux cas variés. Le RFC cite
l'exemple des chaines "FGW" (qui se décode en une paire d'octets
valant chacun 255) et "GGW" qui, quoique proche de la précédente et
ne comportant que des caractères de l'encodage Base45 est néanmoins
invalide (testez-la avec votre décodeur). Le code de test dans le module Elixir
donne des idées de tests utiles (certains viennent de bogues
détectées pendant le développement). Regardez
test/base45_test.exs
.
Et, comme toujours, lorsque vous recevez des données venues de l'extérieur, soyez paranoïaques dans le décodage ! Un code QR peut être malveillant et chercher à activer une bogue dans le décodeur (section 6 du RFC).
Enfin, le RFC rappelle qu'une chaine encodée n'est pas forcément directement utilisable comme URL, elle peut comporter des caractères qui ne sont pas sûrs, il faut donc encore une étape d'encodage si on veut en faire un URL.
Compte tenu de l'importance des codes QR, on trouve des mises en œuvre de Base45 un peu partout. J'ai par exemple cité celle en Python. Si vous programmez en Elixir, il y a une bibliothèque sur Hex (écrite en Erlang) mais aussi ma bibliothèque (aux performances très basses).
Sinon, si vous cherchez un bon article d'explication sur Base45, je recommande celui-ci.
Date de publication du RFC : Août 2022
Auteur(s) du RFC : W. Hardaker (USC/ISI), V. Dukhovni
(Bloomberg)
Réalisé dans le cadre du groupe de travail IETF dnsop
Première rédaction de cet article le 26 août 2024
Si vous êtes responsable d'une zone DNS, et que vous la testez régulièrement avec des outils comme Zonemaster ou DNSviz (ce que font tous les responsables sérieux), vous avez peut-être eu des avertissements comme quoi vos « paramètres NSEC3 » n'étaient pas ceux conseillés. C'est parce que les recommandations en ce sens ont changé avec ce RFC. Lisez-le donc si vous voulez comprendre les recommandations actuelles.
D'abord, un peu de contexte. Ce RFC concerne les zones qui sont
signées avec DNSSEC
et qui utilisent les enregistrements NSEC3 du
RFC 5155. Aujourd'hui, par exemple, c'est le
cas de .fr
,
.com
mais aussi de
bortzmeyer.org
grâce à qui vous êtes arrivés
sur cet article. Mais ce n'est pas le cas de la racine des
noms de domaine, qui
utilise NSEC (RFC 4035). Pour comprendre la
dfifférence entre les deux, je vous renvoie à mon article sur le
RFC 5155.
Un exemple où Zonemaster proteste, sur
icann.org
:
Ce RFC 5155 donnait des conseils de sécurité cryptographiques qui, avec le recul et l'expérience se sont avérés sous-optimaux. Ce nouveau RFC 9276 les modifie donc et suggère fortement de ne plus utiliser de sel, ni d'itérations successives, dans le calcul des condensats pour NSEC3.
Lorsqu'une zone est signée avec utilisation de NSEC3, elle comprend un enregistrement de type NSEC3PARAM qui indique quatre choses :
.fr
ou
.com
utilisent NSEC3,
même s'il n'y a pas de problème avec l'énumération des noms
(.fr
distribue la
liste). (Notez que si l'option est à 0 dans le NSEC3PARAM,
cela ne signifie pas qu'il n'y a pas d'opt-out,
celui-ci est typiquement indiqué uniquement dans les
enregistrements NSEC3.)
Voici par exemple l'enregistrement de icann.org
en août 2024 :
% dig +short icann.org NSEC3PARAM 1 0 5 A4196F45E2097176
Utilisation de SHA-1 (le 1 est le code de SHA-1), pas
d'opt-out (mais prudence, son utilisation n'est
pas obligatoirement signalée dans les options, voir plus haut), cinq
itérations supplémentaires (donc six au total) et un sel apparemment
aléatoire, A4196F45E2097176
.
La première recommandation du RFC concerne le nombre
d'itérations. Comme le sel, le but est de rendre plus difficile
l'utilisation de tables calculées à l'avance par un attaquant. Sans
sel et avec une seule itération, un attaquant qui a à l'avance
calculé tout un dictionnaire et sait donc que le condensat de
foobar
est
8843d7f92416211de9ebb963ff4ce28125932878
pourra
donc facilement inverser le condensat dans un enregistrement
NSEC3. C'est pour cela que le RFC 5155
recommandait un nombre variable d'itérations, indiqué par
l'enregistrement NSEC3PARAM. Mais, en pratique, la protection contre
l'énumération n'est pas si solide que ça. Bien des noms peuvent être
devinés (www
étant le plus évident mais il y a
aussi les mots d'un dictionnaire de la langue), d'autant plus qu'on
choisit en général un nom de domaine pour être simple
et facilement mémorisable. Et que ces noms se retrouvent à
plein d'endroits comme les journaux Certificate
Transparency (RFC 9162). L'opinion
d'aujourd'hui est que le jeu (la protection contre l'énumération)
n'en vaut pas la chandelle (le coût de signature
et de validation). Notez aussi une externalité négative : les résolveurs aussi devront effectuer ces
itérations et sont donc concernés. Bon, en prime, les techniques
modernes rendent la protection peu efficace de toute façon
(cf. « GPU-Based NSEC3 Hash
Breaking »). La recommandation du RFC
est donc de ne pas avoir d'itérations supplémentaires, donc de
mettre ce nombre à zéro.
Et la deuxième recommandation concerne le sel. Il y a dans NSEC3 un sel implicite, c'est le nom de domaine (RFC 5155, section 5). D'ailleurs, mon exemple de condensat de foobar était faux, puisque j'avais omis cette étape. Si on l'inclut, le sel supplémentaire indiqué dans l'enregistrement NSEC3PARAM perd de son intérêt. En outre, en pratique, on change rarement le sel (cela nécessite de modifier toute la chaine NSEC3) ce qui diminue la protection qu'il offre. La recommandation actuelle est donc de ne pas utiliser de sel (ce qui se note avec un tiret, pas avec une chaine vide).
Si on suit les recommandations du RFC, le NSEC3PARAM aura cette allure :
% dig +short fr NSEC3PARAM 1 0 0 -
Et un des NSEC3 sera du genre :
% dig nexistesurementpas.fr qu7kmgn3e….fr. 594 IN NSEC3 1 1 0 - ( QU7MMK1… NS DS RRSIG )
Notez aussi que le RFC recommande (section 3), avant de réfléchir
aux paramètres de NSEC3, de réfléchir à NSEC3 lui-même. Sur une
grosse zone de délégation, changeant souvent, comme
.fr
, NSEC3 est tout à fait justifié en raison
des avantages de l'opt-out. Mais sur la zone DNS
typique d'une petite organisation, qui ne compte souvent que des
noms prévisibles (l'apex, www
et
mail
), NSEC3 peut avantageusement être remplacé
par NSEC, qui consomme moins de ressources. (NSEC3, ou d'ailleurs
les couvertures minimales du RFC 4470, peut, dans le
pire des cas, faciliter certaines attaques par déni de
service.)
Les recommandations précédentes s'appliquaient aux signeurs de zone (côté serveurs faisant autorité, donc). Mais la section 3 a aussi des recommandations pour les résolveurs : compte-tenu du coût que représente pour eux les itérations NSEC3, ils ont le droit d'imposer un maximum, et de le diminuer petit à petit. Ces résolveurs peuvent refuser de répondre (réponse SERVFAIL) ou bien traiter la zone come n'étant pas signée (cf. section 6). Un nouveau code d'erreur étendu (RFC 8914), le numéro 27, Unsupported NSEC3 iterations value, a été réservé pour qu'ils puissent informer leurs clients.
Revenons aux serveurs faisant autorité : le RFC précise aussi qu'un hébergeur DNS devrait informer clairement ses utilisateurs des paramètres NSEC3 qu'il accepte. Il ne faudrait pas qu'on choisisse N itérations et qu'on s'aperçoive au déploiement qu'un des secondaires n'accepte pas d'en faire autant.
Aujourd'hui, la grande majorité des zones utilisant NSEC3 est
passée aux recommandations de ce RFC (comme par
exemple .fr en 2022). Notons que .org
a
un sel mais pas d'itérations supplémentaires.
% dig +short org NSEC3PARAM 1 0 0 332539EE7F95C32A
Si vous utilisez OpenDNSSEC pour automatiser les opérations DNSSEC sur vos zones, voici la configuration conforme au RFC que j'utilise :
<Denial> <NSEC3> <!-- <OptOut/> --> <Resalt>P100D</Resalt> <Hash> <Algorithm>1</Algorithm> <Iterations>0</Iterations> <Salt length="0"/> </Hash> </NSEC3> </Denial>
Date de publication du RFC : Juillet 2022
Auteur(s) du RFC : S. Dashevskyi, D. dos Santos, J. Wetzels, A. Amri (Forescout Technologies)
Pour information
Première rédaction de cet article le 14 septembre 2022
Comme chacun·e sait, l'Internet est une jungle (les politiciens ajouteraient « une jungle Far-West de non-droit qu'il faut civiliser »). Par exemple, les logiciels qui parlent avec les vôtres ne sont pas toujours correctement écrits, voire ils sont franchement malveillants. Le code de votre côté doit donc être robuste, voire paranoïaque, et penser à tout. Ce RFC décrit quelques problèmes qui ont été observés dans des logiciels mettant en œuvre le DNS et explique comment ne pas refaire la même erreur.
Le RFC ne parle pas de failles DNS mais de failles dans les programmes DNS, ce qui est très différent (mais la différence est souvent oubliée dans les médias). Le protocole lui-même n'était pas en cause dans ces cas, ce sont juste les logiciels qui avaient des bogues. Les cas sont nombreux, par exemple SIGRed (CVE-2020-1350) ou DNSpooq (CVE-2020-25681 à CVE-2020-25687). Ces problèmes frappent notamment souvent dnsmasq (personnellement, je n'ai jamais compris pourquoi ce logiciel était si utilisé, mais c'est une autre histoire).
Plusieurs vulnérabilités ont concerné l'analyse des
enregistrements DNS (RR = Resource Record). Les
risques lors de cette analyse devraient être bien connus, la
première faille documentée l'ayant été en 2000 (CVE-2000-0333) !
Tout logiciel qui analyse des enregistrements DNS (client, serveurs,
mais aussi pare-feux, IDS, etc) peut
tomber dans ces pièges, d'où l'importance de les documenter. C'était
déjà fait dans
l'Internet-Draft
draft-ietf-dnsind-local-compression
et dans le RFC 5625 mais c'était perdu au
milieu d'autres choses donc notre RFC choisit d'enfoncer le
clou.
Il commence par le grand classique des bogues de logiciels DNS : le traitement des pointeurs de compression. Pour gagner quelques octets, à l'époque où ça comptait, le DNS prévoit (RFC 1035, section 4.1.4) qu'on peut, dans un enregistrement DNS, remplacer tout ou partie d'un nom de domaine par un pointeur vers un autre endroit du paquet. Ainsi, si on a tous ses serveurs qui se terminent par les mêmes composants (comme c'est le cas, par exemple, de la racine), on peut ne mettre ces composants qu'une fois, et pointer vers eux partout ailleurs. Évidemment, si vous êtes programmeuse ou programmeur, vous avez déjà vu le piège : les pointeurs, c'est dangereux. Ils peuvent pointer en dehors du paquet, ou pointer vers eux-même, par exemple. Si on suit aveuglément un pointeur, un déni de service est possible, si on tape en dehors de la mémoire allouée ou bien si on se lance dans une boucle sans fin.
Rentrons dans les détails. Le RFC 1035 nous dit que l'octet qui indique la longueur d'un composant doit valoir moins de 64 (la taille maximale d'un composant), donc avoir les deux bits de plus fort poids à zéro. S'ils sont tous les deux à un, cela indique qu'on a un pointeur. Cet octet et le suivant (privés des deux bits de plus fort poids) sont alors un pointeur, le nombre d'octets depuis le début du message. On voit qu'on peut atteindre 16 383 octets (2 ** 14 - 1), largement assez pour sortir de la mémoire allouée pour le paquet, avec les conséquences qu'on imagine. Cela s'est produit en vrai (CVE-2020-25767, CVE-2020-24339 et CVE-2020-24335).
Autre exemple amusant cité par le RFC, le cas d'un pointeur pointant sur lui-même. Soit un message DNS minimal. Le premier composant est à 12 octets du début du message. Si on y met les octets 0xC0 et 0x0C, on aura un pointeur (0xC0 = 11000000, les deux bits de plus fort poids à un) qui vaut 12 (0xC0 0x0C moins les deux bits les plus significatifs = 0000000000001100 = 12). Le pointeur pointera alors sur lui-même, entrainant le logiciel DNS imprudent dans une boucle sans fin. Ça aussi, ça s'est déjà produit (CVE-2017-9345).
Dernier exemple amusant avec des pointeurs, le pointeur qui va
mener à un nombre infini de composants. L'attaquant (ou le
programmeur maladroit) met dans le message un composant suivi d'un
pointeur qui revient au début de ce composant. Si le composant était
test
, un analyseur DNS imprudent va créer le
nom de domaine test.test.test.test…
avant de
tomber à court de mémoire, ou bien, dans un langage de programmation
comme C, d'écrire dans une
autre zone de la mémoire, ce qui peut mener à un
RCE. Notez que les pointeurs ne devraient pas
pointer vers un pointeur : ça n'a aucun intérêt pratique et c'est
dangereux, mais le RFC 1035 ne l'interdit pas
explicitement. Une alternative est de tester le nombre de fois qu'on
a suivi un pointeur ou, encore mieux, de vérifier que le pointeur ne
pointe qu'en avant de lui-même.
Notez que le problème de la répétition infinie d'un composant pourrait également être évité en s'assurant que le nom de domaine reste en dessous de sa taille maximale, 255 octets (section 3 du RFC).
Mais il n'y a pas que les pointeurs. Un nom de domaine doit être
terminé par un octet nul, indiquant la racine. Ainsi, le nom
afnic.fr
est encodé 0x05 0x61 0x66 0x6E 0x69
0x63 ("afnic") 0x02 0x66 0x72 ("fr") 0x00 (la racine). Que se
passe-t-il si l'octet nul final manque ? Certains programmes en
C ont été assez imprudents
pour tenter de traiter le nom de domaine avec des routines comme
strlen
, qui comptent sur un octet nul pour
terminer les données (section 4 de notre RFC). Le développeur qui ne
veut pas refaire des failles comme CVE-2020-25107, CVE-2020-17440,
CVE-2020-24383 ou CVE-2020-27736 devrait utiliser
strnlen
ou une autre méthode sûre.
On n'a pas terminé. Lorsqu'on traite une réponse DNS, les
enregistrements incluent des données. Leur longueur est donné par un
champ RDLENGTH de deux octets, qui précède les données (section
5). Si un analyseur DNS ne fait pas attention, la longueur indiquée
peut être supérieure à la taille du paquet. Un programme qui,
bêtement, lirait RDLENGTH puis ferait un read()
de la longueur indiquée aurait de fortes chances de taper en dehors
de la mémoire accessible (CVE-2020-25108, CVE-2020-24336 et
CVE-2020-27009).
Ah, et le RFC n'en parle pas, mais l'analyse des options EDNS (RFC 6891) offre également ce genre de pièges. Une option est encodée en TLV et une longueur invalide peut mener à lire les valeurs en dehors du paquet.
Un problème du même genre est décrit dans la section 6 du RFC. Un message DNS comprend plusieurs sections (Question, Réponse, Autorité, Additionnelle) et le nombre d'enregistrements par section est indiqué au début du message DNS. Ces nombres (count) peuvent également être faux et ne doivent pas être utilisés aveuglément, sinon, on aura des malheurs du genre CVE-2020-25109, CVE-2020-24340, CVE-2020-24334 ou CVE-2020-27737.
C'est en raison de tous ces risques qu'il faut tester que ses
programmes résistent bien à des messages DNS mal formés. Par
exemple, le serveur faisant autorité
Drink, écrit en Elixir, a dans ses jeux de tests de tels
messages. Regardez par exemple drink_parsing_test.exs
. Un
exemple d'un tel test, pour le problème de boucle sans fin sur des
pointeurs, décrit en section 2 du RFC, est :
test "pointerloop1" do # RFC 9267, section 2 result = Drink.Parsing.parse_request(<<@id::unsigned-integer-size(16), @request::unsigned-integer-size(16), 1::unsigned-integer-size(16), # QDcount 0::unsigned-integer-size(16), # ANcount 0::unsigned-integer-size(16), # NScount 0::unsigned-integer-size(16), # ARcount 0xc0::unsigned-integer-size(8), # First label of the question # section starts with a # compression pointer to itself. 0x0c::unsigned-integer-size(8), @mx::unsigned-integer-size(16), @in_class::unsigned-integer-size(16) >>, false) assert result == {:error, :badDNS} end
On fabrique un paquet DNS avec le pointeur invalide, et on
l'analyse. Le sous-programme parse_request
ne
doit pas tourner sans fin, et doit renvoyer une erreur.
Un test du même genre en Python, où on envoie un message DNS avec un pointeur pointant sur lui-même (et on espère que ça n'aura pas tué le serveur qui le reçoit) :
id = 12345 misc = 0 # opcode 0, all flags zero data = struct.pack(">HHHHHHBB", id, misc, 1, 0, 0, 0, 0xc0, 0x0c) s.sendto(data, sockaddr)
Date de publication du RFC : Juin 2022
Auteur(s) du RFC : R. Stewart (Netflix), M. Tüxen
(Münster Univ. of Appl. Sciences), K. Nielsen (Kamstrup A/S)
Chemin des normes
Réalisé dans le cadre du groupe de travail IETF twvwg
Première rédaction de cet article le 6 juin 2022
Une des particularités du protocole IP est que vous avez plusieurs protocoles de transport disponibles. TCP et UDP sont les plus connus mais SCTP, normalisé dans notre RFC, est également intéressant. C'est un protocole déjà ancien (il date de 2000) et ce RFC remplace son prédécesseur, le RFC 4960. Beaucoup de changements de détails, mais rien de crucial.
SCTP ressemble plutôt à TCP, notamment par le fait qu'il fournit un transport fiable. Mais il a plusieurs différences :
Cette dernière possibilité le rapproche des protocoles qui séparent l'identificateur et le localisateur et lui permet de gérer proprement le multihoming. Cela se fait en indiquant, au début du contact, toutes les adresses IP (autrefois, il y avait même les noms de domaines, cf. la section 3.3.2.1.4) de la machine.
SCTP tient également compte de l'expérience acquise avec TCP. par exemple, l'établissement d'une connexion (que SCTP nomme association) se fait avec un échange de quatre paquets (et non pas trois comme avec TCP), pour offrir une meilleure protection contre les dénis de service. Les SYN cookies, un ajout plus ou moins bancal en TCP, sont ici partie intégrante du protocole.
SCTP est surtout issu des demandes du monde de la téléphonie sur IP, qui avait besoin d'un tel protocole pour la signalisation mais il peut être aussi utilisé dans d'autres domaines.
Un excellent article du Linux Journal explique bien SCTP.
SCTP est depuis longtemps mis en œuvre dans Linux et dans FreeBSD. De même, des programmes de débogage comme Wireshark sont capables de décoder et d'afficher le SCTP. Voici par exemple un pcap entre deux machines dont l'une a envoyé (trame 3) la chaîne de caractères « toto ». (Vous avez également la version texte de ce dialogue.) Des exemples de programmes de tests SCTP se trouvent dans mon article sur le RFC 3286.
Comme tout « nouveau » protocole de transport, SCTP est handicapé par des coupe-feux mal programmés et/ou mal configurés. L'Internet s'ossifiant de plus en plus, il devient très difficile de déployer un nouveau protocole de transport. D'où le RFC 6951, pour faire tourner SCTP sur UDP (comme ce que fait QUIC).
Les changements qu'introduit ce nouveau RFC ne modifient pas en profondeur le protocole mais corrigent les nombreux problèmes survenus pendant ses premières années d'utilisation. Le RFC 8540 donne la liste complète des problèmes corrigés.
Date de publication du RFC : Juin 2022
Auteur(s) du RFC : R. Bush (Arrcus & IIJ Research), R. Housley (Vigil Security)
Chemin des normes
Réalisé dans le cadre du groupe de travail IETF sidrops
Première rédaction de cet article le 15 juin 2022
Un très court RFC pour un simple rappel, qui ne devrait même pas être nécessaire : les identités utilisées dans la RPKI, la base des identités qui sert aux techniques de sécurisation de BGP, n'ont pas de lien avec les identités attribuées par l'État et ne doivent pas être utilisées pour, par exemple, signer des documents « officiels » ou des contrats commerciaux.
Le RFC 6480 décrit cette RPKI : il s'agit d'un ensemble de certificats et de signatures qui servent de base pour des techniques de sécurité du routage comme les ROA du RFC 6482 ou comme le BGPsec du RFC 8205. Les objets de la RPKI servent à établir l'autorité sur des ressources Internet (INR, Internet Number Resources, comme les adresses IP et les numéros d'AS). Le RFC 6480, section 2.1, dit clairement que cela ne va pas au-delà et que la RPKI ne prétend pas être la source d'identité de l'Internet, ni même une source « officielle ».
Prenons un exemple concret, un certificat
choisi au hasard dans les données du RIPE-NCC (qu'on peut récupérer avec
rsync en
rsync://rpki.ripe.net/repository
). Il est au
format DER donc ouvrons-le :
% openssl x509 -inform DER -text -in ./DEFAULT/ztLYANxsM7afpHKR4vFbM16jYA8.cer Certificate: Data: ... Serial Number: 724816122963 (0xa8c2685453) Validity Not Before: Jan 1 14:04:04 2022 GMT Not After : Jul 1 00:00:00 2023 GMT Subject: CN = ced2d800dc6c33b69fa47291e2f15b335ea3600f ... X509v3 extensions: ... sbgp-ipAddrBlock: critical IPv4: 51.33.0.0/16 ... sbgp-autonomousSysNum: critical Autonomous System Numbers: 206918 ...
Je n'ai pas tout montré, mais seulement les choses importantes :
51.33.0.0/16
, utilisant les extensions du
RFC 3779.Ce certificat dit simplement que l'entité qui a la clé privée (RFC 5280) correspondante (une administration britannique, dans ce cas) a autorité sur des ressources comme l'AS 206918. Rien d'autre.
Mais, apparemment, certaines personnes n'avaient pas bien lu le RFC 6480 et croyaient que cet attirail PKIesque leur permettait également de signer des documents sans lien avec les buts de la RPKI, voire n'ayant rien à voir avec le routage. Et d'autant plus que des gens croient que le I dans RPKI veut dire Identity (il veut en fait dire Infrastructure). Il a ainsi été suggéré que les clés de la RPKI pouvaient être utilisées pour signer des LOA demandant l'installation d'un serveur dans une baie.
Pourquoi est-ce que cela serait une mauvaise idée ? Il y a
plusieurs raisons mais la principale est qu'utiliser la RPKI pour
des usages en dehors du monde restreint du routage Internet est que
cela exposerait les AC de cette RPKI à des
risques juridiques qu'elles n'ont aucune envie d'assumer. Et cela
compliquerait les choses, obligeant sans doute ces AC à déployer des
processus bureaucratiques bien plus rigides. Si on veut connaitre
l'identité officielle (que le RFC nomme à tort « identité dans le
monde réel ») d'un titulaire de ressources Internet, on a les bases
des RIR,
qu'on interroge via RDAP ou autres
protocoles. (C'est ainsi que j'ai trouvé le titulaire de
51.33.0.0/16
.) Bien sûr, il y a les
enregistrements Ghostbusters du RFC 6493 mais ils sont uniquement là pour aider à trouver un
contact opérationnel, pas pour indiquer l'identité étatique du
titulaire. Même les numéros d'AS ne sont pas l'« identité » d'un acteur de
l'Internet (certains en ont plusieurs).
Notons qu'il y a d'autres problèmes avec l'idée de se servir de la RPKI pour signer des documents à valeur légale. Par exemple, dans beaucoup de grandes organisations, ce ne sont pas les mêmes personnes qui gèrent le routage et qui gèrent les commandes aux fournisseurs. Et, au RIPE-NCC, les clés privées sont souvent hébergées par le RIPE-NCC, pas par les titulaires, et le RIPE-NCC n'a évidemment pas le droit de s'en servir pour autre chose que le routage.
Bref, n'utilisez pas la RPKI pour autre chose que ce pour quoi elle a été conçue.
Date de publication du RFC : Mai 2022
Auteur(s) du RFC : C. Huitema (Private Octopus), S. Dickinson (Sinodun IT), A. Mankin (Salesforce)
Chemin des normes
Réalisé dans le cadre du groupe de travail IETF dprive
Première rédaction de cet article le 22 mai 2022
Ce nouveau RFC complète la série des mécanismes de protection cryptographique du DNS. Après DoT (RFC 7858) et DoH (RFC 8484), voici DoQ, DNS sur QUIC. On notera que bien que QUIC ait été conçu essentiellement pour les besoins de HTTP, c'est le DNS qui a été la première application normalisée de QUIC.
Fonctionnellement, DoQ est très proche de ses prédécesseurs, et offre les mêmes services de sécurité, confidentialité, grâce au chiffrement, et authentification du serveur, via le certificat de celui-ci. L'utilisation du transport QUIC permet quelques améliorations, notamment dans le « vrai » parallélisme. Contrairement à DoT (RFC 7858) et DoH (RFC 8484), DoQ peut être utilisé pour parler à un serveur faisant autorité aussi bien qu'à un résolveur.
Notez que le terme de DoQ (DNS over QUIC) n'apparait pas dans le RFC de terminologie DNS, le RFC 8499. Il sera normalement dans son successeur.
Logiquement, DoQ devrait faire face aux mêmes problèmes politiques que ceux vécus par DoH (voir aussi mon article dans Terminal) mais cela n'a pas été encore le cas. Il faut dire qu'il y a peu de déploiements (ne comptez pas installer DoQ comme serveur ou client tout de suite, le code est loin d'être disponible partout).
Le cahier des charges de DoQ est (sections 1 et 3 du RFC) :
En revanche, DoQ n'esssaie pas de :
DoQ utilise QUIC. QUIC est un protocole de transport généraliste, un concurrent de TCP. Plusieurs protocoles applicatifs peuvent utiliser QUIC et, pour chacun de ces protocoles, cela implique de définir comment sont utilisés les ruisseaux (streams) de QUIC. Pour HTTP/3, cela a été fait dans le RFC 9113, publié plus tard. Ironiquement, le DNS est donc le premier protocole à être normalisé pour une utilisation sur QUIC. Notez qu'une autre façon de faire du DNS sur QUIC est de passer par DoH et HTTP/3 (c'est parfois appelé DoH3, terme trompeur) mais cette façon n'est pas couverte dans notre RFC, qui se concentre sur DoQ, du DNS directement sur QUIC, sans HTTP.
La spécification de DoQ se trouve en section 4. Elle est plutôt
simple, DoQ étant une application assez directe de QUIC. Comme DoQ
utilise QUIC, il y a forcément un ALPN, en l'occurrence via
l'identificateur doq
.
Le port, quant à lui, est par défaut 853,
également nommé domain-s
. C'est donc le même
que DoT, et aussi le même que le DNS sur DTLS (RFC 8094, protocole expérimental, jamais vraiment mis en œuvre
et encore moins déployé, et qui est de toute façon distinguable du
trafic QUIC, cf. section 17.2 du RFC 9000). Bien sûr, un client et un serveur DNS majeurs et
vaccinés peuvent toujours se mettre d'accord sur un autre port (mais
pas 53, réservé au DNS classique). Cette utilisation d'un port
spécifique à DoQ est un des points qui le rend vulnérable au
filtrage, comme vu plus haut. Utiliser 443 peut aider. (Le point a
été chaudement discuté à l'IETF, entre
défenseurs de la vie privée et gestionnaires de réseau qui voulaient
pouvoir filtrer facilement les protocoles de leur choix. Si on
utilise le port 443, il faut se rappeler que l'ALPN est pour
l'instant en clair, la lutte de l'épée et de la cuirasse va donc continuer.)
Plus important, la question de la correspondance entre les messages DNS et les ruisseaux QUIC. La règle est simple : pour chaque requête DNS, un ruisseau est créé. La première requête sur une connexion donnée sera donc sur le ruisseau 0 (RFC 9000, section 2.1), la deuxième sur le ruisseau 4, etc. Si les connexions QUIC sont potentiellement de longue durée, en revanche, les ruisseaux ne servent qu'une fois. Le démultiplexage des réponses est assuré par QUIC, pas par le DNS (et l'identificateur de requête DNS est donc obligatoirement à zéro, puisqu'inutile). Le parallélisme est également fourni par QUIC, client et serveur n'ont rien de particulier à faire. Il est donc recommandé de ne pas attendre une réponse pour envoyer les autres questions. Les messages DNS sont précédés de deux octets indiquant leur longueur, comme avec TCP.
Comme avec TCP, client et serveur ont le droit de garder un œil sur les ruisseaux et les connexions inactifs, et de les fermer d'autorité. La section 5.5 donne des détails sur cette gestion de connexion, mais le RFC 7766 est également une bonne lecture (voir aussi le RFC 9103 pour le cas des transferts de zones).
DoQ introduit quelques codes
d'erreur à lui (RFC 9000, section
20.2), comme DOQ_INTERNAL_ERROR
(quelque chose
s'est mal passé), DOQ_PROTOCOL_ERROR
(quelqu'un
n'a pas lu le RFC, par exemple l'identificateur de requête était
différent de zéro) ou DOQ_EXCESSIVE_LOAD
(trop
de travail tue le travail).
Un des gros avantages de QUIC est le 0-RTT, où des données sont envoyées dès le premier paquet, ce qui réduit nettement la latence. Cela implique que le client ait déjà contacté le serveur avant, mémorisant les informations qui seront données au serveur pour qu'il retrouve la session à reprendre. C'est cool, mais cela pose d'évidents problèmes de vie privée (ces informations mémorisées sont une sorte de cookie, et permettent le suivi à la trace d'un client). D'ailleurs, en parlant de vie privée, le RFC signale aussi (section 5.5.4) que la possibilité de migrer une connexion QUIC d'une adresse IP à l'autre a également des conséquences pour la traçabilité.
Autre piège avec le 0-RTT, il ne protège pas contre le rejeu et
il ne faut donc l'utiliser que pour des requêtes DNS
idempotentes comme QUERY
(le cas le plus courant) ou NOTIFY
(qui change
l'état du serveur, mais est idempotent). Bon, de toute façon, un
serveur peut toujours être configuré pour ne pas accepter de 0-RTT,
et répondre alors REFUSED
(avec un code
d'erreur étendu - RFC 8914 - Too
Early).
La section 5 du RFC liste les exigences auxquelles doivent se soumettre les mises en œuvre de DoQ. Le client doit authentifier le serveur (cf. RFC 7858 et RFC 8310, mais aussi RFC 8932, sur les bonnes pratiques). Comme tous les serveurs ne géreront pas DoQ, en tout cas dans un avenir prévisible, les clients doivent être préparés à ce que ça échoue et à réessayer, par exemple en DoT.
QUIC ne dissimule pas forcément la taille des messages échangés, et celle-ci peut être révélatrice. Malgré le chiffrement, la taille d'une requête ou surtout d'une réponse peut donner une idée sur le nom demandé. Le RFC impose donc l'utilisation du remplissage, soit par la méthode QUIC générique de la section 19.1 du RFC 9000, soit par la méthode spécifique au DNS du RFC 7830. La première méthode est en théorie meilleure car elle dissimule d'autres métadonnées, et qu'elle tient compte de davantage d'éléments, mais les bibliothèques QUIC n'exposent pas forcément dans leur API de moyen de contrôler ce remplissage. Le RFC recommande donc aux clients et serveurs DoQ de se préparer à faire le remplissage eux-mêmes (en relisant bien le RFC 8467 avant).
Un petit retour sur la protection de la vie privée en section 7. Cette section rappele l'importance de lire et suivre le RFC 8932 si vous gérez un service de résolution DNS sécurisé (DoQ ou pas DoQ). Et elle insiste sur le fait que des fonctions de QUIC très pratiques pour diminuer la latence à l'établissement de la connexion, notamment le 0-RTT, ont des conséquences pour la vie privée, en permettant de suivre un même utilisateur (plus exactement une même machine). Et cela marche même si la machine a changé d'adresse IP entretemps. On peut aussi dire que le problème de QUIC est de permettre des sessions de très longue durée (que ce soit une vraie session unique, ou bien une « session virtuelle », formée en reprenant une session existante avec le 0-RTT) et que de telles sessions longues, excellentes pour les performances, le sont forcément moins pour l'intimité.
Les mises en œuvre de DoQ, maintenant. La société AdGuard a produit du code, dont un serveur et un client en Go, dnslookup. (Regardez l'exposé du directeur technique de cette société à l'OARC.) Voici un exemple de compilation, puis d'utilisation de dnslookup :
% git clone https://github.com/ameshkov/dnslookup.git % cd dnslookup % make % ./dnslookup www.bortzmeyer.org quic://dns.adguard.com
On utilisait le serveur DoQ public d'AdGuard. Officiellement, NextDNS en a également un mais je n'arrive pas à l'utiliser :
% ./dnslookup www.bortzmeyer.org quic://3e4935.dns.nextdns.io ... 2022/05/22 08:16:33 Cannot make the DNS request: opening quic session to quic://3e4935.dns.nextdns.io:853: timeout: no recent network activity
Notez que si vous voulez utiliser dnslookup avec un serveur dont le
certificat n'est pas valide, il faut mettre la variable
d'environnement VERIFY
à 0 (cela ne semble pas documenté).
Il existe une autre mise en œuvre de DoQ, quicdoq, écrite en C. Elle dépend de la bibliothèque QUIC picoquic qui, à son tour, dépend de picotls, dont la compilation n'est pas toujours évidente. D'une manière générale, les bibliothèques QUIC sont souvent récentes, parfois expérimentales, et ne marchent pas toujours du premier coup. Mais si vous arrivez à compiler les dépendances de quicdoq, vous pouvez lancer le serveur ainsi :
% ./quicdoq_app -p 8053 -d 9.9.9.9
Puis le client :
% ./quicdoq_app ::1 8053 foobar:SOA bv:NS www.bortzmeyer.org:AAAA
Le logiciel de test de performances Flamethrower, écrit en C++ a une branche DoQ en cours de développement.
Le logiciel en Python aioquic ne semble pouvoir interagir qu'avec lui-même. Vu les messages d'erreur (quelque chose à propos de not enough bytes), je soupçonne qu'il utilise l'encodage des débuts de DoQ, quand il n'y avait pas encore le champ de deux octets au début des messages. Même avec lui-même, il a des exigences pénibles en matière de certificat (pas de certificats auto-signés, obligation que le nom du serveur soit dans le SAN - Subject Alternative Name, pas seulement dans le sujet).
Pour PowerDNS, il n'y a pour l'instant qu'un ticket. Et pour Unbound, c'est en cours, ainsi que pour Knot.
Dans les bibliothèques DNS, un travail est en cours pour go-dns.
Pour finir, vous pouvez regarder la présentation de DoQ par une des auteures du RFC au RIPE 84. Et un des premiers articles de recherche sur DoQ est « One to Rule them All? A First Look at DNS over QUIC ».
Date de publication du RFC : Mai 2022
Auteur(s) du RFC : M. Miller, M. Borins (GitHub), M. Bynens (Google), B. Farias
Pour information
Réalisé dans le cadre du groupe de travail IETF dispatch
Première rédaction de cet article le 11 mai 2022
Ce RFC
documente les types de médias pour le langage
de programmation JavaScript (dont le nom
officiel est ECMAScript, qu'on retrouve dans le titre de ce RFC). Il
remplace le RFC 4329 et le principal
changement est que le type recommandé est désormais
text/javascript
et non plus
application/javascript
.
Si vous voulez vous renseigner en détail sur
JavaScript, notre RFC recommande de lire la norme
ECMA, en
. Cette
norme est développée en dehors de l'IETF et le choix des
types de médias (aussi appelés types
MIME, cf. RFC 2046)
n'est donc pas forcément en accord avec les règles de l'IETF (RFC 6838). C'est pour cela que l'IESG a ajouté
une note d'avertissement au RFC. Mais, bon, ce n'est pas trop grave
en pratique. Le type recommandé est donc désormais
https://262.ecma-international.org/12.0/
text/javascript
. D'autres types existent,
application/ecmascript
,
application/javascript
, etc, mais ils sont
maintenant considérés comme dépassés.
Il existe plusieurs versions de la norme JavaScript et d'autres
apparaitront peut-être dans le futur. Mais le type officiel
n'indique pas de version (il a existé des propositions comme
text/javascript1.4
) et compte que toutes ces
versions sont suffisamment compatibles pour qu'on ne gère pas la
complexité d'avoir plusieurs types. Normalement,
ECMA s'engage à ne pas bouleverser le
langage.
Le choix de text/
est contestable car la
définition originale de text/
dans le RFC 2045 prévoyait plutôt
application/
pour les programmes. Le RFC 4329 enregistrait les types
text/javascript
et
application/javascript
, et recommandait le
application/javascript
(en accord avec ce que
dit le RFC 6838). Aujourd'hui, c'est le
contraire, text/javascript
est le
préféré. D'une manière générale, application/
n'a guère été utilisé (pas seulement dans le cas de
JavaScript). (Personnellement, cela me convient très bien : pour
moi, si ça peut s'afficher avec cat et
s'éditer avec vi, c'est du texte.)
JavaScript est donc officiellement du texte, et la section 4 de
notre RFC précise : du texte en UTF-8. Le
paramètre charset
est donc facultatif (même si
le RFC 6838 dit le contraire). Si vous aimez
les détails d'encodage, la section 4 vous ravira (c'est l'un des
points qui a suscité le plus de discussion à l'IETF).
Le type text/javascript
est enregistré
à l'IANA ; les types comme
application/javascript
sont marqués
comme dépassés. Même chose pour
text/ecmascript
. Le nom officiel de JavaScript
est ECMAscript, puisque normalisé à l'ECMA
mais personne n'utilise ce terme. (Il faut quand même noter que
JavaScript est un terme publicitaire mensonger puisqu'il avait été
choisi par le marketing pour profiter de la popularité - à
l'époque - de Java, un
langage avec lequel il n'a rien à voir.) Enfin, les types comme
text/x-javascript
qu'on voit parfois trainer
datent d'avant le RFC 6648, qui abandonnait
les identificateurs semi-privés commençant par
x-
.
La section 5 couvre les questions de sécurité. Elle est très longue car l'envoi de JavaScript à un programme qui l'exécutera (ce qui est très courant sur le Web) pose plein de problèmes de sécurité. Le RFC rappelle que l'exécution automatique d'un programme fourni par un tiers est évidemment intrinsèquement dangereuse, et doit se faire dans un bac à sable. Parmi les dangers (mais il y en a beaucoup d'autres !) le déni de service puisque JavaScript est un langage de Turing et permet donc, par exemple, des boucles infinies.
L'annexe B du RFC résume les changements depuis le RFC 4329 : le principal est bien sûr l'abandon de
application/javascript
au profit de
text/javascript
. Il y a aussi l'ajout de
quelques détails comme une faille de sécurité nouvelle, et le cas
des modules JavaScript.
Ah, un point de détail cité au détour d'un paragraphe du RFC : il
n'y a pas de norme pour les identificateurs de fragments dans un
URI
pointant vers une ressource de type
text/javascript
. Si
https://code.example/js/foreach.js#l23
pointe
vers du JavaScript, la signification du #l23
(l'identificateur de fragment) n'est pas spécifiée.
Date de publication du RFC : Mai 2022
Auteur(s) du RFC : A. Azimov (Qrator Labs & Yandex), E. Bogomazov (Qrator Labs), R. Bush (IIJ & Arrcus), K. Patel (Arrcus), K. Sriram (USA NIST)
Chemin des normes
Réalisé dans le cadre du groupe de travail IETF idr
Première rédaction de cet article le 25 mai 2022
Vous savez sans doute que l'Internet repose sur le protocole de routage BGP et que ce protocole a été cité dans des accidents (voire des attaques) spectaculaires, par exemple lorsqu'un routeur BGP annonçait ou réannonçait des routes qu'il n'aurait pas dû annoncer (ce qu'on nomme une fuite de routes). Si BGP lui-même est pair-à-pair, en revanche, les relations entre les pairs ne sont pas forcément symétriques et des règles sont largement admises ; par exemple, on n'est pas censé annoncer à un transitaire des routes apprises d'un autre transitaire. Ce RFC étend BGP pour indiquer à l'ouverture de la session le type de relations qu'on a avec son pair BGP, ce qui permet de détecter plus facilement certaines fuites de routes.
La solution proposée s'appuie sur un modèle des relations entre opérateurs, le modèle « sans vallée » (valley-free, même si ce terme n'est pas utilisé dans le RFC). On le nomme aussi modèle Gao-Rexford. Ce modèle structure ces relations de telle façon à ce que le trafic aille forcément vers un opérateur aussi ou plus important (la « montagne »), avant de « redescendre » vers la destination ; le trafic ne descend pas dans une « vallée », sauf si elle est la destination finale. Le but est de permettre un routage optimum et d'éviter boucles et goulets d'étranglement, mais ce modèle a aussi une conséquence politico-économique, maintenir la domination des gros acteurs (typiquement les Tier-1). Les relations entre participants à une session BGP sont de type Client-Fournisseur ou Pair-Pair, et le trafic va toujours dans le sens du client vers le fournisseur, sauf pour les destinations finales (on n'utilise donc pas un pair pour du transit, c'est-à-dire pour joindre d'autres réseaux que ceux du pair).
En raison de ce modèle, un routeur BGP n'est pas censé annoncer à un transitaire des routes apprises d'un autre transitaire, ou annoncer à un pair des routes apprises d'un transitaire, ou bien apprises d'un autre pair (RFC 7908). Une fuite de routes est une annonce qui viole cette politique (qu'elle soit explicite ou implicite).
Traditionnellement, on empêche ces fuites par des règles dans la configuration du routeur. Par exemple, le client d'un transitaire va bloquer l'exportation par défaut, puis lister explicitement les routes qu'il veut annoncer. Et le transitaire va bloquer l'importation par défaut, et lister ensuite les routes qu'il acceptera de son client. Ce nouveau RFC ajoute un autre mécanisme, où la relation entre les deux partenaires BGP est explicitement marquée comme Client-Fournisseur ou Pair-Pair, facilitant la détection des fuites. Un concept qui était auparavant purement business, le type de relation entre acteurs BGP, passe ainsi dans le protocole.
Le RFC utilise donc ces termes, pour désigner la place d'un acteur BGP dans le processus de routage :
Une violation de ces règles est une fuite de routes (RFC 7908). Le RFC précise qu'il s'agit de relations « techniques », qu'elles n'impliquent pas forcément une relation business mais, en pratique, l'échange entre pairs est souvent gratuit et la relation Client-Fournisseur payante. Le RFC précise aussi qu'il peut exister des cas plus complexes, comme une relation Client-Fournisseur pour certains préfixes IP et Pair-Pair pour d'autres.
Bon, assez de politique et de business, place à la technique et au protocole. Le RFC définit une nouvelle capacité (RFC 5492) BGP, Role, code 9 et dont la valeur, stockée sur un octet, indique un des rôles listés ci-dessus (0 pour Fournisseur, 3 pour Client, 4 pour Pair, etc). D'autres rôles pourront être ajoutés dans le futur (politique « Examen par l'IETF », cf. RFC 8126). Ce rôle indique la place de l'AS et est configuré avec la session BGP (rappelez-vous qu'un AS peut être Client d'un côté et Fournisseur de l'autre). À l'ouverture de la connexion, les routeurs vérifient la compatibilité des rôles (si tous les deux annoncent qu'ils sont Fournisseur, c'est un problème, d'où un nouveau code d'erreur BGP, Role mismatch).
Une fois que les deux routeurs sont d'accord sur les rôles respectifs de leurs AS, les routes annoncées peuvent être marquées avec un nouvel attribut, OTC (Only To Customer, code 35), dont la valeur est un numéro d'AS. Une route ainsi marquée ne sera acceptée que si elle vient d'un Fournisseur, ou d'un pair dont le numéro d'AS coïncide. Autrement, elle sera considérée comme résultant d'une fuite.
Ah, et pour les cas compliqués dont on a parlé plus haut, comme les rôles différents pour chaque préfixe ? Dans ce cas-là, le mécanisme des rôles de ce RFC n'est pas adapté et ne doit pas être utilisé.
Ce système des rôles est apparemment mis en œuvre dans des patches (non encore intégrés ?) pour FRRouting et BIRD. Par exemple, pour FRRouting, on configurerait :
neighbor 2001:db8:999::1 local-role customer
Pour BIRD, cela serait :
configuration ... protocol bgp { ... local_role customer; }
Et la gestion des rôles devrait être bientôt
dans BGPkit. Et une version antérieure à la sortie du RFC
tourne sur les routeurs MikroTik (voir leur
documentation, local.role ebgp-customer
…).
Par contre, je ne sais pas pour Cisco ou
Juniper. Si quelqu'un a des informations…
Date de publication du RFC : Mars 2022
Auteur(s) du RFC : P. Faltstrom (Netnod)
Chemin des normes
Première rédaction de cet article le 24 avril 2022
La norme pour les noms de domaine en Unicode, dite « IDNA 2008 », prévoit une révision à chaque nouvelle version d'Unicode pour éventuellement s'adapter à des changements dus à ces nouvelles versions. Ce processus de révision a pas mal cafouillé (euphémisme), et ce RFC doit donc traiter d'un coup les versions 6 à 12 d'Unicode.
Le fond du problème est que l'ancienne norme sur les IDN (RFC 3490) était strictement liée à une version donnée d'Unicode et qu'il fallait donc une nouvelle norme pour chacune des versions annuelles d'Unicode. Vu le processus de publication d'une norme à l'IETF, ce n'était pas réaliste. La seconde norme IDN, « IDN 2 » ou « IDN 2008 » (bien qu'elle soit sortie en 2010) remplaçait les anciennes tables fixes de caractères autorisés ou interdits par un algorithme, à faire tourner à chaque sortie d'une version d'Unicode pour produire les tables listant les caractères qu'on peut accepter dans un nom de domaine internationalisé (le mécanisme exact, utilisant les propriétés des caractères listées dans la norme Unicode, figure dans le RFC 5892). En théorie, c'était très bien. En pratique, malgré les règles de stabilité d'Unicode, il se produisait parfois des problèmes. Comme le documente le RFC 8753, un caractère peut ainsi passer d'interdit à autorisé, ce qui n'est pas grave mais aussi dans certains cas d'autorisé à interdit ce qui est bien plus embêtant : que faire des noms déjà réservés qui utilisent ce caractère ? En général, il faut ajouter une exception manuelle, ce qui justifie un mécanisme de révision de la norme IDN, mis en place par ce RFC 8753. Ce nouveau RFC 9233 est le premier RFC de révision. Heureusement, cette fois, aucune exception manuelle n'a été nécessaire.
La précédente crise était due à Unicode version 6 qui avait créé trois incompatibilités (RFC 6452). Une seule était vraiment gênante, le caractère ᧚, qui, autorisé auparavant, était devenu interdit suite au changement de ses propriétés dans la norme Unicode. Le RFC 6452 avait documenté la décision de ne rien faire pour ce cas, ce caractère n'ayant apparemment jamais été utilisé. Unicode 7 a ajouté un autre problème, le ࢡ, qui était un cas différent (la possibilité de l'écrire de plusieurs façons), et la décision a été prise de faire désormais un examen formel de chaque nouvelle version d'Unicode. Mais cet examen a été souvent retardé et voilà pourquoi, alors qu'Unicode 13 est sorti (ainsi qu'Unicode 14 depuis), ce nouveau RFC ne traite que jusqu'à la version 12.
Passons maintenant à l'examen des changements apportés par les versions 7 à 12 d'Unicode, fait en section 3 du RFC :
En Unicode 11, 𑇉 qui passe d'interdit à autorisé, était un cas peu gênant. Le RFC prend donc la décision de ne pas ajouter d'exception pour ce caractère peu commun.
Voilà, arrivé ici, vous pensez peut-être que cela fait beaucoup de bruit pour rien puisque finalement les différentes versions d'Unicode n'ont pas créé de problème. Mais c'est justement pour s'assurer de cela que cet examen était nécessaire.
Pour compliquer davantage les choses, on notera qu'il existe encore sans doute (section 2.3 du RFC) des déploiements d'IDN qui en sont restés à la première version (celle du RFC 3490) voire qui sont un mélange des deux versions d'IDN, en ce qui concerne l'acceptation ou le refus des caractères.
En avril 2022, le travail pour Unicode 13 ou Unicode 14 n'a apparemment pas encore commencé…
Date de publication du RFC : Juin 2022
Auteur(s) du RFC : E. Kinnear (Apple), P. McManus (Fastly), T. Pauly (Apple), T. Verma, C.A. Wood (Cloudflare)
Expérimental
Première rédaction de cet article le 9 juin 2022
Des protocoles de DNS sécurisés comme DoH protègent la liaison entre un client et un résolveur DNS. Mais le résolveur, lui, voit tout, aussi bien le nom de domaine demandé que l'adresse IP du client. Ce nouveau RFC décrit un mécanisme expérimental de relais entre le client et le serveur DoH, permettant de cacher l'adresse IP du client au serveur, et la requête au relais. Une sorte de Tor minimum.
Normalement, DoH (normalisé dans le RFC 8484) résout le problème de la surveillance des requêtes DNS. Sauf qu'évidemment le serveur DoH lui-même, le résolveur, voit le nom demandé, et la réponse faite. (Les utilisateurices de l'Internet oublient souvent que la cryptographie, par exemple avec TLS, protège certes contre le tiers qui écoute le réseau mais pas du tout contre le serveur avec qui on parle.) C'est certes nécessaire à son fonctionnement mais, comme il voit également l'adresse IP du client, il peut apprendre pas mal de choses sur le client. Une solution possible est d'utiliser DoH (ou DoT) sur Tor (exemple plus loin). Mais Tor est souvent lent, complexe et pas toujours disponible (cf. annexe A du RFC). D'où les recherches d'une solution plus légère, Oblivious DoH, décrit dans ce nouveau RFC. L'idée de base est de séparer le routage des messages (qui nécessite qu'on connaisse l'adresse IP du client) et la résolution DNS (qui nécessite qu'on connaisse la requête). Si ces deux fonctions sont assurées par deux machines indépendantes, on aura amélioré la protection de la vie privée.
Les deux machines vont donc être :
Un schéma simplifié :
Il est important de répéter que, si le relais et le serveur sont complices, Oblivious DoH perd tout intérêt. Ils doivent donc être gérés par des organisations différentes.
La découverte du relais, et de la clé publique du serveur, n'est pas traité dans ce RFC. Au moins au début, elle se fera « manuellement ».
Le gros morceau du RFC est sa section 4. Elle explique les
détails du protocole. Le client qui a une requête DNS à faire doit
la chiffrer avec la clé publique du serveur. Ce client a été
configuré avec un gabarit d'URI (RFC 6570) et avec
l'URL du
serveur DoH, serveur qui doit connaitre le mécanisme
Oblivious DoH. Le gabarit doit contenir deux
variables, targethost
(qui sera incarné avec le
nom du serveur DoH) et targetpath
(qui sera
incarné avec le chemin dans l'URL du serveur DoH). Par exemple, un
gabarit peut être
https://dnsproxy.example/{targethost}/{targetpath}
. Le
client va ensuite faire une requête (méthode HTTP
POST
) vers le relais. La requête aura le
type application/oblivious-dns-message
(la réponse également, donc le client a intérêt à indiquer
Accept: application/oblivious-dns-message
).
En supposant un serveur DoH d'URL
https://dnstarget.example/dns
, la requête sera
donc (en utilisant la syntaxe de description de HTTP/2 et 3) :
:method = POST :scheme = https :authority = dnsproxy.example :path = /dnstarget.example/dns accept = application/oblivious-dns-message content-type = application/oblivious-dns-message content-length = 106 [La requête, chiffrée]
Le relais enverra alors au serveur :
:method = POST :scheme = https :authority = dnstarget.example :path = /dns accept = application/oblivious-dns-message content-type = application/oblivious-dns-message content-length = 106 [La requête, chiffrée]
Le relais ne doit pas ajouter quelque chose qui permettrait
d'identifier le client, ce qui annulerait tout l'intérêt
d'Oblivious DoH. Ainsi, le champ
forwarded
(RFC 7239) est
interdit. Et bien sûr, on n'envoie pas de
cookies, non plus.
La réponse utilise le même type de médias :
:status = 200 content-type = application/oblivious-dns-message content-length = 154 [La réponse, chiffrée]
Le relais, en la transmettant, ajoutera un champ
proxy-status
(RFC 9209).
Les messages de type
application/oblivious-dns-message
sont encodés
ainsi (en utilisant le langage de description de TLS, cf. RFC 8446, section 3) :
struct { opaque dns_message<1..2^16-1>; opaque padding<0..2^16-1>; } ObliviousDoHMessagePlaintext;
Bref, un message DNS binaire, et du remplissage. Le remplissage est nécessaire car TLS, par défaut, n'empêche pas l'analyse de trafic. (Lisez le RFC 8467.) Le tout est ensuite chiffré puis mis dans :
struct { uint8 message_type; opaque key_id<0..2^16-1>; opaque encrypted_message<1..2^16-1>; } ObliviousDoHMessage;
Le key_id
servant à identifier ensuite le
matériel cryptographique utilisé.
J'ai parlé à plusieurs reprises de chiffrement. Le client doit connaitre la clé publique du serveur DoH pour pouvoir chiffrer. (Le RFC ne prévoit pas de mécanisme pour cela.) Comme avec la plupart des systèmes de chiffrement, le chiffrement sera en fait réalisé avec un algorithme symétrique. La transmission ou la génération de la clé de cet algorithme utlisera le mécanisme HPKE (Hybrid Public Key Encryption, spécifié dans le RFC 9180).
Outre l'analyse de trafic, citée plus haut, une potentielle faille d'Oblivious DoH serait le cas d'un serveur indiscret qui essaierait de déduire des informations de l'établissement des connexions DoH vers lui. Le RFC recommande que les relais établissent des connexions de longue durée et les réutilisent pour plusieurs clients, rendant cette éventuelle analyse plus complexe.
Notez aussi que le relais est évidemment libre de sa politique. Les relais peuvent par exemple ne relayer que vers certains serveurs DoH connus.
Le serveur DoH ne connait pas l'adresse IP du client (c'est le but) et ne peut donc pas transmettre aux serveurs faisant autorité les informations qui peuvent leur permettre d'envoyer une réponse adaptée au client final. Si c'est un problème, le client final peut toujours utiliser ECS (EDNS Client Subnet, RFC 7871), en indiquant un préfixe IP assez générique pour ne pas trop en révéler. Mais cela fait évidemment perdre une partie de l'intérêt d'Oblivious DoH.
Bon, et questions mises en œuvre de ce RFC ? Il y en a une dans DNScrypt (et ils publient une liste de serveurs et de relais).
Oblivious DoH est en travaux depuis un certain temps (voyez par exemple cet article de Cloudflare). Notez que la technique de ce RFC est spécifique à DoH. Il y a aussi des travaux en cours à l'IETF pour un mécanisme plus général, comme par exemple un Oblivious HTTP, qui serait un concurrent « low cost » de Tor.
À propos de Tor, j'avais dit plus haut qu'on pouvait évidemment, si on ne veut pas révéler son adresse IP au résolveur DoH, faire du DoH sur Tor. Voici un exemple de requête DoH faite avec le client kdig et le programme torsocks pour faire passer les requêtes TCP par Tor :
% torsocks kdig +https=/ @doh.bortzmeyer.fr ukraine.ua ;; TLS session (TLS1.3)-(ECDHE-SECP256R1)-(RSA-PSS-RSAE-SHA256)-(AES-256-GCM) ;; HTTP session (HTTP/2-POST)-(doh.bortzmeyer.fr/)-(status: 200) ... ;; ANSWER SECTION: ukraine.ua. 260 IN A 104.18.7.16 ukraine.ua. 260 IN A 104.18.6.16 ... ;; Received 468 B ;; Time 2022-06-02 18:45:15 UTC ;; From 193.70.85.11@443(TCP) in 313.9 ms
Et ce résolveur DoH ayant un service
.onion
, on peut même :
% torsocks kdig +https=/ @lani4a4fr33kqqjeiy3qubhfx2jewfd3aeaepuwzxrx6zywp2mo4cjad.onion ukraine.ua ;; TLS session (TLS1.3)-(ECDHE-SECP256R1)-(RSA-PSS-RSAE-SHA256)-(AES-256-GCM) ;; HTTP session (HTTP/2-POST)-(lani4a4fr33kqqjeiy3qubhfx2jewfd3aeaepuwzxrx6zywp2mo4cjad.onion/)-(status: 200) ... ;; ANSWER SECTION: ukraine.ua. 212 IN A 104.18.6.16 ukraine.ua. 212 IN A 104.18.7.16 ... ;; Received 468 B ;; Time 2022-06-02 18:46:00 UTC ;; From 127.42.42.0@443(TCP) in 1253.8 ms
Pour comparer les performances (on a dit que la latence de Tor était franchement élevée), voici une requête non-Tor vers le même résolveur :
% kdig +https=/ @doh.bortzmeyer.fr ukraine.ua ;; TLS session (TLS1.3)-(ECDHE-SECP256R1)-(RSA-PSS-RSAE-SHA256)-(AES-256-GCM) ;; HTTP session (HTTP/2-POST)-([2001:41d0:302:2200::180]/)-(status: 200) ... ;; ANSWER SECTION: ukraine.ua. 300 IN A 104.18.6.16 ukraine.ua. 300 IN A 104.18.7.16 ... ;; Received 468 B ;; Time 2022-06-02 18:49:44 UTC ;; From 2001:41d0:302:2200::180@443(TCP) in 26.0 ms
Date de publication du RFC : Mai 2022
Auteur(s) du RFC : J. Chroboczek (IRIF, University of Paris)
Expérimental
Réalisé dans le cadre du groupe de travail IETF babel
Première rédaction de cet article le 11 mai 2022
Voici une extension sympathique qui réjouira tous les amateurs de routage IP. Elle permet à un routeur qui parle le protocole de routage Babel d'annoncer un préfixe IPv4 où le routeur suivant pour ce préfixe n'a pas d'adresse IPv4 sur cette interface.
Un petit rappel de routage : les annonces
d'un routeur qui parle un protocole de
routage dynamique comme Babel (RFC 8966) comprennent un préfixe IP (comme
2001:db8:543::/48
) et l'adresse IP du routeur suivant
(next hop dans la langue de Radia
Perlman). Traditionnellement, le préfixe et l'adresse du
routeur suivant étaient de la même version (famille) : tous les deux
en IPv4 ou tous les deux en
IPv6. Mais, si on veut router IPv6
et IPv4, cela consomme une adresse IP par
interface sur chaque routeur, or les adresses IPv4 sont désormais
une denrée très rare. D'où la proposition de ce RFC : permettre des annonces
d'un préfixe IPv4 avec une adresse de routeur suivant en
IPv6. (Notez que cela ne concerne pas que Babel, cela pourrait être
utilisé pour d'autres protocoles de routage dynamique.)
La section 1 du RFC résume ce que fait un protocole de routage. Son but est de construire des tables de routage, où chaque entrée de la table est indexée par un préfixe d'adresses IP et a une valeur, l'adresse du routeur suivant. Par exemple :
destination next hop 2001:db8:0:1::/64 fe80::1234:5678%eth0 203.0.113.0/24 192.0.2.1
Lorsqu'un routeur doit transmettre un paquet, il regarde dans cette table et trouve l'adresse du routeur suivant. Il va alors utiliser un protocole de résolution d'adresses (ARP - RFC 826 - pour IPv4, ND - RFC 4861 - pour IPv6) pour trouver l'adresse MAC du routeur suivant. Rien dans cette procédure n'impose que le préfixe de destination et l'adresse du routeur suivant soient de la même version d'IP. Un routeur qui veut transmettre un paquet IPv4 vers un routeur dont il ne connait que l'adresse IPv6 procède à la résolution en adresse MAC, puis met simplement le paquet IPv4 dans une trame portant l'adresse MAC en question (l'adresse IPv6 du routeur suivant n'apparait pas dans le paquet).
En permettant des annonces de préfixes IPv4 avec un routeur suivant en IPv6, on économise des adresses IPv4. Un réseau peut fournir de la connectivité IPv4 sans avoir d'adresses IPv4, à part au bord. Et comme les adresses IPv6 des routeurs sont des adresses locales au lien allouées automatiquement (cf. RFC 7404), on peut avoir un réseau dont le cœur n'a aucune adresse statique, ce qui peut faciliter sa gestion.
Notre RFC documente donc une extension au protocole Babel (qui
est normalisé dans le RFC 8966), nommée
v4-via-v6
. (Comme dit plus haut, le principe
n'est pas spécifique à Babel, voir le RFC 5549 pour son équivalent pour BGP.)
Bon, le concret, maintenant. Les préfixes annoncés en Babel sont précédés de l'AE (Address Encoding), un entier qui indique la version (famille) du préfixe. Ce RFC crée un nouvel AE, portant le numéro 4, qui a le même format que l'AE 1 qui servait à IPv4. Une annonce d'un préfixe ayant l'AE 4 devra donc contenir un préfixe IPv4 et un routeur suivant en IPv6. Un routeur qui sait router IPv4 mais n'a pas d'adresse IPv4 sur une de ses interfaces peut donc quand même y annoncer les préfixes connus, en les marquant avec l'AE 4 et en mettant comme adresse l'adresse IPv6 pour cette interface. (Cet AE est uniquement pour les préfixes, pas pour l'indication du routeur suivant.)
Les routeurs qui ne gèrent pas l'extension
v4-via-v6
ignoreront cette annonce, comme ils
doivent ignorer toutes les annonces portant un AE inconnu (RFC 8966). Pour
cette raison, si un routeur a une adresse IPv4 sur une interface, il
faut qu'il utilise des annonces traditionnelles, avec l'AE 1, pour
maximiser les chances que son annonce soit acceptée.
Arrivé ici, mes lectrices et mes lecteurs, qui sont très malins, se demandent depuis longtemps « mais un routeur doit parfois émettre des erreurs ICMP (RFC 792), par exemple s'il n'a pas d'entrée dans sa table pour cette destination ». Comment peut-il faire s'il n'a pas d'adresse sur l'interface où il a reçu le paquet coupable ? Ne rien émettre n'est pas acceptable, certains protocoles en dépendent. C'est par exemple le cas de la découverte de la MTU du chemin, documentée dans le RFC 1191, qui a besoin des messages ICMP Packet too big. Certes, il existe un algorithme de découverte de cette MTU du chemin qui est entièrement de bout en bout, et ne dépend donc pas des routeurs et de leurs messages (RFC 4821). Mais ICMP sert à d'autres choses, on ne peut pas y renoncer.
Si le routeur Babel a une adresse IPv4 quelque part, sur une de
ses interfaces, il peut l'utiliser comme adresse IP source pour ses
messages ICMP. S'il n'en a pas, il peut toujours utiliser
192.0.0.8
comme suggéré par la section 4.8 du
RFC 7600. Bien sûr, si tout le monde le fait, des outils
d'administration du réseau très pratiques comme
traceroute seront moins utiles.
La nouvelle extension peut soulever des questions de sécurité (section 6 du RFC). Par exemple, si l'administrateur réseaux croyait que, parce que les routeurs n'avaient pas d'adresse IPv4 sur une interface, les paquets IPv4 ne seraient pas traités sur cette interface, cette supposition n'est plus vraie. Ainsi, une île de machines IPv4 qui se croyaient séparées de l'Internet IPv4 par un ensemble de routeurs qui n'avaient pas d'adresse IPv4 de leur côté peut se retrouver subitement connectée. Si ce n'est pas ce qu'on veut, il faut penser à ne pas se contenter du protocole de routage pour filtrer, mais aussi à filtrer explicitement IPv4.
Question mise en œuvre, cette extension figure dans babeld (voir ici un compte-rendu d'expérience) à partir de la version 1.12, ainsi que dans BIRD.
Date de publication du RFC : Avril 2022
Auteur(s) du RFC : D. Crocker (Brandenburg InternetWorking)
Expérimental
Première rédaction de cet article le 14 avril 2022
Le courrier électronique est souvent plus
compliqué et plus varié que ne le croient certains utilisateurs. Par
exemple, rien ne garantit que l'adresse à laquelle un message est
livré soit listée dans les champs de l'en-tête (RFC 5322) comme To:
ou
Cc:
. Cette adresse de destination est en effet
séparément gérée, dans le protocole SMTP (RFC 5321)
et ses comandes RCPT TO
. Et puis un message
peut être relayé, il peut y avoir plusieurs livraisons
successives. Bref, quand on a un message dans sa boite aux lettres,
on ne sait pas forcément quelle adresse avait servi. D'où ce
RFC expérimental
qui propose d'élargir le rôle du champ
Delivered-To:
pour en faire un bon outil
d'information.
Ce champ existe déjà et on le voit parfois apparaitre dans les en-têtes, par exemple dans un message que je viens de recevoir :
Delivered-To: monpseudo@sources.org
Mais il n'est pas vraiment normalisé, son contenu exact peut varier et, en pratique, lorsqu'il y a eu plusieurs délivrances successives suivies de transmissions à une autre adresse, il ne reste parfois qu'une occurrence de ce champ, la plus récente. Difficile donc de compter dessus.
Mais c'est quoi, la livraison d'un message ? Comme définie par le RFC 5598, dans sa section 4.3.3, c'est un transfert de responsabilité d'un MTA vers un MDA comme procmail. Il peut y avoir plusieurs délivrances successives par exemple si le MDA, au lieu de simplement écrire dans la boite aux lettres de l'utilisateur, fait suivre le message à une autre adresse. Un exemple typique est celui d'une liste de diffusion, où le message va être délivré à la liste puis, après transmission par le logiciel de gestion de listes, à chaque utilisateur (dont les MDA renverront peut-être le message, créant de nouvelles livraisons).
Pourquoi est-il utile d'avoir de l'information sur ces livraisons successives ? Outre la curiosité, une motivation importante est le débogage. S'il y a des problèmes de livraison, la traçabilité est essentielle à l'analyse. Une autre motivation est la détection automatique de boucles, un problème souvent compliqué.
On l'a dit, Delivered-To:
existe déjà, même
s'il n'était normalisé dans aucun RFC, bien qu'il soit cité dans des exemples,
comme dans le RFC 5193. Ce RFC 9228 enregistre le champ (selon la
procédure du RFC 3864), et prévoit d'étendre son
application. (Une tentative de normalisation est en cours dans
draft-duklev-deliveredto
.) Du fait de cette
absence de normalisation, il existe de nombreuses variations dans
l'utilisation de Delivered-To:
. Par exemple, si
la plupart des logiciels mettent comme valeur une adresse de
courrier et rien d'autre, certains ajoutent des commentaires. Notons
que le champ Received:
est souvent utilisé de
la même façon, via sa clause for
. On peut ainsi
trouver (regardez le for
) :
Received: from bendel.debian.org (bendel.debian.org [82.195.75.100]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by ayla.bortzmeyer.org (Postfix) with ESMTPS id 6D245A00D9 for <monpseudo@sources.org>; Wed, 16 Feb 2022 13:22:03 +0100 (CET)
Là encore, il n'y a pas de normalisation et il semble qu'on ne
puisse pas compter sur cette clause for
. Par
exemple, elle n'indique pas forcément une livraison mais peut être
utilisée pour une simple transmission d'un serveur à un autre.
Venons-en à la définition de l'utilisation de
Delivered-To:
proposée par notre RFC dans sa
section 4 :
Delivered-To:
est ajouté
uniquement lors d'une livraison,Delivered-To:
avec l'adresse réécrite,Delivered-To:
(en cas de livraisons multiples),
le logiciel qui en ajoute un doit le mettre en haut (comme pour les
autres champs de trace de l'en-tête, par exemple
Received:
, cf. RFC 5321,
section, 4.1.1.4), l'ordre chronologique des
Delivered-To:
sera donc de bas en haut,Delivered-To:
ou en supprimer, sous peine de
casser cette chronologie.
En conséquence, si un logiciel qui va ajouter un
Delivered-To:
avec une certaine adresse voit un
Delivered-To:
avec la même adresse, il peut
être sûr qu'il y a une boucle. Voici un exemple de deux
Delivered-To:
différents, en raison du passage
par une liste de diffusion (le message comportait évidemment bien
d'autres champs dans son en-tête). La liste a été la première à
traiter le message, la livraison finale ayant eu lieu après (donc
plus haut dans le message, cf. section 5 du RFC) :
Delivered-To: monpseudo@sources.org Delivered-To: lists-debian-user-french@bendel.debian.org
Le RFC donne un exemple imaginaire mais plus détaillé (j'ai
raccourci les exemples du RFC, référez-vous à lui pour avoir les
Received:
et autres détails). D'abord, le
message est émis par Ann :
From: Ann Author <aauthor@com.example> Date: Mon, 25 Jan 2021 18:29:00 -0500 To: list@org.example Subject: Sending through a list and alias Sender: Ann Author <aauthor@com.example>
Ce message a été envoyé à une liste de diffusion. Il est donc désormais :
Delivered-To: list@org.example From: Ann Author <aauthor@com.example> Date: Mon, 25 Jan 2021 18:29:06 -0500 To: list@org.example Subject: Sending through a list and alias
Un des abonnés à la liste est
alumn@edu.example
. Chez lui, le
message sera :
Delivered-To: alumn@edu.example Delivered-To: list@org.example From: Ann Author <aauthor@com.example> Date: Mon, 25 Jan 2021 18:29:06 -0500 To: list@org.example Subject: Sending through a list and alias
Et cet abonné fait suivre automatiquement son courrier à
theRecipient@example.net
. Le message, dans son
état final, sera :
Delivered-To: theRecipient@example.net Delivered-To: alumn@edu.example Delivered-To: list@org.example From: Ann Author <aauthor@com.example> Date: Mon, 25 Jan 2021 18:29:06 -0500 To: list@org.example Subject: Sending through a list and alias
Voilà, vous savez tout désormais sur l'extension proposée de
l'utilisation de Delivered-To:
. La section 6
attire toutefois notre attention sur quelques
risques. Delivered-To:
a comme valeur une
donnée personnelle. Donc, attention, cela
peut poser des questions de vie
privée. Par exemple, la liste des adresses par
lesquelles est passé un message peut en révéler plus que ce que le
destinataire connaissait. Le problème risque surtout de se poser si
quelqu'un fait suivre manuellement un message en incluant tous les
en-têtes.
Autre problème potentiel, certains systèmes stockent les messages
identiques en un seul exemplaire. Si on écrit à
alice@example.com
et
bob@example.com
, le serveur
d'example.com
pourrait décider de ne stocker
qu'un seul exemplaire du message, avec des liens depuis les boites
aux lettres d'Alice et Bob. Ce serait évidemment incompatible avec
Delivered-To:
(et pas question de mettre deux
Delivered-To:
, on ne veut pas révéler à Alice
que Bob a reçu le message et réciproquement).
Date de publication du RFC : Mars 2022
Auteur(s) du RFC : M. Blanchet (Viagenie)
Chemin des normes
Réalisé dans le cadre du groupe de travail IETF regext
Première rédaction de cet article le 25 avril 2022
Le protocole RDAP d'accès à l'information
sur les objets (noms de
domaines, adresses
IP, etc) stockés dans un registre fonctionne en
interrogeant le serveur en HTTP. Encore faut-il trouver le serveur à
interroger. Comment le client RDAP qui veut des informations sur
2001:67c:288::2
sait-il à quel serveur
demander ? Ce RFC
décrit le mécanisme choisi. En gros, l'IANA gère des
« méta-services » qui donnent la liste de serveurs RDAP, mais il
peut aussi y avoir redirection d'un serveur RDAP vers un autre. Ce
RFC remplace le premier RFC sur le sujet, le RFC 7484, avec très peu de changements.
Le protocole RDAP est décrit entre autres dans le RFC 7480 qui précise comment utiliser HTTP pour transporter les requêtes et réponses RDAP. Comme indiqué plus haut, il ne précise pas comment trouver le serveur RDAP responsable d'un objet donné. Le prédécesseur de RDAP, whois, avait exactement le même problème, résolu par exemple en mettant en dur dans le logiciel client tous les serveurs whois connus. En pratique, beaucoup de gens font simplement une requête Google et accordent une confiance aveugle auu premier résultat venu, quel que soit sa source. Au contraire, ce RFC vise à fournir un mécanisme pour trouver la source faisant autorité.
Avant d'exposer la solution utilisée, la section 1 de notre RFC rappelle comment ces objets (noms de domaine, adresses IP, numéros d'AS, etc) sont alloués et délégués. L'IANA délégue les TLD aux registres de noms de domaines, des grands préfixes IP et les blocs de numéros d'AS aux RIR. Il est donc logique que RDAP suive le même chemin : pour trouver le serveur responsable, on commence par demander à l'IANA, qui a déjà les processus pour cela, et maintient les registres correspondants (adresses IPv4, adresses IPv6, numéros d'AS et domaines).
Pour permettre à RDAP de fonctionner, ce RFC demande donc à l'IANA quelques fichiers supplémentaires, au format JSON (RFC 8259), dont le contenu dérive des registres cités plus haut. Et l'IANA doit aussi prévoir des serveurs adaptés à servir ces fichiers, nommés RDAP Bootstrap Service, à de nombreux clients RDAP.
Le format de ces fichiers JSON est décrit dans la section 3 de
notre RFC. Chaque bootstrap service contient des
métadonnées comme un numéro de version et une date de publication,
et il contient un membre nommé services
qui
indique les URL où aller chercher l'information (la syntaxe
formelle figure en section 10). Voici un extrait d'un des fichiers
actuels :
% curl https://data.iana.org/rdap/ipv4.json "description": "RDAP bootstrap file for IPv4 address allocations", "publication": "2019-06-07T19:00:02Z", "services": [ [ [ "41.0.0.0/8", "102.0.0.0/8", "105.0.0.0/8", "154.0.0.0/8", "196.0.0.0/8", "197.0.0.0/8" ], [ "https://rdap.afrinic.net/rdap/", "http://rdap.afrinic.net/rdap/" ] ], ...
On voit que le contenu est un tableau donnant, pour des objets
donnés, les URL des serveurs RDAP à contacter pour ces objets
indiqués, par exemple pour l'adresse IP
154.3.2.1
, on ira demander au serveur RDAP
d'AfriNIC en
https://rdap.afrinic.net/rdap/
.
Le membre publication
indique la date de
parution au format du RFC 3339. Les URL
indiqués se terminent par une barre oblique,
le client RDAP a juste à leur ajouter une requête formulée selon la
syntaxe du RFC 9082. Ainsi, cherchant des
informations sur 192.0.2.24
, on ajoutera
ip/192.0.2.24
à l'URL de base.
Pour les adresses IP (section 5), les entrées sont des préfixes, comme dans le premier exemple montré plus haut et la correspondance se fait avec le préfixe le plus spécifique (comme pour le routage IP). Les préfixes IPv4 suivent la syntaxe du RFC 4632 et les IPv6 celle du RFC 5952. Voici un exemple IPv6 (légèrement modifié d'un cas réel) :
[ [ "2001:200::/23", "2001:4400::/23", "2001:8000::/19", "2001:a000::/20", "2001:b000::/20", "2001:c00::/23", "2001:e00::/23", "2400::/12" ], [ "https://rdap.apnic.net/" ] ], [ [ "2001:200:1000::/36" ], [ "https://example.net/rdaprir2/" ]
Si on cherche de l'information sur le préfixe
2001:200:1000::/48
, il correspond à deux
entrées dans le tableau des services
(2001:200::/23
et
2001:200:1000::/36
) mais la règle du préfixe le
plus long (le plus spécifique) fait qu'on va utiliser
2001:200:1000::/36
, et la requête finale sera
donc
https://example.net/rdaprir2/ip/2001:200:1000::/48
.
Pour les domaines (section 4), les entrées des services sont des noms de domaine :
... "services": [ [ ["net", "com", "org"], [ "https://registry.example.com/myrdap/" ] ], [ ["foobar.org", "mytld"], [ "https://example.org/" ] ], ...
L'entrée sélectionnée est la plus longue (en nombre de composants du
nom, pas en nombre de caractères) qui correspond. Dans l'exemple
ci-dessus, si on cherche des informations sur
foobar.org
, on ira voir le serveur RDAP en
https://example.org/
, si on en cherche sur
toto.org
, on ira en
https://registry.example.com/myrdap/
. (En
pratique, aujourd'hui, le tableau des serveurs RDAP ne contient que
des TLD.)
Pour les numéros d'AS (section 5.3), on se sert d'intervalles de numéros et de la syntaxe du RFC 5396 :
"services": [ [ ["2045-2045"], [ "https://rir3.example.com/myrdap/" ] ], [ ["10000-12000", "300000-400000"], [ "http://example.org/" ] ], ...
Les entités (section 6 de notre RFC) posent un problème particulier, elles ne se situent pas dans un espace arborescent, contrairement aux autres objets utilisable avec RDAP. (Rappel : les entités sont les contacts, les titulaires, les BE…) Il n'existe donc pas de bootstrap service pour les entités (ni, d'ailleurs, pour les serveurs de noms, cf. section 9). En pratique, si une requête RDAP renvoie une réponse incluant un handle pour une entité, il n'est pas idiot d'envoyer les requêtes d'information sur cette entité au même serveur RDAP mais il n'est pas garanti que cela marche.
Notez (section 7) que le tableau services
ne
sera pas forcément complet et que certains objets peuvent ne pas s'y
trouver. Par exemple, dans un tableau pour les TLD, les registres n'ayant pas
encore de serveur RDAP ne seront logiquement pas cités. On peut
toujours tenter un autre serveur, en espérant qu'il utilisera les
redirections HTTP. Par exemple, ici, on demande au serveur RDAP de
l'APNIC pour une adresse RIPE. On est bien redirigé
avec un code 301 (RFC 7231, section 6.4.2) :
% curl -v http://rdap.apnic.net/ip/2001:67c:288::2 ... > GET /ip/2001:67c:288::2 HTTP/1.1 > User-Agent: curl/7.38.0 > Host: rdap.apnic.net > Accept: */* > < HTTP/1.1 301 Moved Permanently < Date: Wed, 01 Apr 2015 13:07:00 GMT < Location: http://rdap.db.ripe.net/ip/2001:67c:288::2 < Content-Type: application/rdap+json ...
La section 8 couvre quelques détails liés au déploiement de ce
service. C'est que le Bootstrap Service est
différent des autres registres IANA. Ces autres registres ne sont
consultés que rarement (par exemple, lors de l'écriture d'un
logiciel) et jamais en temps réel. Si le serveur HTTP de l'IANA
plante, ce n'est donc pas trop grave. Au contraire, le
Bootstrap Service doit marcher en permanence, car
un client RDAP en a besoin. Pour limiter la pression sur les
serveurs de l'IANA, notre RFC recommande que les clients RDAP ne
consultent pas ce service à chaque requête RDAP, mais qu'au
contraire ils mémorisent le JSON récupéré, en utilisant le champ
Expires:
de HTTP (RFC 7234, section 5.3) pour déterminer combien de temps doit
durer cette mémorisation. Néanmoins, l'IANA a dû ajuster ses
serveurs HTTP et louer les services d'un CDN pour assurer ce rôle relativement
nouveau.
Le client RDAP doit d'autre part être conscient que le registre n'est pas mis à jour instantanément. Par exemple, si un nouveau TLD est ajouté par le gouvernement états-unien, via Verisign, TLD dont le registre a un serveur RDAP, cette information ne sera pas disponible immédiatement pour tous les clients RDAP.
Comme son ancêtre whois, RDAP soulève plein de questions de sécurité intéressantes, détaillées plus précisement dans le RFC 7481.
La section 12 de notre RFC détaille les processus IANA à l'œuvre. En effet, et c'est une autre différence avec les registres IANA habituels, il n'y a pas de mise à jour explicite des registres du bootstrap service, ils sont mis à jour implicitement comme effet de bord des allocations et modifications d'allocation dans les registres d'adresses IPv4, adresses IPv6, numéros d'AS et domaines. Il a juste fallu modifier les procédures de gestion de ces registres existants, pour permettre d'indiquer le serveur RDAP. Ainsi, le formulaire de gestion d'un TLD par son responsable a été modifié pour ajouter un champ "serveur RDAP" comme il y avait un champ "serveur Whois".
Aujourd'hui, les fichiers de ce service sont :
https://data.iana.org/rdap/dns.json
https://data.iana.org/rdap/ipv4.json
https://data.iana.org/rdap/ipv6.json
https://data.iana.org/rdap/asn.json
Voici, par exemple, celui d'IPv6 (notez le champ
Expires:
dans la réponse) :
% curl -v https://data.iana.org/rdap/ipv6.json ... * Connected to data.iana.org (2606:2800:11f:bb5:f27:227f:1bbf:a0e) port 80 (#0) > GET /rdap/ipv6.json HTTP/1.1 > Host: data.iana.org > User-Agent: curl/7.68.0 > Accept: */* > < HTTP/1.1 200 OK < Expires: Tue, 26 Apr 2022 12:30:52 GMT < Last-Modified: Wed, 06 Nov 2019 19:00:04 GMT ... { "description": "RDAP bootstrap file for IPv6 address allocations", "publication": "2019-11-06T19:00:04Z", "services": [ [ [ "2001:4200::/23", "2c00::/12" ], [ "https://rdap.afrinic.net/rdap/", "http://rdap.afrinic.net/rdap/" ...
Et celui des TLD :
% curl -v http://data.iana.org/rdap/dns.json ... "description": "RDAP bootstrap file for Domain Name System registrations", "publication": "2022-04-22T16:00:01Z", "services": [ [ [ "nowruz", "pars", "shia", "tci", "xn--mgbt3dhd" ], [ "https://api.rdap.agitsys.net/" ] ],
Testons si cela marche vraiment :
% curl -v https://api.rdap.agitsys.net/domain/www.nowruz ... "nameservers": [ { "objectClassName": "nameserver", "ldhName": "ns1.aftermarket.pl.", "unicodeName": "ns1.aftermarket.pl." }, { "objectClassName": "nameserver", "ldhName": "ns2.aftermarket.pl.", "unicodeName": "ns2.aftermarket.pl." } ]
Parfait, tout marche.
Il y a très peu de changements depuis le RFC précédent, le RFC 7484, quelques nettoyages seulement, et un changement de statut sur le chemin des normes.
Si vous aimez lire des programmes, j'ai fait deux mises en œuvre
de ce RFC, la première en Python est incluse dans le code utilisé pour
mon article « Récupérer la date
d'expiration d'un domaine en RDAP ». Le code principal est
mais vous noterez
que, contrairement à ce que demande le RFC dans sa section 8, la
mémorisation du bootstreap registry n'utilise pas
le champ HTTP ianardap.py
Expires:
. La deuxième, en
Elixir, est plus correcte et est disponible
dans
. Lorsque la constante
find-rdap-elixir.tar
@verbose
est vraie, le programme affiche les
étapes. S'il n'a pas mémorisé de données :
% mix run find_rdap.exs quimper.bzh Starting No expiration known Retrieving from https://data.iana.org/rdap/dns.json Updating the cache RDAP bootstrap file for Domain Name System registrations published on 2022-04-22T16:00:01Z RDAP server for quimper.bzh is https://rdap.nic.bzh/ Retrieving RDAP info at https://rdap.nic.bzh/domain/quimper.bzh {"rdapConformance":["rdap_level_0","...
S'il en a mémorisé :
% mix run find_rdap.exs quimper.bzh Starting Expiration is 2022-04-26 12:44:48Z Using the cache ./iana-rdap-bootstrap.json RDAP bootstrap file for Domain Name System registrations published on 2022-04-22T16:00:01Z RDAP server for quimper.bzh is https://rdap.nic.bzh/ Retrieving RDAP info at https://rdap.nic.bzh/domain/quimper.bzh {"rdapConformance":["rdap_level_0", ...
Mais il existe évidemment une mise en œuvre de ce RFC dans tous les clients RDAP comme :
https://rdap-bootstrap.arin.net/bootstrap
,
qui lit la base IANA et envoie les redirections appropriées, quand
un client RDAP l'interroge.Date de publication du RFC : Mars 2022
Auteur(s) du RFC : T. Pauly, E. Kinnear (Apple), D. Schinazi (Google)
Chemin des normes
Réalisé dans le cadre du groupe de travail IETF quic
Première rédaction de cet article le 1 avril 2022
Le protocole QUIC fournit, comme son
concurrent TCP, un canal fiable d'envoi de données. Mais
certaines applications n'ont pas impérativement besoin de cette
fiabilité et se contenteraient très bien d'un service de type
datagramme. Ce nouveau RFC ajoute donc un nouveau
type de trame QUIC, DATAGRAM
, pour fournir un
tel service.
Dit comme cela, évidemment, ça semble drôle, de bâtir un service non fiable au-dessus du service fiable que rend QUIC. Mais cela permet plusieurs choses très intéressantes, notamment si deux machines qui communiquent ont besoin des deux types de service : elles pourront utiliser une seule session QUIC pour les deux.
Un petit rappel (mais relisez le RFC 9000
pour les détails) : dans une connexion QUIC sont envoyés des paquets
et chaque paquet QUIC contient une ou plusieurs trames. Chaque trame
a un type et ce type indique si la trame sera retransmise ou non en
cas de perte. Le service habituel (utilisé par exemple par
HTTP/3) se sert de trames de type
STREAM
, qui fournissent un service fiable (les
données perdues sont retransmises par QUIC). Mais toutes les
applications ne veulent pas d'une telle fiabilité ou, plus
précisement, ne sont pas prêtes à en payer le coût en termes de
performance. Ces applications sont celles qui préféraient utiliser
UDP (RFC 768) plutôt que TCP. Pour rajouter de la sécurité,
elles utilisaient DTLS (RFC 6347). Si
QUIC remplace TCP, comment remplacer
UDP+DTLS ?
D'ailleurs, faut-il le remplacer ? La section 2 du RFC donne les raisons suivantes :
Ces raisons sont particulièrement importantes pour le streaming audio/vidéo, pour les jeux en ligne, etc. Les datagrammes dans QUIC peuvent aussi être utiles pour un service de VPN. Là aussi, le VPN a besoin à la fois d'un canal fiable pour la configuration de la communication, mais peut se satisfaire d'un service de type datagramme ensuite (puisque les machines connectées via le VPN auront leur propre mécanisme de récupération des données perdues). Permettre la création de VPN au-dessus de QUIC est le projet du groupe de travail MASQUE.
Bien, normalement, maintenant, vous êtes convaincu·es de
l'intérêt des datagrammes au-dessus de QUIC. Pour en envoyer, il
faut toutefois que les deux parties qui communiquent soient
d'accord. C'est le rôle du paramètre de transport QUIC
max_datagram_frame_size
, à utiliser lors de
l'établissement de la session. (Ce paramètre est enregistré
à l'IANA.) S'il est absent, on ne peut pas envoyer de
datagrammes (et le partenaire coupe la connexion si on le fait quand
même). S'il est présent, il indique la taille maximale acceptée,
typiquement 65 535 octets. Sa valeur peut être stockée dans la
mémoire d'une machine, de façon à permettre son utilisation lors du
0-RTT (commencer une session QUIC directement sans perdre du temps
en négociations). Un paquet QUIC 0-RTT peut donc inclure des trames
de type DATAGRAM
.
Ah, justement, ce type DATAGRAM
. Désormais
enregistré
à l'IANA parmi les types de trames QUIC, il sert à indiquer
des trames qui seront traitées comme des datagrammes, et qui ont la
forme suivant :
DATAGRAM Frame { Type (i) = 0x30..0x31, [Length (i)], Datagram Data (..), }
Le champ de longueur est optionnel, sa présence est indiquée par le dernier bit du type (0x30, pas de champ Longueur, 0x31, il y en a un). S'il est absent, la trame va jusqu'au bout du paquet QUIC (rappelons qu'un paquet QUIC peut contenir plusieurs trames).
Maintenant qu'on a des datagrammes comme QUIC, comment les
utilise-t-on (section 5 du RFC) ? Rien d'extraordinaire,
l'application envoie une trame DATAGRAM
et
l'application à l'autre bout la recevra. Si la trame n'est pas
arrivée, l'émetteur ne le saura pas. La trame peut se retrouver avec
d'autres trames (du même type ou pas) dans un même paquet QUIC (et
plusieurs paquets QUIC peuvent se retrouver dans un même datagramme
IP). La notion de
ruisseau (stream) n'existe pas pour les
trames-datagrammes, si on veut pouvoir les démultiplexer, c'est à
l'application de se débrouiller. (Une version préliminaire de cette
extension à QUIC prévoyait un mécanisme de démultiplexage,
finalement abandonné.) Comme toutes les trames QUIC, les trames
DATAGRAM
sont protégées par la
cryptographie. Le service est donc équivalent à celui d'UDP+DTLS,
pas UDP seul.
Un service de datagrammes est non fiable, des données peuvent se
perdre. Mais si les trames de type DATAGRAM
ne
sont pas réémises (pas par QUIC, en tout cas), elles entrainent
néanmoins l'émission d'accusés de réception (RFC 9002, sections 2 et 3). Une bibliothèque QUIC peut ainsi
(mais ce n'est pas obligatoire) notifier l'application des
pertes. De la même façon, elle a la possibilité de dire à
l'application quelles données ont été reçues par le QUIC à l'autre
bout (ce qui ne garantit pas que l'application à l'autre bout les
aient bien reçues, si on a besoin d'informations sûres, il faut le
faire au niveau applicatif).
Comme l'émetteur n'estime pas
crucial que toutes les données arrivent, une optimisation possible
pour QUIC est que l'accusé de réception d'un paquet ne contenant que
des trames DATAGRAM
ne soit pas émis tout de
suite, il peut attendre le paquet suivant.
Cette extension existe déjà dans plusieurs mises en œuvre de QUIC, mais je ne l'ai pas testée.
Date de publication du RFC : Juin 2022
Auteur(s) du RFC : M. Nottingham (Fastly)
Chemin des normes
Réalisé dans le cadre du groupe de travail IETF httpbis
Première rédaction de cet article le 9 juin 2022
Ce RFC normalise enfin un en-tête HTTP pour permettre aux relais HTTP qui mémorisent les réponses reçues, les caches, d'indiquer au client comment ils ont traité une requête et si, par exemple, la réponse vient de la mémoire du relais ou bien s'il a fallu aller la récupérer sur le serveur HTTP d'origine.
Il existait déjà des en-têtes de débogage analogues mais
non-standards, variant aussi bien dans leur syntaxe que dans leur
sémantique. Le nouvel en-tête standard,
Cache-Status:
va, espérons-le, mettre de
l'ordre à ce sujet. Sa syntaxe suit les principes des en-têtes
structurés du RFC 9651. Sa valeur est donc une
liste de relais qui ont traité la requête, séparés par des
virgules, dans l'ordre des traitements. Le
premier relais indiqué dans un Cache-Status:
est donc le plus proche du serveur d'origine et le dernier relais
étant le plus proche de l'utilisateur. Voici un exemple avec un seul
relais, nommé cache.example.net
:
Cache-Status: cache.example.net; hit
et un exemple avec deux relais, OriginCache
puis CDN Company Here
:
Cache-Status: OriginCache; hit; ttl=1100, "CDN Company Here"; hit; ttl=545
La description complète de la syntaxe figure dans la section 2 de notre RFC. D'abord, il faut noter qu'un relais n'est pas obligé d'ajouter cet en-tête. Il le fait selon des critères qui lui sont propres. Le RFC déconseille fortement d'ajouter cet en-tête si la réponse a été générée localement par le relais (par exemple un 400 qui indique que le relais a détecté une requête invalide). En tout cas, le relais qui s'ajoute à la liste ne doit pas modifier les éventuels relais précédents. Chaque relais indique son nom, par exemple par un nom de domaine mais ce n'est pas obligatoire. Ce nom peut ensuite être suivi de paramètres, séparés par des point-virgules.
Dans le
premier exemple ci-dessus, hit
était un
paramètre du relais cache.example.net
. Sa
présence indique qu'une réponse était déjà mémorisée par le relais
et qu'elle a été utilisée (hit : succès, on a
trouvé), le serveur d'origine n'ayant pas été contacté. S'il y
avait une réponse stockée, mais qu'elle était rassise (RFC 9111, section 4.2) et qu'il a fallu la
revalider auprès du serveur d'origine, hit
ne
doit pas être utilisé.
Le deuxième paramètre possible est fwd
(pour
Forward) et il indique qu'il a fallu contacter le
serveur HTTP d'origine. Plusieurs raisons sont possibles pour cela,
entre autres :
bypass
, quand le relais a été configuré pour
que cette requête soit systématiquement relayée,method
parce que la méthode HTTP
utilisée doit être relayée (c'est typiquement le cas d'un
PUT
, cf. RFC 9110,
section 9.3.4),miss
, un manque, la ressource n'était
pas dans la mémoire du relais (notez qu'il existe aussi deux
paramètres plus spécifiques, uri-miss
et
vary-miss
),stale
, car la ressource était bien dans
la mémoire du relais mais, trop ancienne et rassise, a dû être revalidée (RFC 9111, section 4.3) auprès du
serveur d'origine.Ainsi, cet en-tête :
Cache-Status: ExampleCache; fwd=miss; stored
indique que l'information n'était pas dans la mémoire de
ExampleCache
et qu'il a donc dû faire suivre au
serveur d'origine (puis qu'il a stocké cette information dans sa
mémoire, le paramètre stored
).
Autre paramètre utile, ttl
, qui indique
pendant combien de temps l'information peut être gardée (cf. RFC 9111, section 4.2.1), selon les
calculs du relais. Quant à detail
,
il permet de donner des informations supplémentaires, de manière non
normalisée (la valeur de ce paramètre ne peut être interprétée que
si on connait le programme qui l'a produite).
Les paramètres sont enregistrés à l'IANA, dans un nouveau registre, et de nouveaux paramètres peuvent y être ajoutés, suivant la politique « Examen par un expert » du RFC 8126. La recommandation est de n'enregistrer que des paramètres d'usage général, non spécifiques à un programme particulier, sauf à nommer le paramètre d'une manière qui identifie clairement sa spécificité.
Questions mises en œuvre, Squid sait
renvoyer cet en-tête (cf. le code qui commence à SBuf
cacheStatus(uniqueHostname());
dans le fichier
src/client_side_reply.cc
). Ce code n'est pas
encore présent dans la version 5.2 de Squid, qui n'utilise encore que
d'anciens en-têtes non-standards, il faut attendre de nouvelles versions :
< X-Cache: MISS from myserver < X-Cache-Lookup: NONE from myserver:3128 < Via: 1.1 myserver (squid/5.2)
Je n'ai pas regardé les autres logiciels.
Date de publication du RFC : Mars 2022
Auteur(s) du RFC : J. Kristoff (Dataplane.org), D. Wessels (Verisign)
Réalisé dans le cadre du groupe de travail IETF dnsop
Première rédaction de cet article le 15 avril 2022
Ce nouveau RFC décrit les exigences opérationnelles pour le DNS, plus précisement pour son protocole de transport TCP. Le RFC 7766 décrivait la norme technique, et s'imposait donc aux programmeur·ses, ce RFC 9210 est plus opérationnel et concerne donc ceux et celles qui installent et configurent les serveurs DNS et les réseaux qui y mènent.
Les messages DNS sont historiquement surtout transportés sur UDP. Mais le DNS a toujours permis TCP et, de nos jours, il est fréquemment utilisé notamment en conjonction avec TLS (cf. RFC 7858). Il est d'autant plus important que TCP marche bien qu'il est parfois nécessaire pour certains usages, par exemple pour des enregistrements de grande taille (ce qui arrive souvent avec DNSSEC). Une mise en œuvre du DNS doit donc inclure TCP, comme clarifié par le RFC 7766 (les RFC précédents n'étaient pas sans ambiguité). Mais cela ne signifiait pas forcément que les serveurs DNS avaient activé TCP ou que celui-ci marchait bien (par exemple, une stupide middlebox pouvait avoir bloqué TCP). Ce nouveau RFC 9210 ajoute donc que l'obligation d'avoir TCP s'applique aussi aux serveurs effectifs, pas juste au logiciel.
DNS sur TCP a une histoire compliquée (section 2 du RFC). Franchement, le fait que le DNS marche sur TCP aussi bien que sur UDP n'a pas toujours été clairement formulé, même dans les RFC. Et, en dehors de l'IETF, beaucoup de gens ont raconté n'importe quoi dans des articles et des documentations, par exemple que TCP n'était utile qu'aux transferts de zone, ceux normalisés dans le RFC 5936. Cette légende a été propagée par certains auteurs (comme Cheswick et Bellovin dans leur livre Firewalls and Internet Security: Repelling the Wily Hacker) ou par des gens qui ne mesuraient pas les limites de leurs compétences DNS comme Dan Bernstein. Pourtant, TCP a toujours été nécessaire, par exemple pour transporter des données des grande taille (que DNSSEC a rendu bien plus fréquentes). Le RFC 1123, en 1989, insistait déjà sur ce rôle. Certes, EDNS (RFC 6891) permettait déjà des données de taille supérieure aux 512 octets de la norme originelle. Mais il ne dispense pas de TCP. Par exemple, des données de taille supérieure à 1 460 octets (le maximum qui peut tenir avec le MTU typique) seront fragmentées et les fragments, hélas, ne passent pas partout sur l'Internet. (Cf. le « DNS Flag Day » de 2020 et lire « DNS XL » et « Dealing with IPv6 fragmentation in the DNS ».) Et la fragmentation pose également des problèmes de sécurité, voir par exemple « Fragmentation Considered Poisonous ».
Bon, en pratique, la part de TCP reste faible mais elle augmente. Mais, trop souvent, le serveur ne répond pas en TCP (ou bien ce protocole est bloqué par le réseau), ce qui entraine des tas de problèmes, voir par exemple le récit dans « DNS Anomalies and Their Impacts on DNS Cache Servers ».
Notons enfin que TCP a toujours permis que plusieurs requêtes se succèdent sur une seule connexion TCP, même si les premières normes n'étaient pas aussi claires qu'il aurait fallu. L'ordre des réponses n'est pas forcément préservé (cf. RFC 5966), ce qui évite aux requêtes rapides d'attendre le résultat des lentes. Et un client peut envoyer plusieurs requêtes sans attendre les réponses (RFC 7766). Par contre, la perte d'un paquet va entrainer un ralentissement de toute la connexion, et donc des autres requêtes (QUIC résout ce problème et il y a un projet de DNS sur QUIC).
Vous pouvez tester qu'un serveur DNS accepte de faire passer
plusieurs requêtes sur une même connexion TCP avec
dig et son option
+keepopen
(qui n'est pas activée par
défaut). Ici, on demande à ns4.bortzmeyer.org
les adresses IP de www.bortzmeyer.org
et
mail.bortzmeyer.org
:
% dig +keepopen +tcp @ns4.bortzmeyer.org www.bortzmeyer.org AAAA mail.bortzmeyer.org AAAA
Vous pouvez vérifier avec tcpdump qu'il n'y
a bien eu qu'une seule connexion TCP, ce qui ne serait pas le cas
sans +keepopen
.
Les exigences opérationnelles pour le DNS sur TCP figurent dans la section 3 du RFC. Il est désormais obligatoire non seulement d'avoir la possibilité de TCP dans le code mais en outre que cela soit activé partout. (En termes du RFC 2119, on est passé de SHOULD à MUST.)
La section 4 discute de certaines considérations opérationnelles que cela pourrait poser. D'abord, certains serveurs ne sont pas joignables en TCP. C'était déjà mal avant notre RFC mais c'est encore pire maintenant. Mais cela arrive (ce n'est pas forcément la faute du serveur, cela peut être dû à une middlebox boguée sur le trajet). Les clients doivent donc être préparés à ce que TCP échoue (de toute façon, sur l'Internet, tout peut toujours échouer). D'autre part, diverses attaques par déni de service sont possibles, aussi bien contre le serveur (SYN flooding, contre lequel la meilleure protection est l'utilisation de cookies, cf. RFC 4987), que contre des machines tierces, le serveur étant utilisé comme réflecteur.
Aussi bien le client DNS que le serveur n'ont pas des ressources illimitées et doivent donc gérer les connexions TCP. Dit plus brutalement, il faut être prêt à couper les connexions qui semblent inutilisées. Bien sûr, il faut aussi laisser les connexions ouvertes suffisamment longtemps pour amortir leur création sur le plus grand nombre de requêtes possibles, mais il y a des limites à tout. (Le RFC suggère une durée maximale dans les dizaines de secondes, pouvant être abaissée à quelques secondes pour les serveurs très chargés.)
TCP est particulièrement intéressant pour le DNS quand TLS est inséré (RFC 7858). Mais il va alors consommer davantage de temps de CPU et, dans certains cas, l'établissement de connexion sera plus lent.
Le RFC donne quelques conseils quantitatifs (à ne pas appliquer aveuglément, bien sûr). Un maximum de 150 connexions TCP simultanées semble raisonnable pour un serveur ordinaire, avec un maximum de 25 par adresse IP source. Un délai de garde après inactivité de 10 secondes est suggéré. Par contre, le RFC ne propose pas de valeur maximale au nombre de requêtes par connexion TCP, ni de durée maximale à une connexion.
Les fanas des questions de sécurité noteront que les systèmes de journalisation et de surveillance peuvent ne pas être adaptés à TCP. Par exemple, un méchant pourrait mettre la requête DNS dans plusieurs segments TCP et donc plusieurs paquets IP. Un système de surveillance qui considérerait qu'une requête = un paquet serait alors aveugle. Notez qu'un logiciel comme dnscap a pensé à cela, et réassemble les paquets. Mais il est sans doute préférable, de nos jours, de se brancher sur le serveur, par exemple avec dnstap, notamment pour éviter de faire le réassemblage deux fois. (Ceux qui lisent mes articles depuis longtemps savent que je donnais autrefois le conseil inverse. Mais le déploiement de plus en plus important de TCP et surtout de TLS impose de changer de tactique.)
Date de publication du RFC : Juin 2022
Auteur(s) du RFC : M. Nottingham (Fastly), P. Sikora (Google)
Chemin des normes
Réalisé dans le cadre du groupe de travail IETF httpbis
Première rédaction de cet article le 14 juin 2022
Le protocole HTTP, qui est à la base du Web, n'est pas forcément de bout
en bout, entre client et serveur. Il y a souvent passage par un
relais et ce relais a
parfois des choses à signaler au client HTTP, notamment en cas
d'erreur. Ce RFC
spécifie le champ d'en-tête Proxy-Status
pour
cela.
La norme HTTP, le RFC 9110 décrit ces relais, ces intermédiaires, dans sa section 3.7. On en trouve fréquemment sur le Web. Il y a depuis longtemps des codes d'erreur pour eux, comme 502 si le serveur d'origine répond mal et 504 pour les cas où il ne répond pas du tout. Mais ce n'est pas forcément assez précis, d'où le nouveau champ dans l'en-tête (ou dans le pied). Il utilise la syntaxe des champs structurés du RFC 9651. Voici un exemple :
Proxy-Status: proxy.example.net; error="http_protocol_error"; details="Malformed response header: space before colon", "Example CDN"
Cet exemple se lit ainsi : le premier (en partant du serveur
d'origine) s'identifie comme proxy.example.net
et il signale que le serveur d'origine n'avait pas bien lu le RFC 9112. Puis la réponse est passée par un autre
intermédiaire, qui s'identifie comme "Example
CDN"
(l'identificateur n'est pas forcément un
nom de domaine), et n'a rien de particulier à
raconter. Le champ Proxy-Status
est désormais
dans
le registres des champs d'en-tête (ou de pied).
Vous avez vu dans l'exemple ci-dessus le paramètre
error
. Il peut s'utiliser, par exemple, avec le
code de retour 504 :
HTTP/1.1 504 Gateway Timeout Proxy-Status: foobar.example.net; error=connection_timeout
Ici, le relais foobar.example.net
n'a eu aucune
réponse du serveur d'origine (ou, plus rigoureusement, du serveur
qu'il essayait de contacter, qui peut être un autre
intermédiaire).
Mais il existe d'autres paramètres possibles, comme :
next-hop
: nom ou adresse du serveur
contacté, par exemple Proxy-Status: cdn.example.org;
next-hop=backend.example.org:8001
.next-protocol
: protocole
(ALPN,
RFC 7301) utilisé avec
le serveur, par exemple Proxy-Status:
"proxy.example.org"; next-protocol=h2
pour du HTTP/2
(RFC 9113).received-status
: le code de retour du
serveur, comme dans Proxy-Status: ExampleCDN;
received-status=200
, pour un cas où tout s'est bien
passé.Et on peut définir de nouveaux paramètres par la procédure d'examen par un expert (RFC 8126).
Le paramètre error
prend comme valeur un
type d'erreur. Il en existe
plusieurs, chacun avec un code de retour recommandé dont (je
ne les indique pas tous, ils sont très nombreux !) :
dns_timeout
(pour le code de retour
504) et dns_error
(code 502) : c'est la faute du
DNS. Le second type permet en outre
d'indiquer le paramètre rcode
(code de retour
DNS, comme NXDOMAIN
) et le paramètre
info-code
, le code étendu du RFC 8914.connection_timeout
ou connection_refused
.destination_ip_prohibited
: le
pare-feu ne veut pas.tls_protocol_error
: là, c'est
TLS qui ne veut pas.tls_certificate_error
:
certificat du serveur problématique, par
exemple expiré.http_protocol_error
: la réponse du
serveur n'était pas du bon HTTP.proxy_internal_error
: le relais a un
problème interne.Là aussi, on peut enregistrer de nouveaux types avec la procédure d'examen par un expert du RFC 8126.
La section 4 du RFC détaille les questions de sécurité. Comme
toute information, Proxy-Status
peut aider un
attaquant, par exemple en lui donnant des idées sur comment joindre
directement un intermédiaire. Pour cette raison, les logiciels
doivent fournir un moyen de contrôler l'ajout (ou pas) de
Proxy-Status
, qu'on peut aussi n'inclure que
dans certains cas. Notez aussi qu'un intermédiaire peut mentir (ou
se tromper) et que le Proxy-Status
ne vaut donc
que ce que vaut l'intermédiaire.
Je n'ai pas de liste des logiciels qui mettent en œuvre ce champ Proxy-Status
.
Date de publication du RFC : Juin 2022
Auteur(s) du RFC : M. Nottingham
Réalisé dans le cadre du groupe de travail IETF httpbis
Première rédaction de cet article le 10 juin 2022
Aujourd'hui, la grande majorité des API accessibles via le réseau fonctionnent au-dessus de HTTP. Ce nouveau RFC, qui remplace le RFC 3205, décrit les bonnes pratiques pour la conception de telles API, notamment pour les protocoles IETF bâtis sur HTTP, comme DoH ou RDAP.
Il y a plein d'applications qui fonctionnent au-dessus de HTTP. Ce nouveau RFC se concentre sur celles qui sont d'usage général et qui ont plusieurs mises en œuvre et déploiements. (Si vous faites un service centralisé qui n'a qu'un seul déploiement de son API spécifique, ce RFC ne va pas forcément être pertinent pour vous.) Si vous avez déjà lu le RFC 3205, il faudra tout recommencer, les changements sont nombreux. Ces applications utilisant HTTP sont parfois qualifiées de REST mais, en toute rigueur, toutes ne suivent pas rigoureusement les principes de REST. Notez aussi un sous-ensemble, CRUD, pour les applications dont l'essentiel du travail est de créer/supprimer/gérer des objets distants.
Normalement, HTTP avait été conçu pour le Web et ses usages. Mais on voit aujourd'hui de très nombreuses API réseau être fondées sur HTTP pour diverses raisons :
Mais tout n'est pas forcément rose et HTTP peut ne pas être bien adapté à ce qu'envisage le développeur de l'API. Et cette développeuse peut faire des erreurs lors de la conception de l'API, erreurs que ce RFC vise à éviter.
En effet, quand on développe une API sur HTTP, il y a plusieurs décisions à prendre :
La section 2 précise l'applicabilité de ce RFC. Il concerne les
protocoles qui utilisent HTTP (ports 80 ou
443, plans d'URI http:
ou
https:
). Ceux qui utiliseraient une version
modifiée de HTTP ne comptent pas, et cette pratique est d'ailleurs
déconseillée, puisque ces versions modifiées feraient probablement
perdre les avantages d'utiliser HTTP, notamment la réutilisation des
logiciels et infrastructures existants.
Section 3, maintenant. Quelles sont les caractéristiques
importantes de HTTP, qui gouvernent ce que peuvent faire les
applications qui l'utilisent ? D'abord, sa sémantique très
générale : on peut tout faire avec HTTP. Notammment, HTTP est
indépendant du type de ressources sur lesquelles il agit. Ainsi, des
composants HTTP génériques (bibliothèques, serveurs, relais) peuvent
être développés et déployés pour des applications très différentes,
même des applcations qui n'existent pas encore. (Voilà d'alleurs
pourquoi la section précédente insistait sur le fait q'il ne faut
pas modifier HTTP.) Plus subtile serait l'erreur qui consisterait à
spécifier un certain profil de HTTP, en restreignant ce que HTTP
peut faire ou pas (« la réponse à un POST
doit
être 201 »). Une telle restriction, là encore, empêcherait
d'utiliser cerrtains composants génériques, en faisant perdre à HTTP
de sa généralité.
Une autre erreur courante est de s'attribuer tout ou partie de
l'espace de nommage fourni par les liens
hypertextes. C'est par exemple le cas lorsqu'une
application estime qu'elle peut contrôler tout le chemin dans l'URI
et décider que /truc/machin
est forcément à
elle (RFC 8820). Cela complique le
déploiement, par exemple si on veut installer cette application sous
/chose
et excusivement sous ce chemin
(cf. section 4.4). L'application devrait au contraire permettre de
la souplesse et utiliser les possibilités qu'offre le système de
liens (RFC 8288).
Enfin, HTTP dispose de nombreuses possibilités comme le multiplexage que permettent HTTP/2 (RFC 9113) et HTTP/3 (RFC 9114), l'intégration avec TLS, la possibilité de relayage, la négociation de contenu, la disponibilité de nombreux clients, et l'application qui utilise HTTP doit donc veiller à ne pas casser cet écosystème, et en tout cas à ne pas réinventer la roue, alors que HTTP offre déjà de nombreuses solutions éprouvées.
Bref, compte-tenu de tout cela, comment faire pour bien utiliser HTTP dans sa nouvelle application ? La section 4 est là pour répondre à cette question.
D'abord, bien définir la dépendance de l'application à HTTP, en donnant comme référence RFC 9110 (et surtout pas une version spécifique de HTTP, toujours afin de profiter au maximum de l'écosystème existant). On notera quand même que DoH (RFC 8484) impose (section 5.2 de son RFC) au moins HTTP/2, pour être sûr d'avoir du multiplexage. Notre RFC permet explicitement ce genre d'exceptions.
Le RFC recommande également, quand on montre un dialogue HTTP
titre d'exemple, d'utiliser plutôt les conventions de HTTP/1 (RFC 9112), plus lisibles. Donc, par exemple,
GET /truc HTTP/1.1
plutôt que le
:method = GET :path = /resource
de
HTTP/2. C'est ce que fait curl :
% curl -v http://www.example > GET / HTTP/2 > Host: www.example > user-Agent: curl/7.68.0 > accept: */* > < HTTP/2 200 < content-Type: text/plain < content-Length: 13
On l'a dit, il ne faut pas modifier le comportement de base de HTTP. Ce qu'on peut faire, par contre :
Et le client ? L'application qui utilise HTTP ne devrait pas exiger de comportement trop spécifique du client ; idéalement, un navigateur Web normal devrait pouvoir interagir avec l'application. On peut par exemple s'appuyer sur les principes FETCH. Il est également préférable que l'application qui va utiliser HTTP soit claire sur le traitement attendu pour les redirections HTTP, ou pour les cookies (RFC 6265), et rappeler que la vérification des certificats doit se faire selon les principes de la section 4.3.4 du RFC 9110.
Le client doit, idéalement, pouvoir se configurer avec uniquement
un URL. (Par exemple, un serveur DoH est annoncé ainsi, comme
https://doh.bortzmeyer.fr/
, la seule
information dont vous avez besoin pour l'utiliser.) Et si on ne
connait qu'un nom de domaine ? La solution du chemin d'URL fixe
qu'on s'alloue
(« obligatoirement /app
») étant interdite (RFC 8820), il y a le choix :
Et le plan d'URI (le premier composant de l'URI) ?
http:
et surtout https:
sont évidemment recommandés mais on peut aussi choisir un plan
spécifique. Cela va évidemment rendre l'application inutilisable
par un navigateur Web ordinaire. Certains navigateurs permettent
d'enregistrer un mécanisme de gestion de ces plans non standards
(comme le registerProtocolHandler()
du WHATWG) mais cela ne
marche pas partout. Et on aura le même problème avec tout
l'écosystème logiciel de HTTP. Bref, utiliser un plan autre que
http:
ou https:
fera
perdre une bonne partie des avantages qu'il y avait à utiliser le
protocole HTTP. D'autres problèmes se poseront comme l'impossibilité
d'utiliser le concept d'origine (RFC 6454) par
exemple dans la Same Origin
Policy, ou comme d'autres fonctions utiles
de HTTP (cookies, authentification, mémorisation
- RFC 9111, HSTS - RFC 6797, etc). Si vous tenez encore, après tout ça, à créer
un plan à vous, consultez le RFC 7595.
Et les ports ? HTTP utilise par défaut les
ports 80 pour le trafic en clair et 443 pour le traffic
chiffré. Utiliser un autre port est possible
(https://machin.example:666
) mais rend le
trafic de l'application distinguable, ce qui peut être gênant pour
la vie privée. (C'est un des choix de conception de DoH que d'utiliser HTTPS sur le
port 443, pour ne pas être distinguable, et donc être plus difficile
à filtrer.) Le RFC 7605 donne des détails sur
ce choix des ports.
Maintenant, quelles méthodes HTTP utiliser ? Le RFC exige que les
applications utilisant HTTP se servent uniquement des méthodes enregistrées,
comme GET
ou PUT
. Certes,
une procédure existe pour enregistrer de nouvelles méthodes mais
l'IETF
insiste que ces nouvelles méthodes doivent être génériques, et non
pas limitées aux besoins d'une seule application. (Le RFC 4791 avait créé des méthodes spécifiques, mais c'était
avant. C'est maintenant interdit.)
Donc, pas de méthodes nouvelles. Mais quelle(s) méthode(s)
utiliser ? GET
est le choix le plus
évident. Cette méthode est idempotente (et
permet donc, entre autres, la mémorisation), et a une sémantique
simple et compréhensible. Elle a quelques limites (comme le fait que
tous les éventuels paramètres doivent être dans l'URL, ce qui peut
nécessiter un encodage spécial, et peut empêcher des paramètres de
grande taille) mais rien de bien grave. Si c'est trop gênant pour
une application donnée, il ne reste plus qu'à utiliser
POST
.
Et pour récupérer des métadonnées sur le
service ? Le RFC note que la méthode OPTIONS
n'est pas très pratique, par exemple parce qu'elle ne permet pas de
donner comme documentation un simple URL (la méthode par défaut
étant GET
). Il recommande plutôt un URL dans
/.well-known
(RFC 8615),
en créant un nouveau nom, ou
bien avec les URL host-meta
(RFC 6415). Pour des métadonnées sur une ressource particulière,
il est recommandé d'utiliser les liens (RFC 8288). Le RFC
note que, dans ce dernier cas, l'en-tête Link:
marche même avec la méthode HEAD
donc pas
besoin de récupérer la ressource pour avoir des informations sur ses
métadonnées.
Et les codes de retour HTTP comme 403 ou 404, comment les utiliser ? D'abord, une application qui utilise HTTP n'a pas forcément un complet contrôle sur ces codes de retour, qui peuvent être générés par des composants logiciels différents. Donc, le client HTTP doit se méfier, le code reçu n'est pas forcément significatif de l'application. Ensuite, une application peut avoir davantage de messages différents qu'il n'existe de codes de retour HTTP, ce qui peut pousser à de mauvaises pratiques, comme l'utilisation de codes de retour non standard, ou commme l'utilisation de codes certes standard mais utilisés d'une manière très éloignée de ce qui était prévu. Bref, le RFC conseille de découpler les erreurs applicatives des erreurs HTTP, de ne pas chercher à tout prix un code de retour HTTP pour chaque erreur applicative, et de ne pas hésiter à utiliser les codes de retour génériques (comme 500, pour « quelque chose ne va pas dans le serveur »). Pour envoyer des informations plus détaillées sur l'erreur, il est préférable d'utiliser les techniques du RFC 7807. Autre avertissement du RFC, les raisons envoyées par le serveur après un code de retour (404 File not found) ne sont pas significatives. Dans certains cas (message HTTP dans un message HTTP), elles ne sont pas transmises du tout, contrairement au code de retour, la seule information sur laquelle on peut compter. L'application ne doit donc pas espérer que le client recevra ces raisons.
Autre difficulté pour le concepteur ou la conceptrice
d'applications utilisant HTTP, les redirections. Il y a quatre
redirections différentes en HTTP, chacune pouvant être temporaire ou
définitive, et permettant de changer de méthode ou pas (une requête
POST
indiquant une redirection suivie d'une
requête GET
par le client). On a donc :
Et il faut donc réfléchir un peu avant de choisir un code de redirection (le RFC privilégie 301 et 302, plus souples).
Et les champs dans l'en-tête ? Une application a souvent envie d'en ajouter, que ce soit dans la requête ou dans la réponse. Mais le RFC n'est pas très chaud, demandant qu'on mette les informations plutôt dans l'URL ou dans le corps du message HTTP. Si on crée de nouveaux en-têtes, en théorie (c'est très théorique…), il faut les enregistrer à l'IANA (RFC 9110, section 16.3). Si ces en-têtes ont une structure, il est très recommandé qu'elles suivent les règles du RFC 9651.
Et le corps du message, justement ? L'application doit spécifier quel format est attendu. C'est souvent JSON (RFC 8259) mais cela peut être aussi XML, CBOR (RFC 8949), etc.
Une des grandes forces d'HTTP est la possibilité de mémorisation,
décrite en détail dans le RFC 9111. La
mémorisation améliore les performances, rend le service moins
sensible aux perturbations, et permet le passage à
l'échelle. Les applications qui utilisent HTTP ont donc
tout intérêt à permettre et à utiliser cette mémorisation. Il est
donc recommandé d'indiquer dans la réponse une durée de vie, de
préférence avec Cache-Control: max-age=…
ou
bien, si c'est nécessaire, d'indiquer explicitement que la réponse
ne doit pas être mémorisée (Cache-Control:
no-store
).
Un autre avantage pour une application d'utiliser HTTP est l'existence d'un cadre général pour l'authentification (RFC 9110, section 11). Attention, certains mécanismes ne doivent être utilisés qu'au-dessus d'HTTPS, comme la basic authentication du RFC 7617. HTTPS permet également d'utiliser les certificats client pour l'authentification. (Attention, avec TLS ≤ 1.2, ces certificats, qui contiennent des données personnelles, sont transmis en clair.)
Conséquence de l'utilisation de HTTP, l'application est
utilisable via un navigateur Web. Cela peut
être vu comme un avantage (tout le monde a un navigateur Web sous la
main) ou comme un inconvénient (si la sémantique de l'application ne
permet pas réellement un usage pratique depuis un navigateur). Mais
quoi qu'on en pense, l'application sera accessible aux navigateurs,
et il est donc important de s'assurer que cela ne provoquera pas de
problème. Par exemple, si on peut changer un état avec une requête
POST
, l'application pourrait être attaquée
assez facilement par CSRF. Si l'application
tire une partie des données qu'elle renvoie en réponse d'une source
que l'attaquant peut contrôler, on risque le XSS. Il est donc recommandé,
même si l'application n'est pas prévue pour être utilisée par un
navigateur, de suivre les mêmes règles de développement sécurisé que
si elle devait être accédée depuis un navigateur, notamment :
X-Content-Type-Options: nosniff
,Referrer-Policy:
,HttpOnly
sur les
cookies (RFC 6265, section 5.2.6).Voici un exemple d'une réponse suivant ces principes :
HTTP/1.1 200 OK Content-Type: application/example+json X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Cache-Control: max-age=3600 Referrer-Policy: no-referrer
Il reste à régler la question des frontières de l'application. Le plus simple pour l'application est d'avoir son propre nom de domaine et donc une origine (RFC 6454) unique. Cela simplifie par exemple des problèmes comme les cookies. Mais cela complique le déploiement, empêchant de mettre plusieurs applications derrière le même nom. Le RFC conseille donc plutôt de concevoir des applications pouvant coexister avec d'autres applications sous le même nom (RFC 8820).
Un mot sur la sécurité pour finir (section 6
du RFC). D'abord, une application qui utilise HTTP va évidemment
hériter des questions de sécurité générale de HTTP, comme détaillées
dans la section 17 du RFC 9110. Vu le
caractère sensible des données traitées par beaucoup d'applications,
le RFC recommande l'utilisation de HTTPS. Mais il développe aussi
la question de la vie
privée. HTTP est très bavard et le serveur en apprend
beaucoup, souvent beaucoup trop, sur son client. Ainsi, les
cookies, l'adresse
IP source, les ETags
, les tickets
de session TLS sont très utiles au serveur qui voudrait
suivre un client. Et le RFC rappelle que HTTP donne assez
d'informations « auxiliaires » pour pouvoir reconnaitre un client
(ce qu'on nomme le fingerprinting). Bref, le
maintien de son intimité va être aussi difficile que sur le Web.
Ce RFC remplace l'ancien RFC 3205. Comme le note l'annexe A de notre RFC, il y a trop de changements pour les lister ; ce document est très différent de son prédécesseur (qui date de 2002 !).
Voyons maintenant quelques exemples d'application utilisant
HTTP. Dans le monde IETF, il y a évidemment
RDAP (RFC 7480, RFC 9082 et RFC 9083). RDAP
suit bien les principes de ce RFC. Par exemple, les chemins d'URL
comme /domain
ou /ip
ne
sont pas forcément à la « racine » du serveur HTTP. Autre exemple,
DoH (RFC 8484), également fidéle (heureusement !) aux
recommandations de l'IETF. Notez que ces recommandations laissent
des choix. Ainsi, lorsque le nom de domaine cherché n'est pas
trouvé, RDAP renvoie le code 404 (RFC 7480,
section 5.3) alors que DoH préfère renvoyer un 200 (le serveur HTTP
a bien été joignable et a bien répondu), gardant le signal de
non-existence uniquement dans la réponse DNS (RFC 8484,
section 4.2.1) transportée sur HTTP (l'argument est que DoH ne fait
que transporter les requêtes d'un autre protocole, contrairement à
RDAP).
J'ai parlé plus haut de la possibilité d'utilisation d'un navigateur Web ordinaire pour accéder aux applications utilisant HTTP. Mais comme ces applications envoient souvent des données structurées en JSON, il faut un navigateur qui gère bien le JSON. Et c'est justement ce que fait Firefox, qui sait l'afficher de manière pratique :
Terminons avec quelques exemples d'API « finales » (donc pas le sujet
principal du RFC,
qui parle de protocoles IETF).
Commençons modestement par l'API du DNS
looking glass. A priori, elle suit tous les
principes de ce RFC. En tout cas, elle essaie. Mais si vous
constatez des différences avec le RFC, n'hésitez pas à faire un
rapport.
Autre API intéressante, celle des sondes RIPE Atlas. Elle utilise
toutes les possibilités de HTTP, notamment les multiples méthodes
(DELETE
pour supprimer une mesure en cours, par
exemple). J'aurais juste trouvé plus logique d'utiliser
PUT
au lieu de POST
pour
créer une mesure. L'API de Mastodon
(cf. sa
documentation) est
encore plus incohérente, utilisant POST
pour
créer un pouète, mais PUT
pour le mettre à jour.
Date de publication du RFC : Juin 2022
Auteur(s) du RFC : C. Krasic (Netflix), M. Bishop (Akamai Technologies), A. Frindell (Facebook)
Chemin des normes
Réalisé dans le cadre du groupe de travail IETF quic
Première rédaction de cet article le 7 juin 2022
QPACK, normalisé dans ce RFC est un mécanime de compression des en-têtes HTTP, prévu spécifiquement pour HTTP/3. Il est proche du HPACK de HTTP/2, mais adapté aux particularités de QUIC.
Car le HPACK du RFC 7541 a un défaut qui n'était pas un problème en HTTP/2 mais le devient avec HTTP/3 : il supposait que les trames arrivent dans l'ordre d'émission, même si elles circulent sur des ruisseaux différents. Ce n'est plus vrai en HTTP/3 qui, grâce à son transport sous-jacent, QUIC, a davantage de parallélisme, et où une trame peut en doubler une autre (si elles n'étaient pas dans le même ruisseau). QPACK ressemble à HPACK, mais en ayant corrigé ce problème. (Au fait, ne cherchez pas ce que veut dire QPACK, ce n'est pas un acronyme.)
Donc, le principe de QPACK. Comme HPACK, on travaille avec deux
tables, qui vont associer aux en-têtes HTTP un nombre (appelé
index). L'une des tables est statique, définie dans ce RFC (annexe
A) et donc identique pour tout le monde. L'autre est dynamique et
construite par un échange de messages transmis dans des ruisseaux
QUIC. Le fonctionnement avec la table statique est simple :
l'encodeur regarde si ce qu'il veut écrire est dans la table, si
oui, il le remplace par l'index. Le décodeur, recevant un index, le
remplace par le contenu de la table. Par exemple, l'en-tête HTTP
if-none-match
(RFC 7232,
section 3.2) est dans la table, index 9. L'encodeur remplacera donc
un if-none-match
par 9 (12 octets de gagnés si
tout était sous forme de caractères de 8 bits, mais peut-être un peu
plus ou un peu moins, avec l'encodage de QPACK), et le décodeur fera
l'inverse.
J'ai un peu simplifié en supposant que la table ne contenait que
les noms des en-têtes. Elle peut aussi contenir leur valeur si
celle-ci est très courante. Ainsi, accept:
application/dns-message
est dans la table, index 30, vu
son utilisation intensive par DoH (RFC 8484). Même chose
pour content-type: text/html;charset=utf-8
à
l'index 52.
La table dynamique est évidemment bien plus complexe. Encodeur (celui qui comprime) et décodeur doivent cette fois partager un état. En outre, le parallélisme inhérent à QUIC fait qu'un message d'ajout d'une entrée dans la table pourrait arriver après le message utilisant cette entrée. QPACK fonctionne de la façon suivante :
QUIC a un système de contrôle de flux, et des inconvénients peuvent en résulter, par exemple un blocage des messages de contrôle de la table. Pour éviter tout blocage, un encodeur peut n'utiliser que des entrées de la table qui ont déjà fait l'objet d'un accusé de réception. Il comprimera moins mais ne risquera pas d'être bloqué.
Le RFC détaille les précautions à prendre pour éviter l'interblocage. Ainsi, les messages modifiant la table risquent d'être bloqués par ce système alors que le récepteur n'autorise pas de nouvelles trames tant qu'il n'a pas traité des trames qui ont besoin de ces nouvelles entrées dans la table dynamique. L'encodeur a donc pour consigne de ne pas tenter de modifier la table s'il ne lui reste plus beaucoup de « crédits » d'envoi de données. (D'une manière générale, quand il y a des choses compliquées à faire, QPACK demande à l'encodeur de les faire, le décodeur restant plus simple.)
L'encodage des messages QPACK est spécifié dans la section 4. QPACK utilise deux ruisseaux QUIC unidirectionnels, un dans chaque direction. Ils sont enregistrés à l'IANA.
Notez aussi qu'il y a deux instructions d'ajout d'une entrée dans
la table dynamique, une qui ajoute une valeur litérale (comme « ajoute
accept-language: fr
») et une qui ajoute une
valeur exprimée sous forme d'une référence à une entrée d'une table
(qui peut être la statique ou la dynamique). Par exemple, comme
accept-language
est dans la table statique, à
l'index 72, on
peut dire simplement « ajoute 72: fr ». Encore quelques octets
gagnés.
Dans la trame SETTINGS
de HTTP/3, deux
paramètres concernent spécialement QPACK, pour indiquer la taille
maximale de la table dynamique, et le nombre maximal de ruisseaux
bloqués. Ils sont placés dans un registre IANA.
Quelques mots sur la sécurité : dans certaines conditions, un observateur peut obtenir des informations sur l'état des tables (cf. l'attaque CRIME) même s'il ne peut déchiffrer les données protégées par TLS, celui-ci ne masquant pas la taille. Bien sûr, on pourrait remplir avec des données bidons mais cela annulerait l'avantage de la compression. La section 7 du RFC donne quelques idées sur des mécanismes de limitation du risque.
L'annexe A du RFC spécifie la table statique et ses 98
entrées. Elle a été composée à partir de l'analyse de trafic HTTP en
2018. L'ordre des entrées n'est pas arbitraire : vu comment sont
représentés les entiers, donc les index, dans QPACK, les entrées les
plus fréquentes sont en premier, car QPACK utilise moins de bits pour
les nombres les plus petits. Notez aussi que cette table comprend
les en-têtes HTTP « classiques », comme
content-length
ou
set-cookie
mais aussi ce que
HTTP/2 appelle les « pseudo-en-têtes », qui
commencent par deux-points. C'est par exemple
le cas de la méthode HTTP (GET
,
PUT
, etc), notée :method
ou, dans les réponses, du code de retour, noté
:status
(tiens, la table statique a une entrée
pour le code 403 mais pas pour le 404).
Si vous envisagez de programmer QPACK, l'annexe B contient des exemples de dialogue entre encodeur et décodeur, et l'annexe C du pseudo-code pour l'encodeur.
Date de publication du RFC : Mars 2022
Auteur(s) du RFC : G.C.M. Moura (SIDN Labs/TU Delft), W. Hardaker, J. Heidemann (USC/Information Sciences Institute), M. Davids (SIDN Labs)
Pour information
Première rédaction de cet article le 22 mars 2022
On sait que le DNS est un des services d'infrastructure les plus essentiels de l'Internet. Il est, par exemple, crucial que les serveurs DNS faisant autorité soient correctement gérés, et robustes face aux problèmes qui peuvent survenir. Ce RFC donne un certain nombre de conseils (juste des conseils, il ne s'agit pas d'une norme) aux opérateurs de ces serveurs.
Il n'est pas obligatoire de suivre ces conseils mais il faut noter qu'ils sont tous fondés sur des articles publiés dans des revues techniques avec examen par les pairs. Donc, c'est quand même sérieux, même si ce RFC n'a pas le statut de norme. Le RFC vise surtout les « gros » serveurs faisant autorité, par exemple les serveurs de TLD importants. Ces serveurs sont typiquement anycastés (RFC 1546 et RFC 4786). Si le vôtre est unicast et ne sert qu'à trois visiteurs de temps en temps, vous n'êtes pas forcément concerné·e.
Au passage, cette distinction entre serveurs faisant autorité et résolveurs est cruciale dès qu'on parle du DNS. La section 2 du RFC commence d'ailleurs par un rappel de cette distinction. D'autre part, cette même section insiste sur l'importance du DNS pour le vécu de l'utilisatrice. Par exemple la latence perçue par celle-ci dépend en bonne partie du DNS. Voici un exemple :
% curl-timing https://www.nic.af/ DNS: 0,920891 s TCP connect: 1,124164 s TLS connect: 1,455578 s Start transfer: 1,598288 s Total: 1,735741 s
On voit que la résolution DNS a pris plus de la moitié du temps
total. (La commande curl-timing
est définie par
alias curl-timing='curl --silent --output /dev/null
--write-out "DNS: %{time_namelookup} s\nTCP connect: %{time_connect}
s\nTLS connect: %{time_appconnect} s\nStart transfer:
%{time_starttransfer} s\nTotal: %{time_total} s\n"'
.) Sur
ce sujet de la latence, voir aussi l'article « The
Internet at the Speed of Light ». La latence dépend
en grande partie de la distance géographique, et la réduire impose
donc une présence mondiale.
Et ces performances, et cette robustesse sont menacées, entre autres, par les nombreuses attaques par déni de service qui visent le DNS, comme celle décrite dans l'article « Anycast vs. DDoS: Evaluating the November 2015 Root DNS Event ».
La section 3 est le cœur du RFC, contenant les recommandations, numérotées de C1 à C6. Commençons par C1, le conseil de n'avoir que des serveurs anycast. L'anycast est indispensable pour répartir très largement les serveurs, assurant la performance et la robustesse de la résolution DNS. Bien sûr, on peut se contenter d'avoir plusieurs enregistrements NS mais cela ne suffit pas : diverses raisons font qu'on ne peut pas avoir des centaines d'enregistrements dans un ensemble NS, alors que certains nuages anycast atteignent cette taille. Donc, avoir plusieurs enregistrements NS est nécessaire mais cela ne dispense pas de l'anycast.
C'est d'autant plus vrai que, parmi les enregistrements NS, la
sélection de celui utilisé est faite par le résolveur. Les
résolveurs corrects mesurent la latence et choisissent le serveur
faisant autorité le plus rapide. Mais il y a d'autres résolveurs qui
n'utilisent pas de bons algorithmes de sélection
(cf. « Recursives
in the Wild: Engineering Authoritative DNS
Servers »). Avec l'anycast, la
sélection de l'instance utilisée est faite par le réseau, via
BGP, et peut
donc être meilleure. Du fait de ces résolveurs sous-performants, un
opérateur a donc intérêt à ce que tous les serveurs faisant autorité
soient anycastés. S'il y a un « maillon faible »,
unicast et mal connecté, il sera quand même
consulté par certains résolveurs, et dégradera donc la latence dans
certains cas. Par exemple, le TLD
.nl
est passé en tout
anycast en 2018.
Maintenant, C2, le conseil de travailler son
routage. Une fois qu'on a décidé de mettre de
l'anycast partout, il reste à
optimiser. BGP
ne garantit pas du tout, contrairement à ce qu'on lit parfois, que
la route la plus rapide sera choisie. BGP fait ce que les
administrateurs réseau lui demandent de faire. On voit régulièrement
des traceroute qui montrent qu'on ne va pas à
l'instance anycast la plus proche. Certains
opérateurs DNS compensent en ajoutant davantage d'instances mais
l'article « Anycast
Latency: How Many Sites Are Enough? » montre, en
testant des serveurs de la racine depuis des
sondes RIPE Atlas, que le
nombre d'instances compte moins que le soin apporté au routage. (On
ne parle ici que de performances ; pour la résistance aux attaques
par déni de service, le nombre d'instances reste essentiel.) Ici, le
temps de réponse des serveurs faisant autorité pour
.fr
, depuis une machine
en France (il y a une instance proche pour chaque serveur) :
% check-soa -i fr d.nic.fr. 2001:678:c::1: OK: 2230506452 (4 ms) 194.0.9.1: OK: 2230506452 (0 ms) e.ext.nic.fr. 193.176.144.22: OK: 2230506434 (15 ms) 2a00:d78:0:102:193:176:144:22: OK: 2230506434 (15 ms) f.ext.nic.fr. 194.146.106.46: OK: 2230506434 (6 ms) 2001:67c:1010:11::53: OK: 2230506452 (11 ms) g.ext.nic.fr. 2001:678:4c::1: OK: 2230506434 (2 ms) 194.0.36.1: OK: 2230506434 (2 ms)
Le conseil C3 porte sur un concept très important lorsqu'on fait de l'anycast, celui de bassin d'attraction. Ce terme désigne l'ensemble des réseaux qui vont envoyer leurs paquets vers une instance donnée. Si toutes les instances ont à peu près les mêmes capacités de traitement des requêtes, on souhaite en général placer les instances et configurer BGP de manière à ce que toutes les instances reçoivent à peu près la même quantité de requêtes. Mais en pratique c'est rarement le cas : le réseau est une chose compliquée. Il existe des méthodes pour améliorer les choses (cf. l'article « Broad and Load-Aware Anycast Mapping with Verfploeter », qui décrit l'outil Verfploeter) mais ce n'est jamais parfait. Au moins, un outil comme Verfploeter permet de procéder plus scientifiquement qu'au pifomètre, et d'avoir une idée de ce que produiront des changements de configuration BGP pour faire du traffic engineering (par exemple, allonger le chemin d'AS dans les annonces, pour diminuer le trafic d'une instance).
Le conseil C4, quant à lui, porte sur les attaques par déni de service (comme celle décrite dans « Anycast vs. DDoS: Evaluating the November 2015 Root DNS Event ») et sur le comportement des serveurs soumis à un stress intense. Que peut faire l'opérateur lorsque Mirai ou un de ses semblables attaque ?
Le conseil C4 est donc d'avoir des plans prêts à l'avance, permettant de prendre une décision en cas de crise, et de l'appliquer.
Et les TTL ? Ils ne sont pas oubliés. La mémorisation des réponses, pendant une durée maximale égale au TTL, est un point essentiel du DNS, et un facteur de performance crucial. Même si un serveur faisant autorité répond souvent en moins de 50 ms, la réponse de la mémoire d'un résolveur, qui peut arriver en 1 ms, sera encore préférable. Ce rôle a été largement étudié (voir par exemple « Modeling TTL- based Internet Caches » ou, plus récemment, « When the Dike Breaks: Dissecting DNS Defenses During DDoS »). Une étude des conséquences du choix du TTL sur les performances est la base du conseil C5, l'étude « Cache Me If You Can: Effects of DNS Time-to- Live ». Elle montre que :
.uy
citée dans
l'article ci-dessus,Bref, on ne peut pas faire une recommandation quantitative appliquable à tous les cas. Le conseil C5 est donc plus complexe :
Et si les TTL entre la zone parente et la zone fille diffèrent ?
La question se pose typiquement pour les enregistrements
NS. Par exemple, les enregistrements NS dans la zone
racine ont un TTL de 48 heures, alors que dans les zones des TLD,
l'ensemble NS a presque toujours un TTL plus faible (une heure pour
.cl
, par exemple). Selon
que le résolveur est parent-centric (une
minorité) ou child-centric (la grande majorité),
il utilisera le TTL de la zone parente ou bien celui de la zone
fille. Le conseil C6 : on ne peut pas compter que le TTL qu'on a mis
soit respecté partout, si l'enregistrement existe aussi dans la zone
parente avec des TTL différents. Attention donc lorsqu'on veut
changer quelque chose, à intégrer le TTL de la zone parente.
Un bon résumé en anglais de ce RFC a été écrit par un des auteurs. Il inclut un récit de la création du RFC et du processus suivi.
Date de publication du RFC : Janvier 2022
Auteur(s) du RFC : C. Loibl (next layer Telekom)
Chemin des normes
Réalisé dans le cadre du groupe de travail IETF idr
Première rédaction de cet article le 21 janvier 2022
Les « communautés étendues » de BGP, des attributs d'une annonce de route avec BGP, sont enregistrées dans des registres IANA, décrits par le RFC 7153. Notre nouveau RFC 9184 met légèrement à jour les procédures d'enregistrement dans ces registres.
Le principal changement est simple : les types 0x80, 0x81 et 0x82 du registre des communautés étendues transitives sont désormais utilisables selon une politique d'enregistrement « premier arrivé, premier servi » (cf. RFC 8126 sur ces politiques d'enregistrement à l'IANA). Ils sont en effet utilisés par le RFC 8955 alors qu'ils étaient dans une plage prévue pour les expérimentations. L'erreur est donc désormais rectifiée.
Date de publication du RFC : Janvier 2022
Auteur(s) du RFC : E. Birrane, K. McKeever (JHU/APL)
Chemin des normes
Réalisé dans le cadre du groupe de travail IETF dtn
Première rédaction de cet article le 1 février 2022
Le protocole Bundle, normalisé dans le RFC 9171, permet la communication entre des machines dont la connectivité est faible et/ou intermittente, par exemple dans le domaine spatial. L'absence de synchronicité interdit beaucoup de solutions de sécurité couramment utilisées sur l'Internet. Ce nouveau RFC présente donc un mécanisme spécifique pour assurer la sécurité (confidentialité et intégrité) des communications faites avec Bundle : ce mécanisme se nomme BPsec (Bundle Protocol security). Notez que le mécanisme est très général, et laisse de côté de nombreux détails.
BPsec peut assurer une sécurité de bout en bout. Ce n'est pas évident dans le contexte d'utilisation de Bundle (RFC 4838), les DTN (Delay Tolerant Networks). Bundle est un protocole qui fonctionne en « enregistre et fais suivre » (store and forward), sans liaisons directes entre émetteur et récepteur, et avec des réseaux lents, imprévisibles, à fort taux de perte de paquets. On ne peut donc pas garantir, par exemple, qu'émetteur et récepteur pourront communiquer à la demande avec un tiers de confiance, genre autorité de certification. On ne peut même pas supposer que toutes les liaisons seront bidirectionnelles, ce qui veut dire, entre autres, qu'un échange de clés Diffie-Hellman ne sera pas possible. Le protocole suppose évidemment que ledit réseau n'est pas de confiance : des méchants peuvent regarder et modifier les bits qui circulent. Cette supposition est classique en sécurité.
BPsec ne va pas essayer de garantir une authentification de chaque étape intermédiaire. D'abord, on ne sait pas si le nœud suivant est réellement adjacent dans l'espace physique (il peut y avoir des intermédiaires « invisibles ») et puis les différents nœuds par lequel le message va passer peuvent avoir des choix de sécurité incompatibles, les réseaux DTN pouvant, comme l'Internet, être composés de nœuds gérés par des organisations différentes.
Si les logiciels sont tous bien programmés, et que les clés privées utilisées n'ont pas été compromises, BPsec va garantir l'intégrité et la confidentialité des messages. BPsec est la continuation du cadre défini dans le RFC 6257, en le simplifiant et en le rendant plus réaliste.
Un peu de terminologie pour suivre ce RFC : d'abord, un rappel qu'un bundle est composé de blocs (RFC 9171, section 4.3). Ensuite :
La section 2 du RFC résume les points importants de la conception de BPsec :
Il existe deux sortes de blocs de sécurité (les types de bloc figurent dans un registre IANA, défini dans le RFC 6255), les BIB (Block Integrity Block) et les BCB (Block Confidentiality Block). Les sources de sécurité ajoutent ces blocs et les acceptors les traitent. Dans le cas d'un chiffrement de bout en bout, par exemple, l'émetteur met un BCB que le destinataire déchiffrera. Dans un autre cas, on peut voir un nœud de départ ne mettre aucun bloc de sécurité (peut-être parce que ce nœud est un objet contraint, avec trop peu de ressources de calcul) mais un nœud intermédiaire ajouter un BIB pour protéger le contenu avant un voyage sur un lien qu'on sait dangereux. Ces blocs sont encodés comme spécifié dans le RFC 9171. L'ajout d'un BIB ne modifie pas le contenu du bundle mais celui d'un BCB va le faire, puisque les données seront chiffrées.
Le bloc contient notamment l'identificateur du nœud qui l'a ajouté.
Le bloc doit être mis sous forme canonique de CBOR (RFC 8949, section 4.2). Comme toujours en cryptographie, pour que les signatures soient vérififiables, il faut que les données soient sous une forme canonique.
La section 5 du RFC décrit le traitement des blocs de sécurité. Le nœud peut les passer tels quels, sans les interpréter, s'il n'est qu'un intermédiaire. Ou bien, s'il veut les valider, il va alors vérifier leurs signatures, déchiffrer, etc. En cas d'erreur, notre RFC ajoute cinq codes supplémentaires pour les signaler.
Pas de cryptographie sans clés et la gestion des clés est souvent le point difficile. La section 6 rappelle le problème. Comme les réseaux de type DTN seront très variés, avec des caractéristiques bien différentes, notre RFC ne décrit pas une méthode unique de gestion de clés. Disons que cette gestion est repoussée aux mises en œuvre ultérieures.
Enfin, la section 7 du RFC décrit les différentes politiques possibles, et la section 8 analyse en détail les caractéristiques de sécurité de ce système, face aux différentes menaces. Parmi les points amusants, le RFC note qu'avec les DTN, on peut s'attendre à ce que des bundles restent dans le réseau très longtemps, peut-être même des années (!) et que la cryptographie (choix des algorithmes, par exemple) doit donc être pensée pour durer.
Ce mécanisme de sécurité est mis en œuvre dans le logiciel libre ION.
Date de publication du RFC : Janvier 2022
Auteur(s) du RFC : S. Burleigh (JPL, Calif. Inst. Of Technology), K. Fall (Roland Computing Services), E. Birrane (APL, Johns Hopkins University)
Chemin des normes
Réalisé dans le cadre du groupe de travail IETF dtn
Première rédaction de cet article le 1 février 2022
Après plusieurs itérations, le protocole Bundle, qui sert à transmettre des messages dans des situations où la connectivité est intermittente et la latence importante (par exemple dans l'espace), a atteint sa version 7, la première qui soit officiellement norme de l'Internet. Ce nouveau RFC décrit cette version 7, assez modifiée par rapport à la version 6 qui faisait l'objet du RFC 5050. Les deux versions n'interopèrent pas.
« Dans l'espace, personne ne vous entend crier », disait l'annonce du film Alien. Peut-être, mais on veut quand même communiquer, échanger des messages et des fichiers. Les particularités de cet environnement (et de quelques autres analogues comme par exemple des étendues peu connectées sur Terre ou bien des situations post-catastrophe) ont suscité la création du groupe DTN (Delay-Tolerant Networking), qui travaille à normaliser des techniques pour envoyer une photo de chat à un autre vaisseau spatial. La version 6 du protocole Bundle, spécifié dans le RFC 5050, avait été programmée dans de nombreux langages, et l'expérience acquise avec Bundle v6 a mené cette version 7. Dans les environnements concernés, avec leur latence élevée (et parfois très variable), leur taux de pertes de paquets souvent important, et leur connectivité intermittente, les protocoles de transport comme TCP et les protocoles applicatifs qui les accompagnent ne conviennent pas. Il n'est pas possible de faire du synchrone (on ne peut pas rester à attendre l'autre pendant on ne sait pas combien de temps). D'où la nécessité de protocoles spécifiques, architecturés autour de l'idée de « enregistre et fais suivre » (store and forward), où chaque machine intermédiaire va stocker les messages en attendant une occasion de les livrer. (Oui, ça ressemble à UUCP et c'est logique, les cahiers des charges étant analogues.) Notre RFC résume ce qu'on attend d'un tel protocole :
Des détails sur ce cahier des charges sont dans les RFC 4838 et dans l'article de K. Fall à SIGCOMM « A delay-tolerant network architecture for challenged internets ».
La section 3 du RFC rappelle les termes utilisés, notamment :
dtn
(comme
dtn://example.com/foobar
) ou, de plus bas
niveau et de signification seulement locale,
ipn
, qui avait été créé par le RFC 6260 (comme ipn:9.37
). Les plans
utilisés ont été mis dans le
registre IANA.La section 4 du RFC décrit le format des bundles. Ce sera du CBOR (RFC 8949). Un bundle comprend un certain nombre de booléens qui indiquent si le récepteur doit accuser réception de l'arrivée du bundle, de la transmission au nœud suivant, etc. (Leur liste figure dans un registre IANA.) Il indique également la destination (sous le forme d'un endpoint ID) et la source (facultative).
Un bundle est en fait composé de blocs, chaque bloc étant un tableau CBOR. Le premier bloc porte les métadonnées indispensables pour les nœuds qui relaieront le bundle (comme l'identificateur de destination) ou comme la durée de vie du bundle (analogue au TTL d'IP). Chaque bloc contient un type et des données. La charge utile du bundle est dans un bloc de type 1, les autres types servant à divers détails (qui, dans IP, seraient mis dans les options du paquet en IPv4 et dans un en-tête d'extension en IPv6). Par exemple, un bloc de type 7 sert à transporter l'âge du bundle, alors qu'un bloc de type 10 à indiquer le nombre de sauts maximum et le nombre déjà effectués. Les différents types figurent dans un registre IANA.
La section 5 du RFC précise comment on traite les bundles. Lors de la réception d'un bundle, le nœud qui n'est pas destination (dont l'identificateur n'apparait pas dans le champ de destination) doit décider s'il transmet le bundle ou pas. S'il décide de le transmettre, il doit ensuite regarder s'il « connait » le nœud de destination (s'il sait le joindre directement) ou bien s'il doit le transmettre à un nœud mieux placé. Comment trouve-t-on ces nœuds mieux placés ? De même que le RFC sur IP ne décrit pas les protocoles de routage, le Bundle Protocol ne précise pas. Cela est fait par des protocoles externes comme SABR.
Une fois prise la décision d'envoyer le bundle à un autre nœud, on sélectionne un CLA adapté à cet autre nœud et on lui transmet. (Puis on le supprime du stockage local.) Bref, tout cela ressemble beaucoup au traitement d'un paquet IP par un routeur IP classique, avec le CLA jouant le rôle de la couche 2. La plus grosse différence est sans doute que le nœud peut garder le paquet pendant un temps assez long, s'il ne peut pas le transmettre tout de suite. La gestion des files d'attentes et des retransmissions sera donc assez différente de celle d'un routeur IP.
En cas d'échec, le nœud peut décider de renvoyer le bundle à l'expéditeur ou de le jeter. Si le bundle a expiré (durée de vie dépassée), il est jeté.
À la réception d'un bundle, le nœud regarde aussi si ce bundle nécessitait un accusé de réception et, si nécessaire, il en envoie. Si le bundle a une destination locale, il est livré à la machine. (Le bundle a pu être fragmenté et, dans ce cas, il faudra attendre que tous les fragments soient là, pour le réassemblage, cf. section 5.8).
La section 6 du RFC couvre le cas des enregistrements administratifs, des messages (envoyés évidemment sous forme de bundles) qui servent à gérer le bon fonctionnement du protocole (un peu comme ICMP pour IP). Ces enregistrements sont un tableau CBOR de deux entrées, un type (un nombre) et les données, un seul type d'enregistrement administratif existe actuellement, de type 1, le rapport d'état, qui va servir à indiquer ce que le message est devenu. Son format et son contenu sont détaillés dans la section 6.1.1. Il dispose d'une liste de codes, comme 1 pour « durée de vie dépassée » ou 5 pour « je n'arrive pas à joindre la destination ».
On a vu que BP, le Bundle Protocol, dépend d'un CLA (Convergence Layer Adapter) pour parler aux couches basses. La section 7 précise ce qu'on attend de ce convergence layer :
Le CLA doit faire tout cela tout en respectant le réseau sous-jacent, par exemple en évitant la congestion. Notez que dans la plupart des cas d'usage de BP, la latence est telle qu'on ne peut pas compter sur les réactions du récepteur, et il faut donc se débrouiller autrement. Un exemple d'un CLA est celui avec TCP du RFC 9174 (mais dans la plupart des scénarios envisagés pour le Bundle Protocol, TCP ne sera pas utilisable).
Il y a actuellement pas moins de sept mises en œuvres différentes de ce BP (Bundle Protocol) :
Notez que deux mises en œuvre de BP ne peuvent interopérer que si elles utilisent le même CLA.
Par défaut, BP n'offre aucune sécurité (cf. section 8). Un attaquant peut suivre le trafic, le modifier, etc. Certes, notre RFC recommande un CLA « sûr » mais cela ne suffit pas. La sécurité doit être fournie par des extensions, comme celle du RFC 9172. Notons aussi que le protocole utilise à plusieurs endroits des estampilles temporelles, donc sa sécurité dépend de celle de son horloge.
L'annexe A détaille les changements depuis le RFC 5050, qui normalisait la version 6 du protocole. Outre le changemente de statut (BP est désormais une norme et plus une « expérience »), les changements techniques sont importants à tel point qu'il n'y a pas d'interopérabilité entre les deux versions. Entre autres, les concepts de node ID et endpoint ID sont désormais séparés, le premier bloc d'un bundle est désormais immuable, de nouveaux types de blocs ont été créés, l'encodage abandonne l'ancien SDNV (RFC 6256) pour CBOR, etc. (Notez qu'il existe un registre IANA des versions de BP.)
Un des avantages de CBOR est l'existence du langage de
description CDDL (RFC 8610), ce qui permet de
donner une description formelle des bundles dans
l'annexe B. Par exemple, voici la description du premier bloc de
chaque bundle, en CDDL (eid
étant l'Endpoint ID) :
primary-block = [ version: 7, bundle-control-flags, crc-type, destination: eid, source-node: eid, report-to: eid, creation-timestamp, lifetime: uint, ... ? crc-value, ]
Avec un cahier des charges présentant beaucoup de ressemblances avec celui du BP, notez l'existence du système NNCP, qui est également de type « enregistre et fais suivre ». Ça semble très intéressant mais je ne l'ai jamais testé.
Date de publication du RFC : Décembre 2021
Auteur(s) du RFC : M. Thomson (Mozilla), T. Pauly (Apple)
Pour information
Première rédaction de cet article le 1 janvier 2022
Après des années de déploiement d'un protocole sur l'Internet, lorsqu'on essaie d'utiliser des fonctions du protocole parfaitement standards et légales, mais qui avaient été inutilisées jusqu'à présent, on découvre souvent que cela ne passe pas : des programmes bogués, notamment dans les middleboxes, plantent de manière malpropre lorsqu'ils rencontrent ces (toutes relatives) nouveautés. C'est ce qu'on nomme l'ossification de l'Internet. Ce RFC de l'IAB fait le point sur le problème et sur les solutions proposées, par exemple le graissage, l'utilisation délibérée et précoce de variations, pour ne pas laisser d'options inutilisées.
Lorsqu'un protocole a du succès (cf. RFC 5218, sur cette notion de succès), on va vouloir le modifier pour traiter des cas nouveaux. Cela n'est pas toujours facile, comme le note le RFC 8170. Tout protocole a des degrés de liberté (extension points) où on pourra l'étendre. Par exemple, le DNS permet de définir de nouveaux types de données (contrairement à ce qu'on lit souvent, le DNS ne sert pas qu'à « trouver des adresses IP à partir de noms ») et IPv6 permet de définir de nouvelles options pour la destination du paquet, voire de nouveaux en-têtes d'extension. En théorie, cela permet d'étendre le protocole. Mais en pratique, utiliser ces degrés de liberté peut amener des résultats imprévus, par exemple, pour le cas du DNS, un pare-feu programmé avec les pieds qui bloque les paquets utilisant un type de données que le pare-feu ne connait pas. Ce RFC se focalise sur des couches relativement hautes, où tout fonctionne de bout en bout (et où, en théorie, les intermédiaires doivent laisser passer ce qu'ils ne comprennent pas). Les couches basses impliquent davantage de participants et sont donc plus problématiques. Ainsi, pour IPv6, l'en-tête « options pour la destination » doit normalement être ignoré et relayé aveuglément par les routeurs alors que l'en-tête « options pour chaque saut » doit (enfin, devait, avant le RFC 8200) être compris et analysé par tous les routeurs du chemin, ce qui complique sérieusement son utilisation.
Notre RFC a été développé par l'IAB dans le cadre du programme « Evolvability, Deployability, & Maintainability (EDM) ».
Déployer une nouvelle version d'un protocole, ou simplement utiliser des options qui n'avaient pas été pratiquées avant, peut donc être très frustrant. Prenons le cas imaginaire mais très proche de cas réels d'un protocole qui a un champ de huit bits inutilisé et donc le RFC d'origine dit que l'émetteur du paquet doit mettre tous ces bits à zéro, et le récepteur du paquet, en application du principe de robustesse, doit ignorer ces bits. Un jour, un nouveau RFC sort, avec une description du protocole où ces bits ont désormais une utilité. Les premiers logiciels qui vont mettre en œuvre cette nouvelle version, et mettre certains bits à un, vont fonctionner lors de tests puis, une fois le déploiement sur l'Internet fait, vont avoir des problèmes dans certains cas. Un pare-feu programmé par des incompétents va jeter ces paquets. Dans son code, il y a un test que le champ vaut zéro partout, car le programmeur n'a jamais lu le RFC et il a juste observé que ce champ était toujours nul. Cette observation a été mise dans le code et bonne chance pour la corriger. (Les DSI qui déploient ce pare-feu n'ont pas regardé s'il était programmé correctement, ils ont juste lu la brochure commerciale.) À partir de là, les programmeurs qui mettent en œuvre le protocole en question ont le choix de foncer et d'ignorer les mines, acceptant que leur programme ne marche pas si les paquets ont le malheur de passer par un des pare-feux bogués (et seront donc perdus), ou bien de revenir à l'ancienne version, ce second choix étant l'ossification : on n'ose plus rien changer de peur que ça casse quelque chose quelque part. Comme les utilisateurs ignorants attribueraient sans doute les problèmes de timeout à l'application, et pas au pare-feu mal écrit, le choix des programmeurs est vite fait : on ne déploie pas la nouvelle version. C'est d'autant plus vrai si le protocole est particulièrement critique pour le bon fonctionnement de l'Internet (BGP, DNS…) ou bien s'il y a de nombreux acteurs non coordonnés (cf. section 2.3). Bien sûr, le blocage de certains paquets peut être volontaire mais, bien souvent, il résulte de la négligence et de l'incompétence des auteurs de middleboxes (cf. section 5).
Les exemples de tels problèmes sont innombrables (l'annexe A du
RFC en fournit plusieurs). Ainsi, TLS a eu bien des ennuis avec des nouvelles
valeurs de l'extension
signature_algorithms
.
Si le protocole en question était conçu de nos jours, il est probable que ses concepteurs prendraient la précaution de réserver quelques valeurs non nulles pour le champ en question, et de demander aux programmes d'utiliser de temps en temps ces valeurs, pour être sûr qu'elles sont effectivement ignorées. C'est ce qu'on nomme le graissage et c'est une technique puissante (mais pas parfaite) pour éviter la rouille, l'ossification. (Notez que les valeurs utilisées pour le graissage ne doivent pas être consécutives, pour limiter les risques qu'un programmeur de middlebox paresseux ne les teste facilement. Et qu'il faut les réserver pour que, plus tard, l'utilisation de vraies valeurs pour ce champ ne soit pas empêchée par les valeurs de graissage.) Le graissage ne résout pas tous les problèmes puisqu'il y a toujours le risque que les valeurs utilisées pour graisser finissent par bénéficier d'un traitement de faveur, et soient acceptées, alors que les valeurs réelles poseront des problèmes.
On pourrait penser qu'une meilleure conception des protocoles éviterait l'ossification. (C'est le discours des inventeurs géniaux et méconnus qui prétendent que leur solution magique n'a aucun inconvénient et devrait remplacer tout l'Internet demain.) Après tout, le RFC 6709 contient beaucoup d'excellents conseils sur la meilleure façon de concevoir des protocoles pour qu'ils puissent évoluer. Par exemple, il insiste sur le fait que le mécanisme de négociation qui permet aux deux parties de se mettre d'accord sur une option ou une version doit être simple, pour qu'il y ait davantage de chances qu'il soit mis en œuvre correctement dans tous les programmes. En effet, en l'absence de graissage, ce mécanisme ne sera testé en vrai que lorsqu'on introduira une nouvelle version ou une nouvelle option et, alors, il sera trop tard pour modifier les programmes qui n'arrivent pas à négocier ces nouveaux cas, car leur code de négociation est bogué. Le RFC 6709 reconnait ce problème (tant qu'on n'a pas utilisé un mécanisme, on ne sait pas vraiment s'il marche) et reconnait que le conseil d'un mécanisme simple n'est pas suffisant.
Notre RFC 9170 contient d'ailleurs une petite pique contre QUIC en regrettant qu'on repousse parfois à plus tard le mécanisme de négociation de version, pour arriver à publier la norme décrivant le protocole (exactement ce qui est arrivé à QUIC, qui n'a pas de mécanisme de négociation des futures versions). Le problème est que, une fois la norme publiée et le protocole déployé, il sera trop tard…
Bref, l'analyse du RFC (section 3) est qu'il faut utiliser tôt et souvent les mécanismes d'extension. Une option ou un moyen de négocier de nouvelles options qui n'a jamais été utilisé depuis des années est probablement ossifié et ne peut plus être utilisé. C'est très joli de dire dans le premier RFC d'un protocole « cet octet est toujours à zéro dans cette version mais les récepteurs doivent ignorer son contenu car il pourra servir dans une future version » mais l'expérience prouve largement qu'une telle phrase est souvent ignorée et que bien des logiciels planteront, parfois de façon brutale, le jour où on commencer à utiliser des valeurs non nulles. Il faut donc utiliser les mécanismes ou bien ils rouillent. Par exemple, si vous concevez un mécanisme d'extension dans votre protocole, il est bon que, dès le premier jour, au moins une fonction soit mise en œuvre via ce mécanisme, pour forcer les programmeurs à en tenir compte, au lieu de simplement sauter cette section du RFC. Les protocoles qui ajoutent fréquemment des options ou des extensions ont moins de problèmes que ceux qui attendent des années avant d'exploiter leurs mécanismes d'extension. Et plus on attend, plus c'est pire. C'est pour cela que, par exemple, il faut féliciter le RIPE NCC d'avoir tenté l'annonce de l'attribut BGP 99, même si cela a cassé des choses, car si on ne l'avait pas fait (comme l'avaient demandé certaines personnes qui ne comprenaient pas les enjeux), le déploiement de nouveaux attributs dans BGP serait resté quasi-impossible.
Tester tôt est d'autant plus important qu'il peut être crucial, pour la sécurité, qu'on puisse étendre un protocole (par exemple pour l'agilité cryptographique, cf. RFC 7696). L'article de S. Bellovin et E. Rescorla, « Deploying a New Hash Algorithm » montre clairement que les choses ne se passent pas aussi bien qu'elles le devraient.
Comme dit plus haut, la meilleure façon de s'assurer qu'un
mécanisme est utilisable est de l'utiliser effectivement. Et pour
cela que ce mécanisme soit indispensable au fonctionnement normal du
protocole, qu'on ne puisse pas l'ignorer. Le RFC cite l'exemple de
SMTP :
le principal mécanisme d'extension de SMTP est d'ajouter de nouveaux
en-têtes (comme le Archived-At:
dans le RFC 5064) or, SMTP dépend d'un traitement de ces
en-têtes pour des fonctions mêmes élémentaires. Un MTA ne peut pas se permettre
d'ignorer les en-têtes. Ainsi, on est sûr que toute mise en œuvre de
SMTP sait analyser les en-têtes. Et, comme des en-têtes nouveaux
sont assez fréquemment ajoutés, on sait que des en-têtes inconnus
des vieux logiciels ne perturbent pas SMTP. Ce cas est idéal : au
lieu d'un mécanisme d'extension qui serait certes spécifié dans le
RFC mais pas encore utilisé, on a un mécanisme d'extension dont
dépendent des fonctions de base du protocole.
Le RFC cite également le cas de SIP, qui est moins favorable : les relais ne transmettent en général pas les en-têtes inconnus, ce qui ne casse pas la communication, mais empêche de déployer de nouveaux services tant que tous les relais n'ont pas été mis à jour.
Bien sûr, aucune solution n'est parfaite. Si SMTP n'avait pas ajouté de nombreux en-têtes depuis sa création, on aurait peut-être des programmes qui certes savent analyser les en-têtes mais plantent lorsque ces en-têtes ne sont pas dans une liste limitée. D'où l'importance de changer souvent (ici, en ajoutant des en-têtes).
Souvent, les protocoles prévoient un mécanisme de négociation de la version utilisée. On parle de version différente lorsque le protocole a suffisamment changé pour qu'on ne puisse pas interagir avec un vieux logiciel. Un client SMTP récent peut toujours parler à un vieux serveur (au pire, le serveur ignorera les en-têtes trop récents) mais une machine TLS 1.2 ne peut pas parler à une machine TLS 1.3. Dans le cas le plus fréquent, la machine récente doit pouvoir parler les deux versions, et la négociation de version sert justement à savoir quelle version utiliser. Le problème de cette approche est que, quand la version 1 est publiée en RFC, avec son beau mécanisme de négociation de version, il n'y a pas encore de version 2 pour tester que cette négociation se passera bien. Celle-ci ne sortira parfois que des années plus tard et on s'apercevra peut-être à ce moment que certains programmes ont mal mis en œuvre la négociation de version, voire ont tout simplement négligé cette section du RFC…
Première solution à ce problème, utiliser un mécanisme de négociation de version située dans une couche plus basse. Ainsi, IP a un mécanisme de négociation de version dans l'en-tête IP lui-même (les quatre premiers bits indiquent le numéro de version, cf. RFC 8200, section 3) mais ce mécanisme n'a jamais marché en pratique. Ce qui marche, et qui est utilisé, c'est de se servir du mécanisme de la couche 2. Par exemple, pour Ethernet (RFC 2464), c'est l'utilisation du type de protocole (EtherType), 0x800 pour IPv4 et 0x86DD pour IPv6. Un autre exemple est l'utilisation d'ALPN (RFC 7301) pour les protocoles au-dessus de TLS. (Le RFC cite aussi la négociation de contenu de HTTP.)
Une solution récente au problème de l'ossification est le graissage, présenté en section 3.3. Décrit à l'origine pour TLS, dans le RFC 8701, il est désormais utilisé dans d'autres protocoles comme QUIC. Son principe est de réserver un certain nombre de valeurs utilisant des extensions du protocole et de s'en servir, de façon à forcer les différents logiciels, intermédiaires ou terminaux, à lire tout le RFC et à gérer tous les cas. Dans l'exemple cité plus haut d'un protocole qui aurait un champ de huit bits « cet octet est toujours à zéro dans cette version mais les récepteurs doivent ignorer son contenu car il pourra servir dans une future version », on peut réserver les valeurs 3, 21, 90 et 174 comme valeurs de graissage et l'émetteur les mettra de temps en temps dans le champ en question. Dans le cas de TLS, où le client propose des options et le serveur accepte celles qu'il choisit dans ce lot (oui, je sais, TLS est plus compliqué que cela, par exemple lorsque le serveur demande une authentification), le client annonce au hasard des valeurs de graissage. Ainsi, une middlebox qui couperait les connexions TLS serait vite détectée et, on peut l'espérer, rejetée par le marché. Le principe du graissage est donc « les extensions à un protocole s'usent quand on ne s'en sert pas ». Cela évoque les tests de fuzzing qu'on fait en sécurité, où on va essayer plein de valeurs inhabituelles prises au hasard, pour s'assurer que le logiciel ne se laisse pas surprendre. On voit aussi un risque du graissage : si les programmes bogués sont nombreux, le premier qui déploie un mécanisme de graissage va essuyer les plâtres et se faire parfois rejeter. Il est donc préférable que le graissage soit utilisé dès le début, par exemple dans un nouveau protocole.
Évidemment, la solution n'est pas parfaite. On peut imaginer un logiciel mal fait qui reconnait les valeurs utilisées pour le graissage (et les ignore) mais rejette quand même les autres valeurs pourtant légitimes. (La plupart du temps, les valeurs réservées pour le graissage ne sont pas continues, comme dans les valeurs 3, 21, 90 et 174 citées plus haut, pour rendre plus difficile un traitement spécifique au graissage.)
Bien sûr, même sans valeurs réservées au graissage, un programme pourrait toujours faire à peu près l'équivalent, en utilisant les valeurs réservées pour des expérimentations ou bien des usages privés. Mais le risque est alors qu'elles soient acceptées par l'autre partie, ce qui n'est pas le but.
Tous les protocoles n'ont pas le même style d'interaction que TLS, et le graissage n'est donc pas systématiquement possible. Et puis il ne teste qu'une partie des capacités du protocole, donc il ne couvre pas tous les cas possibles. Le graissage est donc utile mais ne résout pas complètement le problème.
Parfois, des protocoles qui ne permettaient pas facilement l'extensibilité ont été assez sérieusement modifiés pour la rendre possible. Ce fut le cas du DNS avec EDNS (RFC 6891). Son déploiement n'a pas été un long fleuve tranquille et, pendant longtemps, les réactions erronées de certains serveurs aux requêtes utilisant EDNS nécessitaient un mécanisme de repli (re-essayer sans EDNS). Il a ensuite fallu supprimer ce mécanisme de repli pour être sûr que les derniers systèmes erronés soient retirés du service, le tout s'étalant sur de nombreuses années. Un gros effort collectif a été nécessaire pour parvenir à ce résultat, facilité, dans le cas du DNS, par le relativement petit nombre d'acteurs et leur étroite collaboration.
La section 4 du RFC cite d'autres techniques qui peuvent être utilisées pour lutter contre l'ossification. D'abord, ne pas avoir trop de possibilités d'étendre le protocole car, dans ce cas, certaines possibilités seront fatalement moins testées que d'autres et donc plus fragiles si on veut s'en servir un jour.
Le RFC suggère aussi l'utilisation d'invariants. Un invariant est une promesse des auteurs du protocole, assurant que cette partie du protocole ne bougera pas (et, au contraire, que tout le reste peut bouger et qu'il ne faut pas compter dessus). Si les auteurs de logiciels lisent les RFC (une supposition audacieuse, notamment pour les auteurs de middleboxes), cela devrait éviter les problèmes avec les évolutions futures. (Il est donc toujours utile de graisser les parties du protocole qui ne sont pas des invariants.) Le RFC 5704, dans sa section 2.2, définit plus rigoureusement ce que sont les invariants. Deux exemples d'utilisation de ce concept sont le RFC 8999 (décrivant les invariants de QUIC) et la section 9.3 du RFC 8446 sur TLS. Notre RFC conseille aussi de préciser explicitement ce qui n'est pas invariant (comme le fait l'annexe A du RFC 8999), ce que je trouve contestable (il y a un risque que cette liste d'exemples de non-invariants soit interprétée comme limitative, alors que ce ne sont que des exemples).
Une autre bonne technique, que recommande notre RFC, est de prendre des mesures techniques pour empêcher les intermédiaires de tripoter la communication. Moins il y a d'entités qui analysent et interprètent les paquets, moins il y aura de programmes à vérifier et éventuellement à modifier en cas d'extension du protocole. Un bon exemple de ces mesures techniques est évidemment la cryptographie. Ainsi, chiffrer le fonctionnement du protocole, comme le fait QUIC, ne sert pas qu'à préserver la vie privée de l'utilisateur, cela sert aussi à tenir à l'écart les intermédiaires, ce qui limite l'ossification. Le RFC 8558 est une bonne lecture à ce sujet.
Bien sûr, si bien conçu que soit le protocole, il y aura des
problèmes. Le RFC suggère donc aussi qu'on crée des mécanismes de
retour, permettant d'être informés des problèmes. Prenons par
exemple un serveur TLS qui refuserait des clients corrects, mais qui
utilisent des extensions que le serveur ne gère pas correctement. Si
le serveur ne journalise pas ces problèmes, ou
bien que l'administrateur système ne lit pas
ces journaux, le problème pourra trainer très longtemps. Idéalement,
ces retours seront récoltés et traités automatiquement, et envoyés à
celles et ceux qui sont en mesure d'agir. C'est plus facile à dire
qu'à faire, et cela dépend évidemment du protocole utilisé. Des
exemples de protocoles ayant un tel mécanisme sont
DMARC (RFC 7489, avec
son étiquette rua
, section 7), et SMTP (avec le
TLSRPT
du RFC 8460).
Enfin, pour terminer, l'annexe A du RFC présente quelques
exemples de protocoles et comment ils ont géré ce problème. Elle
commence par le DNS
(RFC 1034 et RFC 1035). Le DNS a une mauvaise expérience avec le
déploiement de nouveaux types d'enregistrement qui, par exemple,
provoquaient des rejets violents des requêtes DNS par certains
équipements. C'est l'une des raisons pour lesquelles SPF (RFC 7208) a finalement renoncé à utiliser son propre type
d'enregistrement, SPF
(cf. RFC 6686) et pour lesquelles tout le monde se sert de
TXT
. Le problème s'est heureusement amélioré
depuis la parution du RFC 3597, qui normalise
le traitement des types inconnus. (Mais il reste d'autres obstacles
au déploiement de nouveaux types, comme la mise à jour des
interfaces « conviviales » d'avitaillement d'une zone DNS,
interfaces qui mettent de nombreuses années à intégrer les nouveaux
types.)
HTTP, lui, a plutôt bien marché, question extensibilité mais il a certains mécanismes d'extension que personne n'ose utiliser et qui ne marcheraient probablement pas, comme les extensions des chunks (RFC 7230, section 4.1.1) ou bien comme l'utilisation d'autres unités que les octets dans les requêtes avec intervalles (RFC 7233, section 2.2).
Et IP lui-même ?
Par exemple, IP avait un mécanisme pour permettre aux équipements
réseau de reconnaitre la version utilisée, permettant donc de faire
coexister plusieurs versions d'IP, le champ Version (les quatre
premiers bits du paquet). Mais il n'a jamais réellement fonctionné,
les équipements utilisant à la place les indications données par la
couche inférieure (l'« ethertype », dans le cas
d'Ethernet, cf. RFC 2464). D'autres problèmes sont arrivés avec IP, par
exemple l'ancienne « classe E ». Le préfixe
224.0.0.0/3
avait été réservé par le RFC 791, section 3.2, mais sans précisions
(« The extended addressing mode is undefined. Both of
these features are reserved for future use. »). Le RFC 988 avait ensuite pris
224.0.0.0/4
pour le
multicast (qui n'a jamais
été réellement déployé sur l'Internet), créant la « classe D », le
reste devenant la « classe E », 240.0.0.0/4
dont on
ne sait plus quoi faire aujourd'hui, même si certains voudraient la
récupérer pour prolonger l'agonie d'IPv4. Son
traitement spécial par de nombreux logiciels empêche de l'affecter
réellement. Une solution souvent utilisée pour changer la
signification de tel ou tel champ dans l'en-tête est la négociation
entre les deux parties qui communiquent, mais cela ne marche pas
pour les adresses (la communication peut être
unidirectionnelle).
SNMP n'a pas eu trop de problèmes avec le déploiement de ses versions 2 et 3. La norme de la version 1 précisait clairement (RFC 1157) que les paquets des versions supérieures à ce qu'on savait gérer devaient être ignorés silencieusement, et cela était vraiment fait en pratique. Il a donc été possible de commencer à envoyer des paquets des versions 2, puis 3, sans casser les vieux logiciels qui écoutaient sur le même port.
TCP, par contre, a eu bien des problèmes avec son mécanisme d'extension. (Lire l'article de Honda, M., Nishida, Y., Raiciu, C., Greenhalgh, A., Handley, M., et H. Tokuda, « Is it still possible to extend TCP? ».) En effet, de nombreuses middleboxes se permettaient de regarder l'en-tête TCP sans avoir lu et compris le RFC, jetant des paquets qui leur semblaient anormaux, alors qu'ils avaient juste des options nouvelles. Ainsi, le multipath TCP (RFC 6824) a été difficile à déployer (cf. l'article de Raiciu, C., Paasch, C., Barre, S., Ford, A., Honda, M., Duchêne, F., Bonaventure, O., et M. Handley, « How Hard Can It Be? Designing and Implementing a Deployable Multipath TCP »). TCP Fast Open (RFC 7413) a eu moins de problèmes sur le chemin, mais davantage avec les machines terminales, qui ne comprenaient pas ce qu'on leur demandait. Comme le but de TCP Fast Open était d'ouvrir une connexion rapidement, ce problème était fatal : on ne peut pas entamer une négociation lorsqu'on veut aller vite.
Et enfin, dernier protocole étudié dans cette annexe A, TLS. Là, la conclusion du RFC est que le mécanisme de négociation et donc d'extension de TLS était correct, mais que la quantité de programmes mal écrits et qui réagissaient mal à ce mécanisme l'a, en pratique, rendu inutilisable. Pour choisir une version de TLS commune aux deux parties qui veulent communiquer de manière sécurisée, la solution était de chercher la plus haute valeur de version commune (HMSV pour highest mutually supported version, cf. RFC 6709, section 4.1). Mais les innombrables bogues dans les terminaux, et dans les middleboxes (qui peuvent accéder à la négociation TLS puisqu'elle est en clair, avant tout chiffrement) ont fait qu'il était difficile d'annoncer une version supérieure sans que les paquets soient rejetés. (Voir l'expérience racontée dans ce message.)
TLS 1.3 (RFC 8446) a donc dû abandonner
complètement le mécanisme HMSV et se présenter comme étant du 1.2,
pour calmer les middleboxes intolérantes,
dissimulant ensuite dans le ClientHello
des
informations qui permettent aux serveurs 1.3 de reconnaitre un
client qui veut faire du 1.3 (ici, vu avec tshark) :
TLSv1 Record Layer: Handshake Protocol: Client Hello Content Type: Handshake (22) Version: TLS 1.0 (0x0301) Length: 665 Handshake Protocol: Client Hello Handshake Type: Client Hello (1) Length: 661 Version: TLS 1.2 (0x0303) [Ce champ Version n'est là que pour satisfaire les middleboxes.] Extension: supported_versions (len=9) Type: supported_versions (43) Length: 9 Supported Versions length: 8 Supported Version: TLS 1.3 (0x0304) Supported Version: TLS 1.2 (0x0303) Supported Version: TLS 1.1 (0x0302) Supported Version: TLS 1.0 (0x0301) [La vraie liste des versions acceptées était dans cette extension.]
Toujours à propos de TLS, SNI (Server Name
Indication, cf. RFC 6066, section 3)
est un autre exemple où la conception était bonne mais le manque
d'utilisation de certains options a mené à l'ossification et ces
options ne sont plus, en pratique, utilisables. Ainsi, SNI
permettait de désigner le serveur par son nom de domaine (host_name
dans SNI) mais aussi en utilisant d'autres types d'identificateurs
(champ name_type
) sauf que, en pratique, ces
autres types n'ont jamais été utilisés et ne marcheraient
probablement pas. (Voir l'article de A. Langley, « Accepting
that other SNI name types will never work ».)
Date de publication du RFC : Décembre 2021
Auteur(s) du RFC : T. Sattler, R. Carney, J. Kolker (GoDaddy)
Chemin des normes
Réalisé dans le cadre du groupe de travail IETF regext
Première rédaction de cet article le 26 décembre 2021
Un registre, par exemple un registre de noms de domaine, utilise parfois le protocole EPP pour la communication avec ses clients. Ce RFC décrit comment utiliser ce protocole pour informer les clients des périodes d'indisponibilité du registre, par exemple lors d'une opération de maintenance.
Aujourd'hui, un registre prévient de ses periodes d'indisponibilité prévues par divers moyens : courriers aux BE, messages sur des réseaux sociaux, page Web dédiée comme :
Chaque registre le fait de façon différente, il n'existe pas de règles communes, et le côté non-structuré de ces annonces fait qu'il faut une interventon humaine pour les analyser et les mettre dans un agenda. Et un BE peut devoir interagir avec de nombreux registres ! Notre RFC propose d'utiliser EPP (RFC 5730) pour ces annonces.
Donc, premier principe, puisqu'on va souvent manipuler des dates,
les dates et heures seront toutes représentées en UTC et dans le format
du RFC 3339. Ensuite, les annonces seront dans
un élément XML <item>
, de
l'espace de noms
urn:ietf:params:xml:ns:epp:maintenance-1.0
(enregistré
à l'IANA). Parmi les sous-éléments de cet élément :
id
, un identificateur de
l'évènement,systems
, qui permettra de désigner les
systèmes affectés,environment
, pour dire si l'évènement
concerne la production ou bien un banc de test,start
et end
, qui
indiquent le début et la fin (prévue…) de l'évènement,
Un exemple d'évènement, une intervention sur le serveur EPP
epp.registry.example
de production, peut être :
<maint:item> <maint:id>2e6df9b0-4092-4491-bcc8-9fb2166dcee6</maint:id> <maint:systems> <maint:system> <maint:name>EPP</maint:name> <maint:host>epp.registry.example</maint:host> <maint:impact>full</maint:impact> </maint:system> </maint:systems> <maint:environment type="production"/> <maint:start>2021-12-30T06:00:00Z</maint:start> <maint:end>2021-12-30T07:00:00Z</maint:end> <maint:reason>planned</maint:reason> <maint:detail> https://www.registry.example/notice?123 </maint:detail> <maint:tlds> <maint:tld>example</maint:tld> <maint:tld>test</maint:tld> </maint:tlds> </maint:item>
On voit que le serveur EPP sera arrêté pendant une heure
(<impact>full</impact>
indiquant
une indisponibilité totale) et que cela affectera les TLD
.example
et .test
. Une
telle information, étant sous une forme structurée, peut être
analysée par un programme et automatiquement insérée dans un agenda,
ou un système de supervision.
Les commandes EPP exactes, maintenant (section 4 du RFC). La
commande <info>
peut renvoyer maintenant
un élément <maint:info>
qui contient
l'information de maintenance. Voici l'exemple du RFC. D'abord, la
question du client, qui veut de l'information sur l'évènement
2e6df9b0-4092-4491-bcc8-9fb2166dcee6
:
<info> <maint:info xmlns:maint="urn:ietf:params:xml:ns:epp:maintenance-1.0"> <maint:id>2e6df9b0-4092-4491-bcc8-9fb2166dcee6</maint:id> </maint:info> </info>
Puis la réponse du serveur :
<response> <result code="1000"> <msg>Command completed successfully</msg> </result> <resData> <maint:infData xmlns:maint="urn:ietf:params:xml:ns:epp:maintenance-1.0"> <maint:item> <maint:id>2e6df9b0-4092-4491-bcc8-9fb2166dcee6 </maint:id> <maint:type lang="en">Routine Maintenance</maint:type> <maint:systems> <maint:system> <maint:name>EPP</maint:name> <maint:host>epp.registry.example </maint:host> <maint:impact>full</maint:impact> </maint:system> </maint:systems> <maint:environment type="production"/> <maint:start>2021-12-30T06:00:00Z</maint:start> <maint:end>2021-12-30T07:00:00Z</maint:end> <maint:reason>planned</maint:reason> <maint:detail> https://www.registry.example/notice?123 </maint:detail> <maint:description lang="en">free-text </maint:description> <maint:description lang="de">Freitext </maint:description> <maint:tlds> <maint:tld>example</maint:tld> <maint:tld>test</maint:tld> </maint:tlds> <maint:intervention> <maint:connection>false</maint:connection> <maint:implementation>false</maint:implementation> </maint:intervention> <maint:crDate>2021-11-08T22:10:00Z</maint:crDate> </maint:item> </maint:infData> </resData> ...
Ici, le client connaissait l'identificateur d'une opération de maintenance particulière. S'il ne le connait pas et veut récupérer une liste d'événements :
<info> <maint:info xmlns:maint="urn:ietf:params:xml:ns:epp:maintenance-1.0"> <maint:list/> </maint:info> </info>
Il récupérera alors une <maint:list>
, une
liste d'opérations de maintenance.
Le client EPP peut
également être prévenu des maintenances par la commande
<poll>
, qui dote EPP d'un système de
messagerie (RFC 5730, section 2.9.2.3). Ainsi,
un message dans la boite aux lettres du client pourra être :
<response> <result code="1301"> <msg>Command completed successfully; ack to dequeue</msg> </result> <msgQ count="1" id="12345"> <qDate>2021-11-08T22:10:00Z</qDate> <msg lang="en">Registry Maintenance Notification</msg> </msgQ> <resData> <maint:infData xmlns:maint="urn:ietf:params:xml:ns:epp:maintenance-1.0"> <maint:item> <maint:id>2e6df9b0-4092-4491-bcc8-9fb2166dcee6</maint:id> <maint:pollType>create</maint:pollType> <maint:systems> <maint:system> <maint:name>EPP</maint:name> <maint:host>epp.registry.example </maint:host> <maint:impact>full</maint:impact> ...
La section 5 du RFC décrit la syntaxe formelle de cette extension (en XML Schema). Elle est dans le registre IANA des extensions à EPP.
Et question mises en œuvre ? Apparemment, les registres gérés par GoDaddy et Tango envoient déjà ces informations de maintenance.
Date de publication du RFC : Décembre 2021
Auteur(s) du RFC : C. Bormann (Universität Bremen TZI)
Chemin des normes
Réalisé dans le cadre du groupe de travail IETF cbor
Première rédaction de cet article le 25 décembre 2021
Le langage CDDL (Concise Data Definition Language) est un langage de description de schémas de données, notamment pour le format CBOR. Ce nouveau RFC étend CDDL avec de nouveaux opérateurs, permettant entre autres l'addition d'entiers et la concaténation de chaines de caractères.
CDDL est normalisé dans le RFC 8610 (et le format CBOR dans le RFC 8949). Il permet l'ajout de nouveaux opérateurs pour étendre le langage, possibilité utilisée par notre nouveau RFC. Notez que, comme les modèles de données de JSON et CBOR sont très proches, les schémas CDDL peuvent également être utilisés pour JSON, ce que je fais ici pour les exemples car le JSON est plus facile à lire et à écrire.
D'abord, l'opérateur .plus
. Il permet par
exemple, dans la spécification d'un schéma, de faire dépendre
certains nombres d'autres nombres. L'exemple ci-dessous
définit un type « intervalle » où la borne supérieure doit être
supérieure de 5 à la borne inférieure :
top = interval<3> interval<BASE> = BASE .. (BASE .plus 5)
Avec un tel schéma, la valeur 4 sera acceptée mais 9 sera refusée :
% cddl tmp.cddl validate tmp.json CDDL validation failure (nil for 9): [9, [:range, 3..8, Integer], ""]
Deuxième opérateur, la
concaténation, avec le nouvel opérateur
.cat
:
s = "foo" .cat "bar"
Dans cet exemple, évidemment, .cat
n'est pas
très utile, on aurait pu écrire la chaine complète directement. Mais
.cat
est plus pertinent quand on veut manipuler
des chaines contenants des sauts de ligne :
s = "foo" .cat ' bar baz '
Ce schéma acceptera la chaine de caractère "foo\n bar\n
baz\n"
.
Dans l'exemple ci-dessus, bar
et
baz
seront précédés des espaces qui
apparaissaient dans le code source. Souvent, on souhaite mettre ces
espaces en début de ligne dans le code source, pour
l'indenter joliment, mais les supprimer dans
le résultat final. Cela peut se faire avec l'opérateur pour lequel
notre RFC invente le joli mot de détentation
(dedending), .det
, qui
fonctionne comme .cat
mais « dédente » les
lignes :
s = "foo" .det ' bar baz '
Cette fois, le schéma n'acceptera que la chaine
"foo\nbar\nbaz\n"
.
Le RFC note que, comme .det
est l'abréviation
de dedending cat, on aurait pu l'appeler
.dedcat
mais cela aurait chagriné les amis des
chats.
CDDL est souvent utilisé dans les normes techniques de l'Internet
et celles-ci contiennent souvent des
grammaires en ABNF (RFC 5234). Pour permettre de réutiliser les règles ABNF dans
CDDL, et donc se dispenser d'une ennuyeuse traduction, un nouvel
opérateur fait son apparition, .abnf
. Le RFC
donne l'exemple de la grammaire du RFC 3339,
qui normalise les formats de date : abnf-rfc3339.cddl
. Avec ce fichier, on peut accepter des
chaines comme "2021-12-15"
ou
"2021-12-15T15:52:00Z"
. Notons qu'il reste
quelques difficultés car les règles d'ABNF ne sont pas parfaitement
compatibles avec celles de CDDL. Si .abnf
va
traiter l'ABNF comme de l'Unicode encodé en
UTF-8, un autre opérateur,
.abnfb
, va le traiter comme une bête suite
d'octets. D'autre part, comme ABNF exige souvent des sauts de ligne,
les opérateurs .cat
et
.det
vont être très utiles.
Quatrième et dernier opérateur introduit par ce RFC,
.feature
. À quoi sert-il ? Comme le langage
CDDL peut ếtre étendu au-delà de ce qui existait dans le RFC 8610, on court toujours le risque de traiter
un schéma CDDL avec une mise en œuvre de CDDL qui ne connait pas
toutes les fonctions utilisées dans le schéma. Cela produit en
général un message d'erreur peu clair et, surtout, cela mènerait à
considérer des données comme invalides alors qu'elles sont
parfaitement acceptables pour le reste du
schéma. .feature
sert donc à marquer les
extensions qu'on utilise. Le programme qui met en œuvre CDDL pourra
ainsi afficher de l'information claire. Par exemple, si on définit
une personne :
person = { ? name: text ? organization: text }
puis qu'on veut rajouter son groupe sanguin :
{"name": "Jean", "bloodgroup": "O+"}
Cet objet sera rejeté, en raison du champ
bloodgroup
. On va faire un schéma plus ouvert, avec
.feature
:
person = { ? name: text ? organization: text * (text .feature "further-person-extension") => any }
Et, cette fois, l'objet est accepté avec un message d'avertissement clair :
% cddl person-new-feature.cddl validate tmp.json ** Features potentially used (tmp.json): further-person-extension: ["bloodgroup"]
Comme le schéma est assez ouvert, la fonction de génération de fichiers d'exemple de l'outil donne des résultats amusants :
% cddl person-new-feature.cddl generate {"name": "plain", "dependency's": "Kathryn's", "marvelous": "cleavers"}
Les nouveaux opérateurs ont été placés dans le registre
IANA. Ils sont mis en œuvre dans l'outil de référence de CDDL
(le cddl
utilisé ici). Écrit en
Ruby, on peut l'installer avec la méthode Ruby
classique :
% gem install cddl
Il existe une autre mise en œuvre de CDDL (qui porte malheureusement le même nom). Elle est en Rust et peut donc s'installer avec :
% cargo install cddl
Elle n'inclut pas encore les opérateurs de ce RFC :
% /home/stephane/.cargo/bin/cddl validate --cddl plus.cddl plus.json Validation of "plus.json" failed error parsing CDDL: error: lexer error ┌─ input:8:12 │ 8 │ (BASE .plus 1) => int ; upper bound │ ^^^^^ invalid control operator
Date de publication du RFC : Décembre 2021
Auteur(s) du RFC : M. Richardson (Sandelman Software Works), C. Bormann (Universität Bremen TZI)
Chemin des normes
Réalisé dans le cadre du groupe de travail IETF cbor
Première rédaction de cet article le 14 décembre 2021
Ce nouveau RFC normalise deux étiquettes CBOR pour représenter des adresses IP et des préfixes d'adresses.
Le format de données CBOR, normalisé dans le RFC 8949, a une liste de types prédéfinis mais on peut en créer d'autres, en étiquetant la donnée avec un entier qui permettre de savoir comment interpréter la donnée en question. Notre RFC introduit les étiquettes 52 (pour les adresses IPv4) et 54 (pour les adresses IPv6). Ah, pourquoi 52 et 54 ? Je vous laisse chercher, la solution est à la fin de l'article
La section 3 de notre RFC décrit le format. Pour chaque famille (IPv4 ou IPv6), il y a trois formats (tous avec la même étiquette) :
eth0
sur Linux,
voir la section 6 du RFC 4007 pour IPv6, et
les RFC 4001 et RFC 6991 pour IPv4, mais cela
peut aussi être un entier), identificateur qui est local à la
machine.La section 5 du RFC contient une description en CDDL (RFC 8610) de ces données.
J'ai écrit une mise en œuvre en Python de ce RFC, qui renvoie à un client
HTTP son
adresse IP, et le préfixe annoncé dans la DFZ en BGP (en utilisant pour cela les
données du RIS,
via le programme WhichASN). Le
service est accessible à l'adresse
https://www.bortzmeyer.org/apps/addresses-in-cbor
,
par exemple :
% curl -s https://www.bortzmeyer.org/apps/addresses-in-cbor > tmp.cbor
Le CBOR est du binaire, on peut regarde avec le programme read-cbor :
% read-cbor tmp.cbor Array of 3 items String of length 165: Your IP address in CBOR [...] Tag 54 Byte string of length 16 Tag 54 Array of 2 items Unsigned integer 32 Byte string of length 4
On voit que le service renvoie un tableau CBOR de trois entrées :
Vu avec le programme cbor2diag, le même fichier :
% cbor2diag.rb tmp.cbor ["Your IP address in CBOR, done with Python 3.9.2 [...]", 54(h'200141D0030222000000000000000180'), 54([32, h'200141D0'])]
(Le préfixe du client HTTP était en effet bien
2001:41d0::/32
.) Le code source de service est
dans les sources du moteur de ce
blog, plus précisement en
wsgis/dispatcher.py
.
Sinon, la raison du choix des étiquettes est que, en ASCII, 52 est le chiffre 4 et 54 est 6. Les deux étiquettes sont désormais dans le registre IANA. À noter que la représentation des adresses IP en CBOR avait été faite initialement avec les étiquettes 260 et 261, en utilisant un encodage complètement différent. 260 désignait les adresses (v4 et v6), 261, les préfixes. (Ces deux étiquettes sont marquées comme abandonnées, dans le registre IANA.) Au contraire, dans notre nouveau RFC, l'étiquette identifie la version d'IP, la distinction entre adresse et préfixe se faisant par un éventuel entier initial pour indiquer la longueur.
Date de publication du RFC : Décembre 2021
Auteur(s) du RFC : B. Laurie, A. Langley, E. Kasper, E. Messeri (Google), R. Stradling (Sectigo)
Expérimental
Réalisé dans le cadre du groupe de travail IETF trans
Première rédaction de cet article le 10 décembre 2021
Le système de gestion de certificats PKIX (dérivé des certificats X.509) a une énorme faiblesse. N'importe quelle AC peut émettre un certificat pour n'importe quel nom de domaine. Il ne suffit donc pas de bien choisir son AC, votre sécurité dépend de toutes les AC. Ce RFC propose une approche pour combler cette faille de sécurité : encourager/obliger les AC à publier « au grand jour » les certificats qu'elles émettent. Un titulaire d'un certificat qui craint qu'une AC n'émette un certificat à son nom sans son autorisation n'a alors qu'à surveiller ces publications. (Il peut aussi découvrir à cette occasion que sa propre AC s'est fait pirater ou bien est devenue méchante et émet des certificats qu'on ne lui a pas demandés. L'idée est aussi d'empêcher l'émission « discrète » de vrais/faux certificats qui seraient ensuite utilisés uniquement à certains endroits.) Ce système, dit « Certificate Transparency » (CT) avait initialement été normalisé dans le RFC 6962, que notre RFC remplace, le protocole passant à une nouvelle version, la v2 (toujours considérée comme expérimentale).
Le principe est donc de créer des journaux des certificats émis. Le journal doit être public, pour que n'importe qui puisse l'auditer (section 4 du RFC). Il doit être en mode « ajout seulement » pour éviter qu'on puisse réécrire l'histoire. Les certificats sont déjà signés mais le journal a ses propres signatures, pour prouver son intégrité. Conceptuellement, ce journal est une liste de certificats dans l'ordre de leur création. Toute AC peut y ajouter des certificats (la liste ne peut pas être ouverte en écriture à tous, de crainte qu'elle ne soit remplie rapidement de certificats bidons). En pratique, le RFC estime que la liste des AC autorisées à écrire dans le journal sera l'union des listes des AC acceptées dans les principaux navigateurs Web (voir aussi les sections 4.2 et 5.7, chaque journal est responsable de ce qu'il accepte ou pas comme soumissions).
À chaque insertion, le journal renvoie à l'AC une estampille
temporelle signéee (SCT, pour Signed Certificate
Timestamp), permettant à l'AC de prouver qu'elle a bien
enregistré le certificat. Si on a cette signature mais que le
certificat est absent du journal, l'observateur aura la preuve que
le journal ne marche pas correctement. Le format exact de cette
estampille temporelle est décrit en section 4.8. Idéalement, elle
devra être envoyée au client par les serveurs TLS, dans l'extension
TLS transparency_info
(désormais enregistrée
à l'IANA), comme preuve de la bonne foi de l'AC (cf. section
6 et notamment 6.5, car c'est plus compliqué que cela). Bien sûr,
cette validation de l'insertion dans un journal ne dispense pas de
la validation normale du certificat (un certificat peut être
journalisé et mensonger à la fois). Notez aussi que, si le serveur
TLS n'envoie pas toutes les données au client, celui-ci peut les
demander au journal (opérations /get-proof-by-hash
et
get-all-by-hash
) mais, ce faisant, il informe
le journal des certificats qui l'intéressent et donc, par exemple,
des sites Web qu'il visite.
De même, une extension à OCSP (RFC 6960) peut être utilisée pour appuyer les réponses OCSP. On peut même inclure les preuves d'inclusion dans le journal dans le certificat lui-même, ce qui permet d'utiliser des serveurs TLS non modifiés.
Les titulaires de certificats importants, comme Google, mais aussi des chercheurs, des agences de sécurité, etc, pourront alors suivre l'activité de ces journaux publics (section 8.2 du RFC). Ce qu'ils feront en cas de détection d'un certificat anormal (portant sur leur nom de domaine, mais qu'ils n'ont pas demandé) n'est pas spécifié dans le RFC : cela dépend de la politique de l'organisation concernée. Ce RFC fournit un mécanisme, son usage n'est pas de son ressort. Ce journal n'empêchera donc pas l'émission de vrais/faux certificats, ni leur usage, mais il les rendra visibles plus facilement et sans doute plus vite.
Notons que les certificats client, eux, ne sont typiquement pas journalisés (rappelez-vous que les journaux sont publics et que les certificats client peuvent contenir des données personnelles). Le serveur TLS ne peut donc pas utiliser Certificate Transparency pour vérifier le certificat du client. (Le RFC estime que le principal risque, de toute façon, est celui d'usurpation du serveur, pas du client.)
Pour que cela fonctionne, il faudra que les clients TLS vérifient que le certificat présenté est bien dans le journal (autrement, le méchant n'aurait qu'à ne pas enregistrer son vrai/faux certificat, cf. section 8.3 du RFC).
En pratique, la réalisation de ce journal utilise un arbre de Merkle, une structure de données qui permet de mettre en œuvre un système où l'ajout de certificats est possible, mais pas leur retrait, puisque chaque nœud est un condensat de ses enfants (voir aussi le RFC 8391). La section 2 du RFC détaille l'utilisation de ces arbres et la cryptographie utilisée. (Et les exemples en section 2.1.5 aident bien à comprendre comment ces arbres de Merkle sont utilisés.)
Le protocole utilisé entre les AC et le journal, comme celui
utilisé entre les clients TLS et le journal, est HTTP et le format des
données du JSON (section 5, qui décrit l'API). Ainsi, pour
ajouter un certificat nouvellement émis au journal géré sur
sunlight-log.example.net
, l'AC fera :
POST https://sunlight-log.example.net/ct/v2/submit-entry
et le corps de la requête HTTP sera un tableau JSON de certificats encodés en Base64. La réponse contiendra notamment l'estampille temporelle (SCT pour Signed Certificate Timestamp). S'il y a un problème, le client recevra une des erreurs enregistrées. Pour récupérer des certificats, le programme de surveillance fera par exemple :
GET https://sunlight-log.example.net/ct/v2/get-entries
D'autres URL permettront de récupérer les condensats cryptographiques contenus dans l'arbre de Merkle, pour s'assurer qu'il est cohérent.
Comme il n'existe (en octobre 2021) aucune mise en œuvre de la
version 2 du protocole, voici quelques exemples, utilisant des
journaux réels, et la version 1 du protocole (notez le
v1
dans l'URL). Pour trouver les coordonnées
des journaux, j'ai utilisé la liste
« officielle » du projet. Notez que tous les journaux qui y figurent ne fonctionnent pas
correctement. Notez aussi que, comme pour les AC ou les serveurs de clés PGP, il n'y a
pas de « journal de référence », c'est à chacun de choisir les
journaux où il va écrire, et qu'il va lire. Le script test-ct-logs-v1.py
teste la liste, et trouve :
50 logs are OK, 54 are currently broken
Si vous vous demandez pourquoi un même opérateur a plusieurs
journaux, c'est en partie parce qu'il n'est pas possible de faire
évoluer les algorithmes cryptographiques au sein d'un même journal
(cf. section 9 du RFC) et qu'il faut donc de temps en temps créer un
nouveau journal. Un journal est identifié par son URL, sa clé publique et
(en v2) par son OID. Par exemple, le journal
« Nimbus 2021 » de Cloudflare est en
https://ct.cloudflare.com/logs/nimbus2021/
et a
la clé
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAExpon7ipsqehIeU1bmpog9TFo4Pk8+9oN8OYHl1Q2JGVXnkVFnuuvPgSo2Ep+6vLffNLcmEbxOucz03sFiematg==
(je ne donne pas l'OID, il n'existe pas encore de journaux qui
utilisent la version 2 du protocole). Voici un exemple d'utilisation
(le STH est le Signed Tree Head, la valeur de la
racine de l'arbre de Merkle, cf. section 4.2.10) :
% curl -s https://ct.cloudflare.com/logs/nimbus2021/ct/v1/get-sth | jq . { "tree_size": 408013312, "timestamp": 1634739692384, "sha256_root_hash": "7GnGjI7L6O5fn8kQKTdJG2riShNTTbcjRP2WbLoZrvQ=", "tree_head_signature": "BAMARjBEAiAQ0gb6udc9e28ykUGUzl0HV8U5NlJhPVSTUF4JtXGSeQIgcSbZ9kRgttGzpFETFem4eCv7GgUYPUUnl7lTGGFZSHM=" }
Plus de quatre cents millions de certificats, fichtre. Si on veut récupérer les deux premiers certificats journalisés :
% curl -s https://ct.cloudflare.com/logs/nimbus2021/ct/v1/get-entries\?start=0\&end=1 | jq . { "entries": [ { "leaf_input": [L'exemple est fait avec un journal v1, l'objet JSON renvoyé est différent en v2.]
Mais vous pouvez aussi utiliser Certificate
Transparency (CT) sans aller regarder du JSON. Un service en
ligne comme
vous permet de
scruter un journal. Voici par exemple l'actuel certificat de ce
blog, ou bien tous les certificats au
nom de la Banque Postale (CT est
utile pour le renseignement).
https://crt.sh
On a vu que plusieurs acteurs intervenaient, le gérant du
journal, les AC, les gens qui regardent le journal, par exemple pour
l'auditer, etc. Une utilisation courante de CT est pour surveiller
l'émission de certificats au nom de son entreprise ou de son
organisation, pour repérer les AC qui créent des certificats
incorrects. Pour éviter de programmer tout cela de zéro en partant
du RFC, on peut utiliser le service Certstream, qui sert
d'intermédiaire avec plusieurs journaux, et sa bibliothèque
Python. Ainsi, le petit script test-certstream.py
permet de détecter tous les certificats
émis pour les noms de domaine en
.fr
:
% pip3 install certstream % ./test-certstream.py ... [2021-10-23T13:21:46] pimpmydrone.fr (SAN: www.pimpmydrone.fr) [2021-10-23T13:21:51] pascal-goldbach.fr (SAN: www.pascal-goldbach.fr) [2021-10-23T13:21:52] leginkobiloba.fr (SAN: www.leginkobiloba.fr) [2021-10-23T13:21:52] promabat-distribution.fr (SAN: www.promabat-distribution.fr) [2021-10-23T13:21:53] maevakaliciak.fr (SAN: mail.maevakaliciak.fr, www.maevakaliciak.fr) [2021-10-23T13:21:55] pascal-goldbach.fr (SAN: www.pascal-goldbach.fr) [2021-10-23T13:21:56] maevakaliciak.fr (SAN: mail.maevakaliciak.fr, www.maevakaliciak.fr) [2021-10-23T13:21:57] blog.nicolas-buffart.itval.fr (SAN: euromillions-generator.itval.fr, itval.fr, loto-generator.itval.fr, password-generator.itval.fr, www.blog.nicolas-buffart.itval.fr, www.euromillions-generator.itval.fr, www.itval.fr, www.loto-generator.itval.fr, www.password-generator.itval.fr) ...
Bien sûr, cela fait beaucoup (regardez les intervalles entre deux messages). En pratique, on modifierait sans doute ce script pour ne regarder que les noms de son organisation. Ainsi, vous pouvez détecter les certificats et chercher ensuite s'ils sont légitimes (ce qui, dans certaines organisations très cloisonnées n'ira pas de soi…).
À part Certstream, Samuel Bizien Filippi me suggère CertSpotter mais qui me semble uniquement payant. Il a une API. Elle peut être utilisée par le programme check_ct_logs, qui peut être utilisé comme script de test pour les programmes de supervision comme Icinga.
Le projet « Certificate Transparency » (largement impulsé par Google) a un site officiel (lecture recommandée) et, une liste de diffusion (sans compter le groupe de travail IETF TRANS, mais qui se limitait à la normalisation, il ne parle pas des aspects opérationnels, et il a de toute façon été clos en août 2021). Questions logiciels, si vous voulez créer votre propre journal, il y a le programme de Google.
Aujourd'hui, on peut dire que « Certificate Transparency » est un grand succès. La plupart (voire toutes ?) des AC y participent, il existe de nombreux journaux publics, et ils sont fréquemment utilisés pour l'investigation numérique (voire pour le renseignement, puisqu'on peut savoir, via les journaux, les noms de domaine pas encore annoncés, ce qui a parfois été cité comme une objection contre CT). Un bon exemple est celui de l'attaque « moyen-orientale » de 2018 (mais il y a aussi l'affaire du certificat révoqué de la Poste). Par contre, un client TLS ne peut pas encore être certain que tous les certificats seront dans ces journaux, et ne peut donc pas encore refuser les serveurs qui ne signalent pas la journalisation du certificat. Et le navigateur Firefox ne teste pas encore la présence des certificats dans le sjournaux.
Un point amusant : le concept de « Certificate Transparency » montre qu'il est parfaitement possible d'avoir un livre des opérations publiquement vérifiable sans chaine de blocs. La chaine de blocs reste nécessaire quand on veut autoriser l'écriture (et pas juste la lecture) au public.
La section 1.3 du RFC résume les principaux changements entre les versions 1 (RFC 6962) et 2 du protocole :
get-all-by-hash
dans
l'API,signed_certificate_timestamp
(valeur 18) par
transparency_info
(valeur 52, et voir aussi le nouvel
en-tête HTTP
Expect-CT:
du RFC 9163,CT ne change pas de statut avec la version 2 : il est toujours classé par l'IETF comme « Expérimental » (bien que largement déployé). La sortie de cette v2 n'est pas allée sans mal (le premier document étant sorti en février 2014), avec par exemple aucune activité du groupe pendant la deuxième moitié de 2020.
Une des plus chaudes discussions pour cette v2 avait été la
proposition de changer l'API pour que les requêtes, au lieu d'aller
à <BASE-URL>/ct/v2/
partent du chemin
/.well-known/
du RFC 8615. Cette idée a finalement été rejetée, malgré le RFC 8820, qui s'oppose à cette idée de chemins
d'URL en dur.
Date de publication du RFC : Novembre 2021
Auteur(s) du RFC : P. Hoffman (ICANN)
Chemin des normes
Réalisé dans le cadre du groupe de travail IETF dnsop
Première rédaction de cet article le 1 décembre 2021
Rien de très grave dans ce nouveau RFC, qui règle un problème surtout bureaucratique, le fait que les politiques d'inclusion dans les registres IANA pour certains algorithmes utilisés par DNSSEC n'étaient pas parfaitement alignées.
En effet, le RFC 6014 avait modifié la politique d'enregistrement des algorithmes cryptographiques de « Action de normalisation » à la plus laxiste « RFC nécessaire » (rappelez-vous que tous les RFC ne sont pas des normes, voir le RFC 8126 qui décrit ces politiques possibles). Mais cette « libéralisation » laissait de côté certains algorithmes, ceux utilisés pour les enregistrements DS (RFC 4034), et ceux utilisés pour NSEC3 (RFC 5155), qui restaient en « Action de normalisation ». Notre nouveau RFC aligne les politiques d'enregistrement des algorithmes utilisés pour les DS et pour NSEC3 pour qu'ils soient eux aussi « RFC nécessaire ».
Il modifie également le RFC 8624 pour préciser que les algorithmes normalisés dans des RFC qui ne sont pas sur le chemin des normes sont également couverts par les règles du RFC 8624 ; en gros, ils sont facultatifs (MAY dans le langage du RFC 2119).
Les registres concernés sur celui sur NSEC3 et celui sur DS. Ils portent désormais la mention RFC Required.
Comme l'enregistrement d'algorithmes va, du fait de ce RFC, être plus léger, cela facilitera l'enregistrement de bons algorithmes, mais aussi de mauvais. Le programmeur qui met en œuvre DNSSEC, ou l'administratrice système qui le déploie, ne doit donc pas considérer que la présence dans un registre IANA vaut forcément approbation de la solidité cryptographique de l'algorithme. Il faut consulter la littérature technique avant d'utiliser ces algorithmes.
Date de publication du RFC : Novembre 2021
Auteur(s) du RFC : S. Bortzmeyer (AFNIC), R. Dolmans (NLnet Labs), P. Hoffman (ICANN)
Chemin des normes
Réalisé dans le cadre du groupe de travail IETF dnsop
Première rédaction de cet article le 19 novembre 2021
Protéger la vie privée sur l'Internet nécessite au moins deux techniques : chiffrer les données en transit pour éviter leur lecture par des tiers et minimiser les données qu'on envoie, pour éviter les abus par les récepteurs des données. Ce deuxième point, pourtant bien mis en avant dans la loi Informatique & Libertés ou dans le RGPD est souvent oublié. Ce RFC applique ce principe au DNS : il ne faut pas envoyer aux serveurs faisant autorité le nom de domaine complet mais seulement la partie du nom de domaine qui lui est strictement nécessaire pour répondre, le minimum. Cette norme succède au RFC 7816, qui était purement expérimental alors que cette minimisation de la requête (QNAME minimisation) est désormais une norme. Le principal changement est la recommandation d'utiliser le type de données A (adresse IPv4) et plus NS (serveurs de noms).
Ce principe de minimisation, qui devrait être central dans toute
approche de la protection de la vie privée
est également exposé dans le RFC 6973, section
6.1. Le DNS violait
ce principe puisque, traditionnellement, un résolveur DNS qui recevait une demande
d'information sur www.foobar.example
transmettait aux serveurs
faisant autorité la question complète, alors que, par
exemple, les serveurs faisant autorité pour la
racine ne connaissent que les TLD et que leur demander simplement
des informations sur le TLD .example
aurait
suffi. (Voir le RFC 7626 pour une analyse
complète des relations entre le DNS et la vie privée.) Cette
tradition (qui ne s'appuyait sur aucune norme technique) est remise
en cause par la QNAME minimisation qui demande au
contraire qu'on n'envoie aux serveurs faisant autorité que le nom
minimal (example
à la racine,
foobar.example
aux serveurs du TLD
.example
, etc).
Cette minimisation est unilatérale, elle ne nécessite qu'un changement des résolveurs, sans toucher aux serveurs faisant autorité puisqu'elle ne change pas le protocole DNS. Depuis la sortie du RFC 7816, en 2016, elle a été largement déployée (si le résolveur que vous utilisez ne le fait pas, réclamez-le à votre service informatique !).
Le précédent RFC sur cette technique, le RFC 7816 avait le statut d'expérimentation alors que notre RFC 9156 est désormais une norme. En effet, une expérience considérable a été accumulée depuis le RFC 7816, qui a été mis en œuvre dans pratiquement tous les résolveurs, et souvent activé. Le FUD souvent entendu comme quoi la QNAME minimisation allait tuer Internet et des chatons a été largement réfuté. Les leçons tirées sont documentées dans « DNSThought QNAME minimisation results. Using Atlas probes », « Maximizing Qname Minimization: A New Chapter in DNS Protocol Evolution », « Measuring Query Name Minimization » et « A First Look at QNAME Minimization in the Domain Name System ».
Maintenant, la pratique, comment fait-on de la QNAME
minimisation ? La question envoyée par le résolveur au
serveur faisant autorité comprend un QNAME (Query
Name, le nom demandé) et un QTYPE (Query
Type, le type de données, par exemple serveur de courrier,
adresse IP, texte libre, etc). Avec la QNAME
minimisation, le nom doit être le nom le plus court
possible. Quand le résolveur interroge un serveur
racine, il n'envoie comme QNAME que le TLD, par
exemple. Trouver « le plus court possible » n'est pas forcément
trivial en raison des coupures de zone. Dans un nom comme
miaou.foo.bar.example
,
foo.bar.example
et
bar.example
font peut-être partie de la même
zone (et ont donc les mêmes serveurs faisant autorité) et peut-être
pas. Rien dans la syntaxe du nom ne l'indique. Contrairement à une
idée fausse et répandue, il n'y a pas forcément une coupure de zone
pour chaque point dans le nom. Trouver les
coupures de zone est expliqué dans le RFC 2181, section 6. Un résolveur qui valide avec DNSSEC
doit savoir trouver ces coupures, pour savoir à qui demander les
enregistrements de type DS. Les autres (mais quelle idée, en 2021,
d'avoir un résolveur qui ne valide pas) doivent s'y mettre. Si, par
exemple, foo.bar.example
et
bar.example
sont dans la même zone, le
résolveur qui veut trouver des données associées à
miaou.foo.bar.example
va envoyer le QNAME
example
à la racine, puis
bar.example
au serveur du TLD, puis
miaou.foo.bar.example
au serveur de
bar.example
. (Avant la QNAME
minimisation, il aurait envoyé le QNAME
miaou.foo.bar.example
à tout le monde.)
Cela, c'était pour le QNAME. Et le QTYPE ? On peut choisir celui qu'on veut (à l'exception de ceux qui ne sont pas dans la zone, comme le DS), puisque les délégations de zones ne dépendent pas du type. Mais, et c'est un sérieux changement depuis le RFC 7816, notre RFC recommande le type A (ou AAAA), celui des adresses IP, et plus le type NS (les serveurs de noms), que recommandait le RFC 7816. Deux raisons à ce changement :
Vous voyez ici le schéma de la résolution DNS sans la QNAME minimisation puis avec :
Dans certains cas, la QNAME minimisation peut
augmenter le nombre de requêtes DNS envoyées par le résolveur. Si un
nom comporte dix composants (ce qui arrive dans des domaines
ip6.arpa
), il faudra dans certains cas dix
requêtes au lieu de deux ou trois. Les RFC 8020 et RFC 8198
peuvent aider à diminuer ce nombre, en permettant la synthèse de
réponses par le résolveur. Une autre solution est de ne pas ajouter
un composant après l'autre en cherchant le serveur faisant autorité
mais d'en mettre plusieurs d'un coup, surtout après les quatre
premiers composants.
Un algorithme complet pour gérer la QNAME
minimisation
figure dans la section 3 du RFC.
Notez que, si vous voulez voir si votre résolveur fait de la QNAME minimisation, vous pouvez utiliser tcpdump pour voir les questions qu'il pose mais il y a une solution plus simple, la page Web de l'OARC (dans les DNS features).
Un test avec les sondes RIPE Atlas semble indiquer que la QNAME minimisation est aujourd'hui largement répandue (les deux tiers des résolveurs utilisés par ces sondes) :
% blaeu-resolve --requested 1000 --type TXT qnamemintest.internet.nl ["hooray - qname minimisation is enabled on your resolver :)!"] : 651 occurrences ["no - qname minimisation is not enabled on your resolver :("] : 343 occurrences Test #33178767 done at 2021-11-05T14:41:02Z
Il existe aussi une étude récente sur la QNAME minimization en République Tchèque.
Comme son prédécesseur, ce RFC utilise (prétend Verisign) un brevet. Comme la plupart des brevets logiciels, il n'est pas fondé sur une réelle invention (la QNAME minimisation était connue bien avant le brevet).
Ah, et vous noterez que le développement de ce RFC, par trois auteurs, a été fait sur FramaGit.
Date de publication du RFC : Décembre 2021
Auteur(s) du RFC : L.V. Velvindron (cyberstorm.mu), K.M. Moriarty (CIS), A.G. Ghedini (Cloudflare)
Chemin des normes
Réalisé dans le cadre du groupe de travail IETF tls
Première rédaction de cet article le 20 décembre 2021
Vous le savez certainement déjà, car toutes les lectrices et tous les lecteurs de ce blog sont très attentif·ves et informé·es, mais les algorithmes de condensation MD5 et SHA-1 ont des failles connues et ne doivent pas être utilisés dans le cadre de signatures. Vous le savez, mais tout le monde ne le sait pas, ou bien certain·es ont besoin d'un document « officiel » pour agir donc, le voici : notre RFC dit qu'on ne doit plus utiliser MD5 et SHA-1 dans TLS.
Si vous voulez savoir pourquoi ces algorithmes sont mauvais, le RFC 6151 vous renseignera (et la section 1 de notre RFC 9155 vous donnera une bibliographie récente).
La section 2 à 5 sont le cœur du RFC et elle est sont très simples : pas de MD5, ni de SHA-1 pour les signatures. Dans le registre IANA, ces algorithmes sont désormais marqués comme déconseillés.
Les fanas de cryptographie noteront qu'on peut toujours utiliser SHA-1 pour HMAC (où ses faiblesses connues n'ont pas de conséquences).
Date de publication du RFC : Décembre 2021
Auteur(s) du RFC : J. Gould, R. Wilhelm (VeriSign)
Chemin des normes
Réalisé dans le cadre du groupe de travail IETF regext
Première rédaction de cet article le 31 décembre 2021
Le protocole EPP d'avitaillement des noms de domaine permet, entre autres opérations, de transférer un domaine d'un client (typiquement un BE) à un autre. Cette opération ouvre de sérieux problèmes de sécurité, le transfert pouvant être utilisé pour détourner un nom de domaine. En général, la sécurisation de ce transfert est faite par un mot de passe stocké en clair. Notre RFC décrit une méthode pour gérer ces mots de passe qui évite ce stockage, et qui gêne sérieusement les transferts malveillants.
EPP est normalisé dans le RFC 5730 et c'est ce document qui décrit le cadre général
d'autorisation d'un transfert de domaines. La forme exacte que prend
l'autorisation dépend du type d'objets qu'on gère avec EPP. Pour les
domaines (RFC 5731), c'est un élément
<authInfo>
, qui peut contenir divers
types de sous-élements mais le plus fréquent est un simple
mot de passe (parfois appelé « code de
transfert » ou « code d'autorisation », ou simplement « authinfo »),
comme dans cet exemple, une réponse à une commande EPP
<info>
, où le mot de passe est
« 2fooBAR » :
<domain:infData> <domain:name>example.com</domain:name> ... <domain:crDate>1999-04-03T22:00:00.0Z</domain:crDate> ... <domain:authInfo> <domain:pw>2fooBAR</domain:pw> </domain:authInfo> </domain:infData>
L'utilisation typique de ce mot de passe est que le client (en général un BE) le crée, le stocke en clair, l'envoie au serveur, qui le stocke. Lors d'un transfert légitime, le BE gagnant recevra ce mot de passe (typiquement via le titulaire de l'objet, ici un nom de domaine) et le transmettra au registre (RFC 5731, section 3.2.4). Ici, le BE gagnant demande à son client le code d'autorisation qu'il a normalement obtenu via le BE perdant (si le client est vraiment le titulaire légitime) : Et ici le BE perdant indique à son client le code d'autorisation : (registar = BE)
Notez que la façon d'autoriser les transferts, et d'accéder aux informations d'autorisation, dépend de la politique du registre. Les RFC sur EPP normalisent la technique mais pas la politique. Par exemple, lorsqu'un BE demande un transfert sans fournir d'information d'autorisation, certains registres refusent immédiatement le transfert, tandis que d'autres le mettent en attente d'une acceptation ou d'un refus explicite. De même, certains registres permettent de récupérer l'information d'autorisation, comme dans l'exemple ci-dessus, alors que d'autres (comme CentralNic) refusent.
Même chose pour d'autres types d'objets comme les contacts (RFC 5733) même si en pratique la pratique du transfert est plus rare pour ces types. Le mot de passe étant typiquement stocké en clair chez le client, pour pouvoir être donné en cas de transfert, on voit les risques que cela pose en cas d'accès à la base du BE. Aujourd'hui, stocker un mot de passe en clair est nettement considéré comme une mauvaise pratique de sécurité.
À la place, notre RFC décrit, non pas une modification du protocole EPP, mais une nouvelle procédure, une façon créative de se servir du protocole existant pour gérer ces informations d'autorisation de manière plus sérieuse : l'objet (par exemple le nom de domaine) est créé sans information d'autorisation, le serveur EPP (par exemple le registre de noms de domaine) doit refuser le transfert si cette information est manquante. Lors d'un transfert légitime, le client (par exemple un BE) perdant va générer un mot de passe, le transmettre au registre et à son client (typiquement le titulaire du nom de domaine) et ne pas le stocker. Le registre stockera le mot de passe uniquement sous forme condensée et, lorsqu'il recevra la demande de transfert accompagnée d'un mot de passe, il pourra condenser ce mot et vérifier qu'il correspond bien à celui stocké. Le mot ne servira qu'une fois et l'information d'autorisation est détruite après le succès du transfert. Tout ceci ne nécessite pas de modification du protocole, mais, dans certains cas, une modification des pratiques des différents acteurs (par exemple, le serveur EPP doit accepter qu'un objet soit créé sans information d'autorisation, et doit considérer que cela vaut refus de tout transfert).
Le RFC note que la norme EPP ne décrit, logiquement, que le protocole, c'est-à-dire l'interaction entre les deux machines, mais pas ce que fait chaque machine de son côté. Ainsi, la nécessité de stocker les mots de passe de manière sécurisée n'est pas imposée par EPP (mais est néanmoins une bonne pratique).
D'autre part, EPP ne prévoit pas explicitement de durée de vie pour les mots de passe (mais n'interdit pas non plus de les supprimer au bout d'un temps donné, ce qui va être justement la technique de notre RFC).
Petite révision sur les acteurs de l'avitaillement de noms de domaine en section 2 du RFC. La norme EPP parle de client et de serveur, notions techniques, mais du point de vue business, il y a trois acteurs (cf. la terminologie dans le RFC 8499), le registre (qui gère le serveur EPP), le bureau d'enregistrement (BE, qui gère le client EPP) et le titulaire (qui se connecte à son BE via une interface Web ou une API). Dans beaucoup de domaines d'enregistrement, il n'y a aucun lien direct entre le titulaire et le registre, tout devant passer par le BE.
Maintenant, place à la description de la nouvelle manière de faire
des transferts. Bien qu'elle ne change pas le protocole, qu'elle ne
soit qu'une nouvelle façon d'utiliser ce qui existe déjà dans EPP,
elle doit se signaler lors de la connexion EPP, avec
l'espace de noms
urn:ietf:params:xml:ns:epp:secure-authinfo-transfer-1.0
(enregistré
à l'IANA). En effet, la nouvelle manière a besoin que le
serveur accepte des choses qui sont autorisées par EPP mais pas
obligatoires, notamment :
NULL
dans une base SQL),<update>
,<info>
,
Le serveur, lui, en recevant
urn:ietf:params:xml:ns:epp:secure-authinfo-transfer-1.0
,
peut compter que le client EPP saura :
L'autorisation d'information dans l'élement XML
<domain:pw>
(RFC 5731, section 3.2.4) est un mot de passe qui doit être
difficile à deviner par un attaquant. Idéalement, il doit être
aléatoire ou équivalent (RFC 4086). Le RFC
calcule que pour avoir 128 bits d'entropie, avec uniquement les
caractères ASCII imprimables, il faut environ 20
caractères.
Pour compenser l'absence de la notion de durée de vie de l'information d'autorisation dans EPP, le client ne doit définir une information d'autorisation que lorsqu'un transfert est demandé, et supprimer cette information ensuite. La plupart du temps, le domaine n'aura pas d'information d'autorisation, et les transferts seront donc refusés.
L'information d'autorisation, comme tout mot de passe, ne doit plus être stockée en clair, mais sous forme d'un condensat. Le BE perdant ne doit pas la stocker (il la génère, la passe au titulaire et l'oublie ensuite). Le BE gagnant ne doit la stocker que le temps de finaliser le transfert. Évidemment, toute la communication EPP doit être chiffrée (RFC 5734). Lors d'une demande de transfert, le registre va vérifier qu'un condensat de l'information d'autorisation transmise par le BE gagnant correspond à ce que le BE perdant avait envoyé. L'information vide est un cas particulier, le registre ne doit pas tester l'égalité mais rejeter le transfert.
La section 4 explique en détail le processus de transfert avec cette nouvelle méthode :
Voici en EPP quelques messages pour réaliser ces différentes opérations. D'abord, la création d'un nom (notez le mot de passe vide) :
<create> <domain:create xmlns:domain="urn:ietf:params:xml:ns:domain-1.0"> <domain:name>example.test</domain:name> <domain:authInfo> <domain:pw/> </domain:authInfo> </domain:create> </create>
Ici, la mise à jour de l'information d'autorisation par le BE
perdant, lorsque le titulaire lui a annoncé le départ du domaine ;
le mot de passe est
LuQ7Bu@w9?%+_HK3cayg$55$LSft3MPP
(le RFC
rappelle fortement l'importance de générer un mot de passe fort, par
exemple en utilisant des sources bien aléatoires, comme documenté
dans le RFC 4086) :
<update> <domain:update xmlns:domain="urn:ietf:params:xml:ns:domain-1.0"> <domain:name>example.test</domain:name> <domain:chg> <domain:authInfo> <domain:pw>LuQ7Bu@w9?%+_HK3cayg$55$LSft3MPP</domain:pw> </domain:authInfo> </domain:chg> </domain:update> </update>
Le BE perdant devra peut-être également supprimer l'état
clientTransferProhibited
, si le domaine était
protégé contre les transferts.
Le BE gagnant peut également vérifier l'information
d'autorisation sans déclencher un transfert, avec une requête
<info>
, qui lui renverra l'information
d'autorisation. Pour plusieurs exemples par la suite, j'ai utilisé le
logiciel Cocca. Cocca,
par défaut, ne stocke pas l'autorisation d'information en clair et
ne peut donc pas la renvoyer.
Ou bien le client EPP peut envoyer une commande
<info>
en indiquant l'information
d'autorisation. S'il obtient une erreur EPP 2202 (RFC 5730, section 3), c'est que cette
information n'était pas correcte. Ici, la
réponse EPP de Cocca lorsqu'on lui envoie un
<info>
avec information d'autorisation correcte :
Client : <info xmlns="urn:ietf:params:xml:ns:epp-1.0"><info xmlns="urn:ietf:params:xml:ns:domain-1.0"><name>foobar.test</name><authInfo xmlns="urn:ietf:params:xml:ns:domain-1.0"><pw xmlns="urn:ietf:params:xml:ns:domain-1.0">tropfort1298</pw></authInfo></info></info> Serveur : ... <ns1:authInfo><ns1:pw>Authinfo Correct</ns1:pw></ns1:authInfo> ...
Et si cette information est incorrecte :
Serveur : ... <ns1:authInfo><ns1:pw>Authinfo Incorrect</ns1:pw></ns1:authInfo>
(Mais Cocca répond quand même avec un code EPP 1000, ce qui n'est pas correct.)
Et enfin, bien sûr, voici la demande de transfert elle-même :
<transfer op="request"> <domain:transfer xmlns:domain="urn:ietf:params:xml:ns:domain-1.0"> <domain:name>example1.com</domain:name> <domain:authInfo> <domain:pw>LuQ7Bu@w9?%+_HK3cayg$55$LSft3MPP</domain:pw> </domain:authInfo> </domain:transfer> </transfer>
Et si c'est bon :
<ns0:response xmlns:ns0="urn:ietf:params:xml:ns:epp-1.0" xmlns:ns1="urn:ietf:params:xml:ns:domain-1.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"><ns0:result code="1000"><ns0:msg>Command completed successfully</ns0:msg></ns0:result><ns0:msgQ count="2" id="3" /> <ns1:trStatus>serverApproved</ns1:trStatus> ...
Et avec une mauvaise information d'autorisation :
<ns0:response xmlns:ns0="urn:ietf:params:xml:ns:epp-1.0"><ns0:result code="2202"><ns0:msg>Invalid authorization information; (T07) Auth Info Password incorrect</ns0:msg></ns0:result>...
La section 6 du RFC décrit le problème de la transition depuis
l'ancien modèle d'autorisation vers le nouveau. Notez que certains
registres peuvent avoir une partie du nouveau système déjà en
place. Le registre qui désire transitionner doit d'abord s'assurer
que l'information d'autorisation absente ou vide équivaut à un
rejet. Il doit ensuite permettre aux BE de mettre une information
d'autorisation vide, permettre que la commande
<info>
puisse tester une information
d'autorisation, s'assurer que l'acceptation d'un transfert supprime
l'information d'autorisation, etc.
L'extension à EPP décrite dans ce RFC a été enregistrée dans le registre des extensions EPP. Quelles sont les mises en œuvre de ce RFC ? Cocca, déjà cité, le fait partiellement (par exemple en ne stockant pas les mots de passe en clair). Je n'ai pas testé avec ce logiciel ce qui se passait avec une information d'autorisation vide. Sinon, CentralNic a déjà ce mécanisme en production. Et Verisign l'a mis dans son SDK.
Date de publication du RFC : Février 2022
Auteur(s) du RFC : S. Card, A. Wiethuechter (AX Enterprize), R. Moskowitz (HTT Consulting), A. Gurtov (Linköping University)
Pour information
Réalisé dans le cadre du groupe de travail IETF drip
Première rédaction de cet article le 11 février 2022
Il y a aujourd'hui beaucoup plus de drones que d'avions pilotés. Ces drones, qui se déplacent dans un espace partagé, soulèvent un certain nombre de questions de sécurité, ce qui justifie des mécanismes obligatoires d'immatriculation et d'identification. Dans tous les pays, des règles imposent aux drones ou au moins à certains d'entre eux de diffuser leur identité. Mais, ensuite, une fois que l'observateur du drone a cette identité, qu'en fait-on ? Le but du projet DRIP à l'IETF est de mettre au point des mécanismes pour utiliser cette identité. Ce RFC est le premier du projet, dédié à établir les exigences pour les futurs protocoles. Ce n'est pas gagné, vu qu'il y a beaucoup de grosses organisations sur le chemin qui vont privilégier des solutions fermées.
Pour aider à comprendre le problème, imaginons le scénario suivant. Un gardien fait sa ronde autour d'une centrale nucléaire. Un drone approche. Sa présence ici est-elle normale ? Est-il encore sous contrôle ou bien a-t-il échappé à son propriétaire ? Peut-on contacter son propriétaire pour vérifier ? S'il s'agissait d'un avion piloté traditionnel, les règles de circulation aérienne imposeraient tout un tas de contraintes au pilote (par exemple d'enregistrement de son vol à l'avance), qui permettraient d'éviter des incertitudes. On ne peut pas imposer des procédures aussi lourdes à un simple drone de loisirs. Mais on ne peut pas laisser non plus l'espace partagé qu'est l'air sans règles minimales. Prenons un autre scénario : un drone se promène au-dessus de votre jardin. Il a sans doute une caméra, comme la plupart des drones. Comment faire en sorte qu'il déguerpisse au lieu de vous filmer tranquillement en train de bronzer ? Peut-on lui tirer dessus, comme ça se fait aux États-Unis (« get off my lawn »), s'il ne répond pas ? Faut-il faire des sommations, et comment ? Comme l'analyse correctement la Quadrature du Net, le danger des drones ne vient pas que d'individus malveillants, une utilisation de ces engins par l'État crée également de nombreux risques.
Depuis quelques années, de nombreuses règles ont été édictées dans divers pays. Ainsi, en France, l'arrêté du 29 décembre 2019 impose la signalisation électronique des appareils pour tous les drones de plus de 800 grammes (cet article du Monde résume les obligations). Le drone doit être enregistré sur le site officiel AlphaTango (uniquement le drone, pas chaque vol, contrairement aux obligations des avions). Aux États-Unis, il existe des règles similaires . La plupart du temps, l'obligation minimale est que le drone diffuse son identité, de la même façon qu'une voiture a une plaque d'immatriculation. Mais cette règle est très insuffisante pour répondre à tous les scénarios. Le drone peut facilement mentir sur son identité. Et puis à quoi sert cette identité si elle ne fournit pas de moyen de retrouver le propriétaire ? Et ne serait-ce pas souhaitable d'avoir un moyen de contacter celui-ci en temps réel ? Ces questions ne sont pas évidentes et, comme toutes les questions liées à la sécurité, soulèvent d'innombrables problèmes politiques, juridiques et techniques. L'IETF se concentre sur une partie limitée de ces problèmes : ne pouvons-nous pas utiliser notre expérience en matière de protocoles réseau pour développer des mécanismes permettant de retrouver des données sur le drone à partir de son identité et, pourquoi pas, permettant de communiquer avec lui ? Voyons d'abord en détail le problème, les exigences de notre RFC pour les futurs protocoles ne seront présentées qu'à la fin de cet article.
Ah, si vous voyez ce drone, c'est le mien :
La section 1 de notre RFC détaille pourquoi les drones posent des problèmes. Il existe plusieurs types de drones (à aile fixe, à aile tournante, capables de décollage vertical ou non, avec un ou plusieurs moteurs…). Le type le plus répandu et le plus connu, notamment grâce aux petits drones de loisir, est le multi-moteur à ailes tournantes (multicoptère). Disposant de systèmes de stabilisation automatiques, ils peuvent être pilotés par des amateurs, contrairement aux avions et hélicoptères. Mais il existe aussi des drones dont la taille et le prix rappelle davantage un avion. Être pilotés à distance par un humain tenant un joystick n'est pas la seule possibilité : doté d'un récepteur GPS, certains drones sont partiellement autonomes et, dans le futur, seront totalement autonomes.
Un petit drone, comme celui que vous achetez pour quelques dizaines d'euros en magasin, a des propriétés importantes pour la sécurité de l'espace aérien :
Un drone peut donc être très utile pour l'espionnage, voire l'attaque. Il peut porter une caméra pour surveiller, et des explosifs pour attaquer (cf. l'attentat contre le premier ministre irakien en novembre 2021). Même sans explosifs, le drone peut jouer un rôle de projectile, involontaire ou volontaire. Un article de Kaspersky donne une bonne idée des dangers possibles (mais notez qu'il est publié par une entreprise qui vend des solutions et peut donc être soupçonnée de dramatiser).
Il n'y a évidemment pas de solution magique à ces risques mais la plupart des approches nécessitent a priori qu'on découvre un identificateur du drone, qu'on ait une solution RID (Remote IDentification and tracking). Dans le scénario typique de RID, un ou plusieurs drones volent dans un espace restreint, et des observateurs, certains ayant un rôle particulier (policiers, par exemple) et d'autres étant du grand public, veulent obtenir un identificateur, qui va être le point de départ de leurs actions (s'informer sur ce drone, peut-être le contacter). Il est donc prévu un ou plusieurs registres d'identificateurs, et des informations associées. Il existe déjà des normes pour cela, comme la F3411-19, développée par le comité F38 de l'ASTM. Elle coûte 91 dollars étatsuniens. Comme beaucoup de normes développées par des organismes privés, elle n'est pas librement et gratuitement disponible, mais on peut apparemment trouver des brouillons en ligne. F3411-19 est la référence dans ce domaine, normalisant les messages que le drone doit envoyer pour annoncer son identifiateur mais, pour l'instant, c'est à peu près tout, on ne peut pas forcément faire grand'chose de cet identificateur. Elle ne précise pas comment l'observateur se renseigne sur les personnes ou organisations derrière un identificateur. Elle ne traite pas la communication entre drones ou avec l'observateur. Elle n'a pas de mécanisme d'authentification de l'identificateur. Le RFC, étant surtout développé par des Étatsuniens, est très inspiré par le travail de l'ASTM, qui ne s'applique pas forcément partout dans le monde. En Europe, ASD-STAN, associée à CEN, publie une norme DIN EN 4709-002:2021-02 (également chère et pas libre). Un résumé est disponible en ligne. Cette norme décrit un système de DRI (Drone Remote Identification) où le drone diffuse par radio :
Un exemple d'identificateur de drone (celui donné par la norme) est
MFR1C123456789ABC
. (Le C en cinquième position
est l'encodage de la longueur du numéro de série, ici 123456789ABC
).
Même avec ces normes, de nombreux problème subsistent. Si trois drones sont proches, chacun diffusant son identité, et qu'un seul des trois a un comportement inquiétant, comment distinguer les trois identités ? Et puis les informations envoyées peuvent être erronnées (les drones bon marché n'incluent pas du matériel de mesure perfectionné) voire mensongères, notamment si le drone est malveillant.
Notez aussi que les groupes techniques comme DRIP ne peuvent qu'établir des normes techniques. Définir des règles politiques est une autre affaire. Et les faire respecter sera largement l'affaire des CAA.
Autre problème, comme le drone diffuse des informations en clair, tout le monde peut les capter et certaines informations (comme l'identificateur de l'opérateur) sont des données personnelles, ce qui soulève des questions de vie privée. On a là un beau débat politique en perspective, entre intimité et transparence.
Le RFC note qu'un déploiement massif d'un système de RID (Remote IDentification and Tracking) est à la fois urgent et important. Le déploiement doit être massif car, comme le dit le RFC, « quel serait l'intérêt des plaques minéralogiques si seulement 90 % des voitures en avaient ? ».
Un autre défi des systèmes d'identification réside dans le caractère capricieux des ondes radio. Elles peuvent être bloquées par des bâtiments ou du feuillage. Si le drone émet avec davantage de puissance, il va épuiser rapidement sa batterie. Et il aggravera la congestion dans cet espace partagé (d'autant plus que le drone émet dans des fréquences où une licence n'est pas nécessaire). Bref, d'une manière générale, la liaison radio sera lente et non fiable.
En parlant de batterie, il faut noter que le drone est typiquement un objet contraint (au sens du RFC 8352). Son « budget » en énergie, puissance de calcul et poids (ce qu'on désigne en général par le sigle CSWaP, Cost, Size, Weight, and Power, parfois écrit $SWaP) est très limité. Cela impose des protocoles de communication simples et frugaux, ce qui va rendre difficile l'utilisation de la cryptographie. Actuellement, il y a zéro authentification : un drone mensonger peut annoncer l'identificateur qu'il veut, sans qu'on puisse vérifier. (Dans des réunions, j'ai entendu des affirmations ridicules, comme de confondre la somme de contrôle avec un mécanisme d'authentification. Comme souvent en cybersécurité, on entend beaucoup de n'importe quoi dans les réunions.)
Et il n'y a pas que le drone, il y a aussi la machine de l'observateur. Dans des zones reculées, on ne peut pas compter sur une connectivité Internet permanente, et c'est une des raisons du choix du Broadcast RID (le drone diffuse son identificateur, également appelé Direct ID) plutôt que du Network ID (l'observateur obtient l'identificateur via un réseau public, typiquement l'Internet). Mais si vous voulez en savoir plus sur ce débat Network ID vs. Broadcast ID, voyez l'article « Why Did the FAA Go with Broadcast Remote ID for Drones Over Network? ».
Bref, les mécanismes actuellement normalisés et déployés sont très insuffisants. Le projet DRIP de l'IETF vise, non pas à remplacer les normes existantes, mais à les compléter pour traiter les manques. Ce RFC est le cahier des charges du projet.
Le monde des drones utilise une grande quantité d'acronymes. La section 2 du RFC en donne une liste complète, mais il faut au moins connaitre :
Pour les RID, les identificateurs des drones, il existe plusieurs textes normatifs. Le plus répandu est F3411-19, déjà mentionné, qui décrit les notions de Network RID et Broadcast RID. Pour le premier, l'observateur va récolter l'information via le réseau (le drone s'étant au préalable enregistré, on est dans le publish-subscribe), alors que dans le cas du second, il va écouter directement ce que diffuse le drone par ondes radio. Comme l'observateur, le pilote du drone et surtout le drone lui-même n'ont pas forcément d'accès à l'Internet, c'est le Broadcast RID qui est privilégié. Il fonctionne donc indépendamment de l'Internet.
Le Broadcast RID soulève de nombreux problèmes techniques, notamment en raison des caractéristiques physiques des ondes radio. Si on utilise Bluetooth, on a de sérieuses contraintes de taille des données, au cas où on veuille transmettre, en sus de l'identificateur, des informations de position, de vitesse et de route. En outre, les normes existantes ne standardisent pas les couches basses (Bluetooth 4, 5, Wifi NAN, Wifi Beacon…), donc il sera difficile de capter tous les drones, et surtout pas avec n'importe quel ordiphone, le drone émettant peut-être avec une technologie que l'ordiphone ne connait pas. En outre, sur beaucoup d'ordiphones, on n'a pas accès direct à Bluetooth (cf. les problèmes de l'application TousAntiCovid) et/ou pas accès aux paquets bruts, ce qui va compliquer la vie des développeurs. (Le projet AltBeacon vise à traiter ce problème.)
Comment est attribué le RID ? F3411-19 propose plusieurs méthodes :
Actuellement, l'Union Européenne impose le premier type, les États-Unis le premier ou le troisième. La situation est compliquée, et le choix fait a des implications évidentes en terme de vie privée (un identificateur statique permet de suivre à la trace un drone). C'est d'autant plus important que cette information, étant diffusée unilatéralement, ne peut pas être chiffrée, les protocoles de type défi-réponse étant inutilisables. Pour prendre un exemple, Amazon ne serait sans doute pas ravi qu'un concurrent puisse suivre précisement leurs drones de livraison.
La simple diffusion du RID est donc déjà un problème compliqué. Mais d'autres organismes que l'IETF s'en occupent. Par contre, cette diffusion laisse entier un autre problème : une fois qu'on a récupéré le RID, qu'en fait-on ? Comment, par exemple, communiquer avec le pilote du drone ? Si le message contenait, en sus de l'identificateur, la position du pilote, on peut toujours s'y rendre. Cela peut être lent, voire infaisable si le pilote est, par exemple, de l'autre côté d'une voie ferrée. Dans l'hypothèse où il existe un registre des identificateurs, associé à des informations de contact (comme on fait pour les noms de domaine), on pourrait imaginer que l'observateur regarde dans le registre (avec des protocoles comme RDAP ou whois) et appelle au téléphone le pilote. Reste à savoir si c'est une bonne idée que le pilote réponde alors que son attention devrait être concentrée sur le pilotage…
L'IETF, via son groupe DRIP, va donc tenter de développer des solutions pour :
Le but est évidemment que ces solutions reposent sur des normes ouvertes et accessibles. (Un certain nombre d'organisations font déjà du lobbying pour pousser au déploiement et à l'obligation légale de solutions privées et fermées, leur assurant le contrôle, et les revenus associés.)
Les normes existantes comme F3411-19 ne permettent pas cela, d'où ce cahier des charges du projet DRIP, dont les exigences forment la section 4 de notre RFC. Chacune est identifiée par trois lettres indiquant sa catégorie, puis un numéro. Ainsi, la première exigence est dans la catégorie des généralités et se nomme GEN-1 : « DRIP doit permettre d'authentifier les affirmations du drone » (puisque, actuellement, un drone peut raconter ce qu'il veut). Je ne vais pas vous lister toutes les exigences, seulement de quoi vous donner une idée du projet. Ainsi, GEN-2 suit GEN-1 en demandant qu'en outre, tous les messages du drone puissent être reliés à ceux qui prouvent l'identité du drone (et pas uniquement parce que les messages sont émis depuis la même adresse MAC). GEN-3 impose qu'il existe un moyen de vérifier l'enregistrement du drone dans un registre, même en l'absence de connexion Internet (par exemple le drone pourrait diffuser un certificat signé par le registre). GEN-6 concerne la prise de contact, il faut qu'il existe un moyen de contacter le pilote. (Attention, comme tout ce cahier des charges, il s'agit d'une exigence technique, pas politique. L'IETF n'a pas l'autorité pour édicter des règles disant, par exemple, que le pilote doit accepter les communications entrantes. Elle dit juste qu'un moyen technique doit exister, mais son utlisation pourra, par exemple, être restreinte à des appelants identifiés et autorisés.) Un protocole envisagé pour cette communication est HIP.
GEN-8 demande que tout cela fonctionne même si le drone (évidemment), son pilote et l'observateur sont en déplacement, GEN-9 veut un mode multicast, et GEN-10 un moyen de superviser le bon fonctionnement du système (notamment pour le Network RID).
La catégorie suivante concerne plus spécifiquement les identificateurs. ID-1 met une taille maximale à 19 octets (c'est une limite de F3411-19, sauf erreur). ID-2 dit que sur ces 19 octets au maximum, il faut placer un identificateur du registre. ID-4 exige que l'identificateur soit unique (ce qui est du simple bon sens) et ID-5, reprenant GEN-1, demande que le drone ne puisse pas mentir sur son identificateur. ID-6 veut qu'on puisse empêcher de suivre un drone ou son propriétaire à la trace (et il faut donc permettre des identificateurs qui ne soient pas statiques sur le long teme). Là encore, rappelez-vous l'avertissement de la catégorie précédente : DRIP ne définit pas une politique, l'activation ou non de la technique demandée par ID-6 n'est pas une question purement technique.
En parlant de gêner la surveillance, la troisième catégorie concerne la vie privée (voir aussi la section 7). PRIV-1 rappelle l'importance de protéger les données privées (par exemple en ne distribuant pas le nom et l'adresse du pilote publiquement) et PRIV-4 demande pour cela que le système d'enregistrement sépare ce qui est public et ce qui ne l'est pas (données personnelles, par exemple).
Et les registres ? Sur l'Internet, il existe de nombreux types de registres, comme les registres de noms de domaine ou les RIR. Certains problèmes, comme l'arbitrage entre les exigences opérationnelles (où on voudrait obtenir facilement beaucoup d'informations sur les titulaires des noms de domaine) et celles de vie privée, sont bien connues depuis longtemps (le « problème whois » de l'ICANN, par exemple). J'ai pris plusieurs exemples tirés des registres de noms de domaines car c'est là où je travaille. La quatrième et dernière catégorie d'exigences DRIP concerne précisément les futurs registres de drones. En raison des limites de taille très strictes sur ce que le drone peut diffuser, il est essentiel de disposer de registres permettant l'accès à davantage d'informations. REG-1 commence par demander qu'il existe un moyen d'interroger le registre (la solution évidente étant RDAP, RFC 9082). Ce moyen doit (exigence REG-2), contrairement à whois (RFC 3912), permettre l'accès différencié (davantage d'informations pour certains clients). REG-3 réclame qu'on puisse ajouter, retirer et modifier de l'information dans le registre (là, la solution évidente est EPP, RFC 5730). Notez qu'une présentation sur l'utilisation de registres avait été faite lors d'une réunion professionnelle des acteurs du monde des registres : les supports.
Enfin, la section 6 du RFC concerne l'analyse de sécurité du futur système. Quelles que soient les solutions adoptées, il faudra faire attention aux attaques Sybil, aux attaques par déni de service utilisant, par exemple, de nombreux messages incorrectement signés, aux attaques de l'Homme du Milieu, etc. On pourra voir aussi des attaques plus subtiles comme, suggère le RFC, un drone de petite taille, a priori pas très effrayant, et qui s'authentifiera proprement, mais derrière lequel surgira le vrai attaquant.
Pour les curieuses et les curieux, je recommande également l'annexe A du RFC, qui discute les choix effectués dans ce RFC et leurs raisons. Par exemple, l'analogie avec les plaques d'immatriculation des voitures, souvent utilisée dans les débats sur l'identification des drones a ses limites. Dans certains pays, par exemple des États des USA, n'importe qui peut accéder, parfois même gratuitement, aux données du registre, et donc aux informations personnelles sur les propriétaires de véhicules. Faut-il faire pareil pour les drones ? Même en Europe, si le registre n'est pas accessible, en revanche, les voitures ont un identificateur statique stable, qui peut faciliter certaines formes de surveillance. Veut-on cela pour les drones ?
Les avions (civils) pilotés et les bateaux ont l'habitude depuis longtemps de diffuser leur identité à qui veut écouter (le transpondeur pour les avions). D'excellentes et utiles applications comme OpenSky et Flightradar24 exploitent cette information. Souhaite-t-on un même fonctionnement pour les drones ? Outre les questions politiques, cela peut poser des problèmes techniques, par exemple parce que le nombre de codes utilisables est limité et qu'il y a beaucoup plus de drones que d'avions. Avec les 24 bits typiquement utilisés, on ne pourrait traiter que seize millions de drones, ce qui est loin des nombres envisagés.
Les drones qui diffusent leur identité le font en Wi-Fi ou en Bluetooth. Ces techniques ont l'avantage d'être très répandues, aussi bien du côté des drones que des observateurs (tous ont un ordiphone), peu chères, libres d'usage, et elles permettent la diffusion. Par contre, leur portée est faible. Il pourrait être intéressant de travailler sur des technologies conçues pour les grandes distances (comme WiMAX), ou pour les réseaux de capteurs (comme LoRA).
Voilà, le groupe DRIP est évidemment au travail pour mettre au point des protocoles correspondant à ces exigences. Le problème posé par les drones ne peut que devenir de plus en plus sérieux, notamment parce que leur discrétion augmentera. Dans les romans de la série Harry Potter (par J. K. Rowling), une journaliste-sorcière sans scrupule peut se transformer en insecte pour aller espionner les gens chez eux. Un drone suffisamment petit pour être quasiment indétectable serait un cauchemar pour la vie privée…
Pour en savoir plus, quelques ressources utiles :
Date de publication du RFC : Avril 2022
Auteur(s) du RFC : D. Cooley (NSA)
Pour information
Première rédaction de cet article le 26 avril 2022
Le choix est vaste parmi les algorithmes de cryptographie. Des protocoles comme TLS offrent de l'agilité cryptographique, c'est-à-dire la possibilité d'avoir différents algorithmes pour un même protocole. Pour l'administrateur système qui n'est pas un ou une expert en cryptographie, lesquels choisir ? Pour l'aider, les agences de sécurité nationales font souvent des documents synthétisant leur analyse des bons algorithmes du moment. C'est le cas du RGS en France, par exemple. Ce RFC définit le profil TLS de CNSA (Commercial National Security Algorithm), les bons algorithmes recommandés par la NSA.
C'est d'ailleurs le deuxième RFC écrit par un employé de cette agence étatsunienne, après le RFC 5903 mais il est évidemment possible que certains auteurs aient été discrets sur leur affiliation.
Ce profil CNSA (Commercial National Security Algorithm) est destiné à être utilisé dans le cadre de TLS, plus exactement ses versions 1.2 (RFC 5246) et 1.3 (RFC 8446). Le cadre réglementaire étatsunien est SP 80059. Un rappel : il s'agit d'un profil, une restriction des algorithmes possibles, il ne définit pas de nouvel algorithme, il liste juste ceux à utiliser, par exemple ceux des RFC 5288 et RFC 5289. C'est en effet une de missions de la NSA que de conseiller l'État et les entreprises en matière de cryptographie. (La NSA a également une mission offensive, qui va en sens inverse, les encourageant par exemple à garder secrètes des vulnérabilités, pour qu'elle puisse les exploiter.) Cette suite CNSA n'a rien de très novateur, elle est dans la continuation des précédentes, le prochain grand changement, estime la NSA, sera le passage aux algorithmes post-quantiques. (Notez que l'analyse de la NSA sur les mesures à prendre en attendant les calculateurs quantiques est très discutée.)
Après ces préliminaires, la suite CNSA elle-même (section 4). Elle inclut les grands classiques, Diffie-Hellman avec les corps finis, les courbes elliptiques du NIST et encore RSA, les signatures avec les courbes elliptiques (ECDSA seulement) et RSA, et l'algorithme de chiffrement symétrique AES en intègre avec GCM (ChaCha - RFC 8439, sans doute pas assez officiel, n'est pas cité). CNSA impose des tailles minimales de clés, par exemple 3 072 bits pour une signature avec RSA. Pour la condensation, c'est SHA-384 seulement (j'ignore pourquoi SHA-256 et les autres ne sont pas cités).
Pour la version de TLS, la section 5 de notre RFC impose au minimum 1.2 (c'est un des points où le blog que vous êtes en train de lire n'est pas conforme aux recommandations de la NSA…). Si on utilise une courbe elliptique (cf. RFC 8422), cela doit être la P-384 du NIST. (Saviez-vous d'ailleurs qu'il existe une courbe elliptique française, « FRP256 », publiée au Journal officiel ?) Et au passage, les certificats doivent suivre le profil du RFC 8603.
Pour TLS 1.3 (RFC 8446), CNSA impose de
tirer profit de certaines nouveautés de cette version, comme
l'extension signature_algorithms
. Par contre,
il faut refuser early_data
(les raisons sont
dans la section 2.3 du RFC 8446).
Date de publication du RFC : Avril 2022
Auteur(s) du RFC : N. Cam-Winget (Cisco Systems), J. Visoky (ODVA)
Pour information
Première rédaction de cet article le 22 avril 2022
Ce nouveau RFC normalise des algorithmes pour le protocole de sécurité TLS, qui ne fournissent pas de chiffrement (seulement authentification et intégrité). C'est évidemment une option très contestée et l'IETF ne suggère pas d'utiliser ces algorithmes mais, bon, certaines personnes y voient une utilité.
La section 1 du RFC décrit des scénarios d'usage (souvent tirés du monde de l'IoT) où cela peut avoir un sens de ne pas chiffrer. Le premier exemple est celui d'un bras robotique commandé via un protocole TCP/IP. L'authentification est importante mais la confidentialité ne l'est peut-être pas, ce qui rendrait acceptable l'absence de chiffrement. Autre exemple, des rapports météo, ou bien l'envoi de signaux d'horloges, qu'il faut évidemment authentifier mais qui ne sont pas secrets. Le RFC cite aussi des exemples de communication avec des trains ou des avions, où l'intégrité des données est cruciale, mais pas leur confidentialité. Mais attention avant de jeter le chiffrement à la poubelle, le RFC dit bien qu'il faut faire une analyse soignée de la menace. Pour reprendre l'exemple du bras robotique, l'écoute des messages pourrait permettre la rétro-ingénierie, ce qu'on ne souhaite pas forcément.
Mais pourquoi se passer de chiffrement, d'ailleurs, alors qu'il est plus simple de l'utiliser systématiquement ? Parce que dans certains cas, il coûte cher, notamment en latence. En outre, certains équipements, notamment du genre objets connectés, ont des capacités de calcul très limitées. Bref, si le chiffrement systématique et par défaut reste la politique de l'IETF, ce RFC présente quelques façons de s'en passer, si on sait ce qu'on fait (si vous ne savez pas, continuez à chiffrer !).
Les algorithmes sans chiffrement sont présentés dans la section
4. Ils se nomment TLS_SHA256_SHA256
et
TLS_SHA384_SHA384
, et sont évidemment dans
le
registre IANA. Ils utilisent une fonction de
condensation comme SHA-256 pour
faire du HMAC, protégeant ainsi l'intégrité des
communications.
Rappelez-vous bien : aucun chiffrement n'est fourni. Les certificats client, par exemple, qui sont normalement chiffrés depuis TLS 1.3, seront envoyés en clair.
Publier un tel RFC n'a pas été facile, la crainte de beaucoup étant que des gens qui ne comprennent pas bien les conséquences utilise ces algorithmes sans mesurer les risques. Le RFC demande (section 9) donc qu'ils ne soient pas utilisés par défaut et qu'ils requièrent une configuration explicite. Et rappelez-vous que ce RFC n'est que « pour information » et ne fait pas l'objet d'un consensus à l'IETF.
Date de publication du RFC : Avril 2022
Auteur(s) du RFC : E. Rescorla (RTFM), H. Tschofenig (Arm Limited), N. Modadugu (Google)
Chemin des normes
Réalisé dans le cadre du groupe de travail IETF tls
Première rédaction de cet article le 22 avril 2022
Pour sécuriser vos applications TCP sur l'Internet, vous utilisez TLS, pas vrai ? Et si vos applications préfèrent UDP, par exemple pour faire de la voix sur IP ? La solution est alors DTLS, dont la nouvelle version, la 1.3, est normalisée dans ce RFC 9147. Elle a vocation à remplacer l'ancienne version 1.2, qui était dans le RFC 6347.
DTLS fournit normalement les mêmes services de sécurité que TLS, notamment la confidentialité (via un chiffrement du trafic) et l'authentification (via une signature). Les seuls services TLS que DTLS ne peut pas rendre sont la protection de l'ordre des messages (ce qui est logique pour UDP) et, selon les options choisies, la protection contre le rejeu. DTLS a été conçu pour être aussi proche que possible de TLS, pour pouvoir réutiliser le code et s'appuyer sur des propriétés de sécurité déjà établies. DTLS 1.0 et 1.2 (il n'y a jamais eu de 1.1) avaient été normalisés sous forme de différences avec la version de TLS correspondante. De même, DTLS 1.3, objet de notre RFC, est défini en décrivant les différences avec TLS 1.3. Il faut donc lire le RFC 8446 avant. À propos de lectures, il faut aussi lire le RFC 9146, pour le concept d'identificateur de connexion (Connection ID).
La section 3 du RFC pose le problème : on veut un truc comme TLS mais qui marche sur un service de datagrammes, en général UDP (RFC 768). Un service de datagrammes ne garantit pas l'ordre d'arrivée, ni même que les datagrammes arrivent. DTLS ajoute de la sécurité au service de datagrammes mais ne change pas ses propriétés fondamentales : une application qui utilise DTLS ne doit pas s'étonner que les messages n'arrivent pas tous, ou bien arrivent dans le désordre. C'est un comportement connu des applications de diffusion vidéo ou de jeu en ligne, qui préfèrent sauter les parties manquantes plutôt que de les attendre.
Pourquoi un protocole distinct, DTLS, plutôt que de reprendre TLS ? C'est parce que TLS ne marche que si le service de transport sous-jacent garantit certaines propriétés qu'UDP ne fournit pas. TLS a besoin de :
La section 4 présente la couche Enregistrements de DTLS, c'est-à-dire le format des paquets sur le réseau. Les messages sont transportés dans des enregistrements (record) TLS, et il peut y avoir plusieurs messages dans un seul datagramme UDP. Le format des messages est différent de celui de TLS (ajout d'un numéro de séquence) et de celui de DTLS 1.2 :
struct { ContentType type; ProtocolVersion legacy_record_version; uint16 epoch = 0 uint48 sequence_number; uint16 length; opaque fragment[DTLSPlaintext.length]; } DTLSPlaintext; struct { opaque unified_hdr[variable]; opaque encrypted_record[length]; } DTLSCiphertext;
La première structure est en clair, la seconde
contient des données chiffrées. Un message dans un datagramme est,
soit un DTLSPlaintext
(ils sont notamment
utilisés pour l'ouverture de connexion, quand on ne connait pas
encore le matériel cryptographique à utiliser) ou bien un
DTLSCiphertext
.
La partie chiffrée du
DTLSCiphertext
est composée d'un :
struct { opaque content[DTLSPlaintext.length]; ContentType type; uint8 zeros[length_of_padding]; } DTLSInnerPlaintext;
Comme pour TLS 1.3, le champ
legacy_record_version
est ignoré (il n'est là
que pour tromper les middleboxes). Le
unified_hdr
contient notamment l'identificateur
de connexion (Connection ID), concept expliqué
dans le RFC 9146 (comme avec QUIC, ils peuvent augmenter la
traçabilité). Les détails de la structure des messages
figurent dans l'annexe A du RFC.
À l'arrivée d'un datagramme, c'est un peu compliqué, vu la
variété de formats. La section 4.1 suggère un mécanisme de
démultiplexage, permettant de déterminer rapidement si le message
était du DTLSPlaintext
, du
DTLSCipherext
, ou une erreur. Un datagramme
invalide doit être silencieusement ignoré (c'est peut-être une
tentative d'un méchant pour essayer d'injecter des données ; couper
la connexion au premier datagramme invalide ouvrirait une facile
voie d'attaque par déni de service).
Qui dit datagramme dit problèmes de MTU, une des plaies de l'Internet. Normalement, c'est l'application qui doit les gérer, après tout le principe d'un service de datagrammes, c'est que l'application fait tout. Mais DTLS ne lui facilite pas la tâche, car le chiffrement augmente la taille des données, « dans le dos » de l'application. Et, avant même que l'application envoie ses premières données, la poignée de main DTLS peut avoir des datagrammes dépassant la MTU, par exemple en raison de la taille des certificats. Et puis dans certains cas, le système d'exploitation ne transmet pas à l'application les signaux indispensables, comme les messages ICMP Packet Too Big. Bref, pour aider, DTLS devrait transmettre à l'application, s'il la connait, la PMTU (la MTU du chemin complet, que la couche Transport a peut-être indiqué à DTLS). Et DTLS doit gérer la découverte de la PMTU tout seul pour la phase initiale de connexion.
La section 5 du RFC décrit le protocole d'établissement de l'association entre client et serveur (on peut aussi dire connexion, si on veut, mais pas session, ce dernier terme devrait être réservé au cas où on reprend une même session sur une connexion différente). Il est proche de celui de TLS 1.3 mais il a fallu ajouter tout ce qu'UDP ne fournit pas, la détection de la MTU (Path MTU, la MTU du chemin complet), des accusés de réception explicites et la gestion des cas de pertes de paquets. Voici l'allure d'un message DTLS d'établissement de connexion :
enum { client_hello(1), server_hello(2), new_session_ticket(4), end_of_early_data(5), encrypted_extensions(8), certificate(11), certificate_request(13), certificate_verify(15), finished(20), key_update(24), message_hash(254), (255) } HandshakeType; struct { HandshakeType msg_type; /* handshake type */ uint24 length; /* bytes in message */ uint16 message_seq; /* DTLS-required field */ uint24 fragment_offset; /* DTLS-required field */ uint24 fragment_length; /* DTLS-required field */ select (msg_type) { case client_hello: ClientHello; case server_hello: ServerHello; case end_of_early_data: EndOfEarlyData; case encrypted_extensions: EncryptedExtensions; case certificate_request: CertificateRequest; case certificate: Certificate; case certificate_verify: CertificateVerify; case finished: Finished; case new_session_ticket: NewSessionTicket; case key_update: KeyUpdate; } body; } Handshake;
Le message ClientHello
, comme en TLS 1.3, a un
champ legacy_version
qui sert à faire croire
aux middleboxes (et aux
serveurs bogués qui ne gèrent pas correctement la négociation de
version) qu'il
s'agit d'un TLS ancien.
Un établissement de connexion DTLS typique est :
ClientHello
,HelloRetryRequest
qui contient le
cookie,ClientHello
, cette
fois avec le cookie (le serveur est désormais
certain que le client ne ment pas sur son adresse IP),ServerHello
,Finished
et transmettre des données.Il existe également, comme en TLS 1.3, un mode rapide, quand le client a déjà contacté ce serveur et obtenu du matériel cryptographique qu'il peut ré-utiliser. (C'est ce qu'on appelle aussi le « 0-RTT » ou la « reprise de session », et ça existe aussi dans des protocoles comme QUIC.) Tous les paquets pouvant se perdre, DTLS doit avoir un mécanisme de réémission.
Les risques d'attaques par déni de service sont très élevés dans le cas où on utilise des datagrammes. Un méchant peut envoyer des demandes de connexion répétées, pour forcer le serveur à faire des calculs cryptographiques et surtout à allouer de la mémoire pour les connexions en attente. Pire, il peut utiliser un serveur DTLS pour des attaques par réflexion où le méchant ment sur son adresse IP pour que le serveur dirige ses réponses vers une victime innocente. Les attaques par réflexion sont encore pires lorsqu'elles sont combinées à une amplification, quand la réponse est plus grosse que la question.
Pour éviter ces attaques, DTLS reprend, comme vu plus haut, le principe des cookies sans état de Photuris (RFC 2522) et IKE (RFC 7296).
L'extension connection_id
(cf. RFC 9146) peut être mise
dans le ClientHello
. Notez qu'une nouveauté par
rapport au RFC 9146 est la possibilité de
changer les identifiants de connexion pendant une association.
En section 7, une nouveauté de DTLS 1.3, et qui n'a pas
d'équivalent dans TLS, les accusés de réception
(ACK
), nécessaires puisqu'on fonctionne
au-dessus d'un service de datagrammes, qui ne garantit pas l'arrivée
de tous les paquets. Un ACK
permet d'indiquer
les numéros de séquence qu'on a vu. Typiquement, on envoie des
ACK
quand on a l'impression que le partenaire
est trop silencieux (ce qui peut vouloir dire que ses messages se
sont perdus). Ces accusés de réception sont facultatifs, on peut
décider que la réception des messages (par exemple un
ServerHello
quand on a envoyé un
ClientHello
) vaut accusé de réception. Ils
servent surtout à exprimer son impatience « allô ? tu ne dis
rien ? »
Dans sa section 11, notre RFC résume les points importants, question sécurité (en plus de celles communes à TLS et DTLS, qui sont traitées dans le RFC 8446). Le principal risque, qui n'a pas vraiment d'équivalent dans TLS, est celui de déni de service via une consommation de ressources déclenchée par un partenaire malveillant. Les cookies à la connexion ne sont pas obligatoires mais fortement recommandés. Pour être vraiment sûrs, ces cookies doivent dépendre de l'adresse IP du partenaire, et d'une information secrète pour empêcher un tiers de les générer.
À noter d'autre part que, si DTLS garantit plusieurs propriétés de sécurité identiques à celle de TLS (confidentialité et authentification du serveur), il ne garantit pas l'ordre d'arrivée des messages (normal, on fait du datagramme…) et ne protège pas parfaitement contre le rejeu (sections 3.4 et 4.5.1 du RFC si vous voulez le faire).
Les sections 12 et 13 résument les principaux changements depuis DTLS 1.2 (seulement les principaux car DTLS 1.3 est très différent de 1.2) :
Si jamais vous vous lancez dans la programmation d'une bibliothèque DTLS, lisez l'annexe C, qui vous avertit sur quelques pièges typiques de DTLS.
En octobre 2021, il n'y avait pas encore de DTLS 1.3 dans GnuTLS (ticket en cours), BoringSSL ou dans OpenSSL (ticket en cours).
Merci à Manuel Pégourié-Gonnard pour sa relecture attentive.
Date de publication du RFC : Mars 2022
Auteur(s) du RFC : E. Rescorla (RTFM), H. Tschofenig, T. Fossati (Arm Limited), A. Kraus (Bosch.IO)
Chemin des normes
Réalisé dans le cadre du groupe de travail IETF tls
Première rédaction de cet article le 20 mars 2022
Ce RFC ajoute au protocole de sécurité DTLS 1.2 la possibilité d'identificateurs de connexion (connection ID), permettant de relier entre eux des paquets d'une même session DTLS, même si l'adresse IP source change. C'est une solution simple et minimale, DTLS 1.3 (RFC 9147) l'utilise (avec quelques ajouts).
Dans le DTLS habituel, version de TLS qui tourne sur UDP et qui est normalisée dans le RFC 6347, lorsqu'un paquet arrive à une machine, celle-ci trouve l'association de sécurité appropriée (ce qui permet de trouver le matériel crptographique qui permettra de déchiffrer et de vérifier les signatures) en regardant le tuple {protocole, adresse IP source, adresse IP destination, port source, port destination}. Mais si la machine avec qui on correspond a changé d'adresse IP, par exemple parce qu'il s'agit d'un malinphone qui est passé de WiFi à 4G ? Ou bien si elle a changé de port car un routeur NAT a trouvé intelligent de considérer la session terminée, effaçant une entrée dans sa table de correspondance ? Dans ce cas, le paquet entrant va être considéré comme une nouvelle session, il faudra reprendre la négociation TLS, et c'est du temps perdu (la poignée de main cryptographique est coûteuse, et on souhaite amortir ce coût sur la plus longue durée possible).
Le RFC note que le problème est particulièrement sérieux pour les déploiements type Internet des Objets, où les objets peuvent se mettre souvent en sommeil pour économiser leur batterie, amenant le routeur NAT à oublier la session en cours.
Notez aussi qu'outre l'identificateur de connexion, notre RFC apporte quelques changements supplémentaires, notamment pour permettre le remplissage.
La section 3 du RFC spécifie l'extension DTLS
connection_id
(numéro 54 dans le
registre IANA) qui permet de spécifier des identifiants des
connexions DTLS, et donc ainsi de construire une connexion qui
résistera aux changements d'adresses IP et de ports. Dans son
ClientHello
, le client DTLS indiquera
l'identifiant qu'il utilisera (idem pour le serveur DTLS dans son
ServerHello
). Par contre, on ne peut pas
changer d'identifiant de connexion en cours de connexion
(contrairement à ce que permettent, par exemple, QUIC ou, tout simplement, DTLS 1.3). Une fois
l'utilisation de connection IDs négociée, les
données sont envoyées dans une nouvelle structure, de type
tls12_cid
(numéro 25 dans le
registre IANA). (Pour DTLS 1.3, il faudra regarder le RFC qui
lui sera consacré.) On peut
ajouter des zéros avant le chiffrement, à des
fins de remplissage. Voici la structure de
données avant le chiffrement :
struct { opaque content[length]; ContentType real_type; uint8 zeros[length_of_padding]; } DTLSInnerPlaintext;
Et une fois chiffrée, on envoie ça sur le réseau :
struct { ContentType outer_type = tls12_cid; ProtocolVersion version; uint16 epoch; uint48 sequence_number; opaque cid[cid_length]; // L'identifiant de // connexion est là. uint16 length; opaque enc_content[DTLSCiphertext.length]; } DTLSCiphertext;
Que se passe-t-il quand on reçoit un message pour un identifiant
de connexion connu mais une nouvelle adresse IP ? Il ne faut pas lui
faire une confiance aveugle (des méchants ont pu usurper l'adresse
IP) et envoyer immédiatement des réponses à
la nouvelle adresse IP. Il faut d'abord vérifier que le message est
correctement signé, qu'il a une epoch
plus
récente que le précédent message (dans certains cas, comme l'ordre
des messages n'est pas garanti, cela peut mener à ignorer un message
valide), et l'application doit tester que le pair est toujours
d'accord (la méthode dépend de l'application).
Les identifiants de connexion posent évidemment des questions de vie privée (section 8 du RFC). Ils doivent être en clair (puisqu'ils servent au récepteur à découvrir le matériel cryptographique de la connexion, qui servira au déchiffrement) et sont donc observables par quiconque est situé sur le trajet, ce qui améliore la traçabilité (ce qui est mauvais pour la vie privée). Et, contrairement à QUIC, on n'a qu'un identifiant, on ne peut pas en changer (c'est mieux en DTLS 1.3).
Date de publication du RFC : Novembre 2021
Auteur(s) du RFC : R. Danyliw (Software Engineering Institute)
Chemin des normes
Première rédaction de cet article le 19 novembre 2021
L'IETF distribuait un certain nombre de documents
en FTP anonyme. Ce service,
ftp.ietf.org
, va être interrompu. Mais un
certain nombre de RFC continuent à le citer comme source. Comme
on ne peut pas modifier un RFC après publication, notre RFC 9141 fait la liste des mises à jour à lire pour corriger ces
RFC et indiquer la nouvelle source des documents.
FTP, qui avait été le premier protocole de transfert de fichiers de l'Internet, avant même l'adoption de TCP/IP, a été pendant longtemps le principal moyen de copier des fichiers à travers le réseau. Une de ces fonctions, le FTP anonyme (qui n'était en fait pas anonyme du tout) permettait d'accéder aux fichiers, lorsque le serveur le voulait bien, sans avoir de compte sur le serveur. D'immenses archives de logiciels, de documents, d'images, ont été ainsi distribuées pendant des années. Aujourd'hui, FTP n'est guère plus utilisé (entre autres parce qu'il fonctionne en clair, cf. la section 4 sur la sécurité) et maintenir un service FTP anonyme n'a plus guère de sens. D'où la décision de l'IETF en 2020 de fermer le sien (cf. l'annonce). Le plan élaboré à cette occasion (une lecture recommandée sur les détails de cette décision, par exemple les statistiques d'utilisation) notait qu'il y avait trente RFC qui référençaient ce service, le dernier, le RFC 7241, datant de 2014. Tous ces RFC sont donc formellement mis à jour par notre RFC 9141. (Comme le note GuB, « C'est pire que légifrance cette histoire ».)
Par exemple, le RFC 2077 dit « Copies of RFCs are available on:
ftp://ftp.isi.edu/in-notes/
» alors qu'il
faudra désormais lire « Copies of RFCs are available
on: https://www.rfc-editor.org/rfc/
».
Voici par exemple, pour la nostalgie, le fonctionnement du serveur
en novembre 2021, avant sa fermeture. On cherche les archives
indiquées par le RFC 5098, et qui sont désormais en
:
https://www.ietf.org/ietf-ftp/ietf-mail-archive/ipcdn/
% ncftp ftp.ietf.org NcFTP 3.2.5 (Feb 02, 2011) by Mike Gleason (http://www.NcFTP.com/contact/). Copyright (c) 1992-2011 by Mike Gleason. All rights reserved. Connecting to 4.31.198.44... FTP server ready Logging in... Anonymous access granted, restrictions apply Logged in to ftp.ietf.org. ncftp / > ls charter/ ietf/ internet-drafts/ slides/ concluded-wg-ietf-mail-archive/ ietf-mail-archive/ review/ status-changes/ conflict-reviews/ ietf-online-proceedings/ rfc/ yang/ ncftp / > ncftp / > cd ietf-mail-archive/ipcdn ncftp /ietf-mail-archive/ipcdn > ls ... 1996-12 1999-07.mail 2001-09.mail 2003-11.mail 2006-01.mail 2008-03.mail 1997-01 1999-08.mail 2001-10.mail 2003-12.mail 2006-02.mail 2008-04.mail 1997-02 1999-09.mail 2001-11.mail 2004-01.mail 2006-03.mail 2008-05.mail 1997-03 1999-10.mail 2001-12.mail 2004-02.mail 2006-04.mail 2008-06.mail ...
Date de publication du RFC : Novembre 2021
Auteur(s) du RFC : J. Hong, T. You (ETRI, L. Dong, C. Westphal (Futurewei Technologies), B. Ohlman (Ericsson)
Pour information
Réalisé dans le cadre du groupe de recherche IRTF icnrg
Première rédaction de cet article le 1 décembre 2021
L'ICN est l'idée (très contestable) qu'un réseau informatique sert à accéder à du « contenu » et que le réseau doit donc être architecturé autour de cette idée de contenu. Les noms identifient ainsi un contenu donné. Mais il faut bien ensuite trouver le contenu donc résoudre ces noms en quelque chose de plus concret. Ce RFC est le cahier des charges d'un tel système de résolution de noms pour les projets ICN. Comme beaucoup de cahier des charges, il est très « liste au Père Noël », accumulant des desiderata sans se demander s'ils sont réalistes (et compatibles entre eux !).
Comme avec beaucoup de documents qui promeuvent l'ICN, ce RFC donne une description erronée du nommage et de l'adressage dans l'Internet d'aujourd'hui. Passons, et voyons ce que l'ICN propose. L'idée est que le contenu est stocké dans des NDO (Named Data Objects) et que toute activité dans le réseau coniste à récupérer des NDO. Les NDO sont identifiés par un nom. Il ne s'agit pas seulement d'un identificateur mis au-dessus d'un réseau architecturé sur d'autres concepts (comme le sont les URI) mais du concept de base du réseau ; les routeurs ne routent plus selon des adresses mais selon les noms des NDO. Le problème est évidemment qu'il faudra bien, à la fin, trouver l'objet désiré. Cela nécessite (cf. RFC 7927) :
Ce RFC se focalise sur le premier point, le NRS (Name Resolution Service), et en est le cahier des charges. Le RFC 9236 a depuis décrit l'architecture envisagée. Si vous voulez apprendre des choses sur les ICN en général et la résolution de noms en particulier, voir par exemple « A Survey of Information-Centric Networking » ou « A Survey of Information-Centric Networking Research ».
Si on compare avec l'Internet actuel, le NRS aura un rôle analogue à celui de BGP (plutôt que du DNS, car le NRS sera au cœur du réseau, et complètement inséparable). Bon, ceci dit, c'est plus compliqué que cela car, derrière l'étiquette « ICN », il y a des tas de propositions différentes. Par exemple, certaines ressemblent plutôt à l'Internet actuel, avec une résolution de noms en localisateurs qui servent ensuite pour le routage (comme dans IDnet, cf. « IDNet: Beyond All-IP Network), alors que d'autres versions du concept d'ICN utilisent les noms pour le routage (comme le NDN ou le CCNx du RFC 8569). La section 2.4 du RFC compare ces approches.
La section 3 du RFC est ensuite le cahier des charges proprement dit. Malheureusement, elle plane au-dessus des réalités quand elle affirme par exemple qu'il faut un NRS qui fonctionnera de la même façon que l'espace de nommage soit plat ou hiérarchique. C'est très irréaliste, il n'y a pas de nette séparation entre la structure de l'espace de nommage et le mécanisme de résolution. Ainsi, ce mécanisme, dans le cas du DNS, est très lié à la structure des noms. Si on la change, tout le DNS serait à refaire (et sans doute en moins efficace). Parmi les systèmes d'ICN qui utilisent un nommage hiérarchique (et réintroduisent donc une forme de « localisation » dans les noms), on trouve NDN et CCNx.
Certains des mécanismes de résolution discutés ont déjà un RFC, par exemple le NI du RFC 6920, utilisé dans NetInf (cf. « Network of Information (NetInf) - An information-centric networking architecture »).
Bref, les principes du NRS :
Et le cahier des charges à proprement parler est en section 4. Je ne cite pas tout mais la liste au Père Noël comprend :
Une conclusion ? Les projets regroupés sous le nom d'ICN sont assez anciens, n'ont rien fait de vraiment nouveau récemment, et il y a peu de chances que ce RFC soit suivi de réalisations concrètes.
Date de publication du RFC : Octobre 2021
Auteur(s) du RFC : M. Duke (F5 Networks)
Réalisé dans le cadre du groupe de travail IETF shmoo
Première rédaction de cet article le 12 octobre 2021
Pour son travail de normalisation technique, l'IETF tient normalement trois réunions physiques par an. La pandémie de Covid-19 a évidemment changé tout cela. Les premières décisions d'annulation ont été prises selon un procédure ad hoc, mais ce nouveau RFC fournit des critères plus rigoureux pour les prochaines décisions d'annulation.
La dernière réunion physique de l'IETF a eu lieu à Singapour en novembre 2019. Depuis, les conditions sanitaires ont forcé à annuler toutes les réunions. Mais notre nouveau RFC ne se limite pas au cas de la Covid-19. Une réunion, après tout, peut devoir être annulée pour d'autres raisons, un problème de dernière minute dans le bâtiment où elle devait se tenir, une catastrophe naturelle dans le pays d'accueil, un brusque changement dans la politique de visa de ce pays, etc. (Le RFC cite même le cas d'une guerre civile, mais ce n'est encore jamais arrivé à une réunion IETF.) Dans ces cas, l'IETF LLC (la direction administrative de l'IETF) et l'IESG vont devoir décider si on maintient la réunion ou pas.
La section 3 du RFC expose les critères de décision. L'IETF LLC (cf. RFC 8711) détermine si la réunion peut se tenir, l'IESG si cela vaut la peine de la tenir. L'IETF LLC doit évidemment travailler en toute transparence, informant l'IETF de la situation, des décisions possibles et d'un éventuel « plan B ». Il n'est pas toujours possible de procéder proprement et démocratiquement si la situation est urgente (tremblement de terre trois jours avant la réunion…). Dans ce cas, l'IETF LLC doit déterminer seule si la réunion peut se tenir (ce qui implique certaines garanties de sécurité pour les participants, de la restauration mais également un accès Internet qui marche ; les participants à l'IETF n'ont pas juste besoin de dormir et de manger, il leur faut aussi du réseau). L'IETF LLC doit aussi intégrer des paramètres internes comme la disponibilité de ses employés et des bénévoles, et bien sûr les conséquences financières du maintien ou de l'annulation (si le lieu de la réunion considère qu'il n'y avait pas force majeure et ne veut pas rembourser…). La section 3 du RFC 8718 contient des indications utiles à cette évaluation.
L'IESG, lui, doit déterminer s'il y aura suffisamment de monde à la réunion pour que ça vaille la peine. Il ne serait pas malin de maintenir une réunion pour que personne ne vienne.
La section 4 du RFC couvre les alternatives. Si on annule, que peut-on proposer à la place ? Il faut évaluer ces alternatives en tenant compte de leur efficacité (une réunion en ligne est moins efficace) et de leur coût (changer les billets d'avion au dernier moment coûte cher). Ces alternatives peuvent être, dans l'ordre décroissant de préférence :
La section 5 de notre RFC couvre les questions financières. En gros, l'IETF ne remboursera pas les dépenses engagées par les participants (avion, hôtel, etc), seulement les frais d'inscription à la réunion, en totalité s'il y a annulation complète et partiellement dans les autres cas.
Date de publication du RFC : Septembre 2021
Auteur(s) du RFC : M. Boucadair (Orange), J. Shallow, T. Reddy.K (Akamai)
Chemin des normes
Réalisé dans le cadre du groupe de travail IETF dots
Première rédaction de cet article le 10 octobre 2021
Le protocole DOTS (Distributed Denial-of-Service Open Threat Signaling) vise à permettre au client d'un service anti-dDoS de demander au service de mettre en route des mesures contre une attaque. Ce RFC décrit le canal de signalisation de DOTS, celui par lequel passera la demande d'atténuation de l'attaque. Il remplace le RFC 8782, mais les changements sont mineurs.
Si vous voulez mieux comprendre DOTS, il est recommandé de lire le RFC 8612, qui décrit le cahier des charges de ce protocole, et le RFC 8811, qui décrit l'architecture générale. Ici, je vais résumer à l'extrême : un client DOTS, détectant qu'une attaque par déni de service est en cours contre lui, signale, par le canal normalisé dans ce RFC, à un serveur DOTS qu'il faudrait faire quelque chose. Le serveur DOTS est un service anti-dDoS qui va, par exemple, examiner le trafic, jeter ce qui appartient à l'attaque, et transmettre le reste à son client.
Ces attaques par déni de service sont une des plaies de l'Internet, et sont bien trop fréquentes aujourd'hui (cf. RFC 4987 ou RFC 4732 pour des exemples). Bien des réseaux n'ont pas les moyens de se défendre seuls et font donc appel à un service de protection (payant, en général, mais il existe aussi des services comme Deflect). Ce service fera la guerre à leur place, recevant le trafic (via des manips DNS ou BGP), l'analysant, le filtrant et envoyant ce qui reste au client. Typiquement, le client DOTS sera chez le réseau attaqué, par exemple en tant que composant d'un IDS ou d'un pare-feu, et le serveur DOTS sera chez le service de protection. Notez donc que client et serveur DOTS sont chez deux organisations différentes, communiquant via le canal de signalisation (signal channel), qui fait l'objet de ce RFC.
La section 3 de notre RFC expose les grands principes du
protocole utilisé sur ce canal de signalisation. Il repose sur
CoAP, un équivalent léger de HTTP, ayant beaucoup
de choses communes avec HTTP. Le choix d'un protocole différent de
HTTP s'explique par les spécificités de DOTS : on l'utilise quand ça
va mal, quand le réseau est attaqué, et il faut donc pouvoir
continuer à fonctionner même quand de nombreux paquets sont
perdus. CoAP a les caractéristiques utiles pour DOTS, il est conçu
pour des réseaux où il y aura des pertes, il tourne sur UDP, il permet des
messages avec ou sans accusé de réception, il utilise peu de
ressources, il peut être sécurisé par DTLS… TCP est également
utilisable mais UDP est préféré, pour éviter le head-of-line
blocking. CoAP est normalisé dans le RFC 7252. Parmi les choses à retenir, n'oubliez pas
que l'encodage du chemin dans l'URI est un peu spécial, avec une option
Uri-Path:
par segment du chemin (RFC 7252, section 5.10.1). Par abus de langage,
j'écrirai « le client CoAP demande
/foo/bar/truc.cbor
» alors qu'il y aura en fait
trois options Uri-Path:
:
Uri-Path: "foo" Uri-Path: "bar" Uri-Path: "truc.cbor"
Par défaut, DOTS va utiliser le port 4646 (et non pas le port par
défaut de CoAP, 5684, pour éviter toute confusion avec d'autres
services tournant sur CoAP). Ce port a été choisi pour une bonne
raison, je vous laisse la chercher, la solution est à la fin de cet
article. Le plan d'URI sera coaps
ou
coaps+tcp
(RFC 7252,
section 6, et RFC 8323, section 8.2).
Le fonctionnement de base est simple : le client DOTS se connecte au serveur, divers paramètres sont négociés. Des battements de cœur peuvent être utilisés (par le client ou par le serveur) pour garder la session ouverte et vérifier son bon fonctionnement. En cas d'attaque, le client va demander une action d'atténuation. Pendant que celle-ci est active, le serveur envoie de temps en temps des messages donnant des nouvelles. L'action se terminera, soit à l'expiration d'un délai défini au début, soit sur demande explicite du client. Le serveur est connu du client par configuration manuelle, ou bien par des techniques de découverte comme celles du RFC 8973.
Les messages sont encodés en CBOR (RFC 8949). Rappelez-vous que le modèle de données de CBOR est
très proche de celui de JSON, et notre RFC spécifie donc les messages
avec une syntaxe JSON, même si ce n'est pas l'encodage utilisé sur
le câble. Pour une syntaxe formelle des messages, le RFC utilise
YANG (cf. RFC 7951). Le type
MIME des messages est application/dots+cbor
.
La section 4 du RFC décrit les différents messages possibles plus
en détail. Je ne vais pas tout reprendre ici, juste donner quelques
exemples. Les URI commencent toujours par
/.well-known/dots
(.well-known
est normalisé dans le RFC 8615, et dots
est
désormais enregistré
à l'IANA). Les différentes actions ajouteront au chemin dans
l'URI /mitigate
pour les demandes d'actions
d'atténuation, visant à protéger de l'attaque,
/hb
pour les battements de cœur, etc.
Voici par exemple une demande de protection, effectuée avec la méthode CoAP PUT :
Header: PUT (Code=0.03) Uri-Path: ".well-known" Uri-Path: "dots" Uri-Path: "mitigate" Uri-Path: "cuid=dz6pHjaADkaFTbjr0JGBpw" Uri-Path: "mid=123" Content-Format: "application/dots+cbor" { ... Données en CBOR (représentées en JSON dans le RFC et dans cet article, pour la lisibilité). }
L'URI, en notation traditionnelle, sera donc
/.well-known/dots/mitigate/cuid=dz6pHjaADkaFTbjr0JGBpw/mid=123
. CUID
veut dire Client Unique IDentifier et sert à
identifier le client DOTS, MID est Mitigation
IDentifier et identifie une demande d'atténuation
particulière. Si ce client DOTS fait une autre demande de
palliation, le MID changera mais le CUID sera le même.
Que met-on dans le corps du message ? On a de nombreux champs définis pour indiquer ce qu'on veut protéger, et pour combien de temps. Par exemple, on pourrait avoir (je rappelle que c'est du CBOR, format binaire, en vrai) :
{ "ietf-dots-signal-channel:mitigation-scope": { "scope": [ { "target-prefix": [ "2001:db8:6401::1/128", "2001:db8:6401::2/128" ], "target-port-range": [ { "lower-port": 80 }, { "lower-port": 443 } ], "target-protocol": [ 6 ], "lifetime": 3600 } ] } }
Ici, le client demande qu'on protège
2001:db8:6401::1
et
2001:db8:6401::2
(target
veut dire qu'ils sont la cible d'une attaque, pas qu'on veut les
prendre pour cible), sur
les ports 80 et 443,
en TCP, pendant une heure. (lower-port
seul,
sans upper-port
indique un port unique, pas un
intervalle.)
Le serveur va alors répondre avec le code 2.01 (indiquant que la requête est acceptée et traitée) et des données :
{ "ietf-dots-signal-channel:mitigation-scope": { "scope": [ { "mid": 123, "lifetime": 3600 } ] } }
La durée de l'action peut être plus petite que ce que le client a demandé, par exemple si le serveur n'accepte pas d'actions trop longues. Évidemment, si la requête n'est pas correcte, le serveur répondra 4.00 (format invalide), si le client n'a pas payé, 4.03, s'il y a un conflit avec une autre requête, 4.09, etc. Le serveur peut donner des détails, et la liste des réponses possibles figure dans des registres IANA, comme celui de l'état d'une atténuation, ou celui des conflits entre ce qui est demandé et d'autres actions en cours.
Le client DOTS peut ensuite récupérer des informations sur une action de palliation en cours, avec la méthode CoAP GET :
Header: GET (Code=0.01) Uri-Path: ".well-known" Uri-Path: "dots" Uri-Path: "mitigate" Uri-Path: "cuid=dz6pHjaADkaFTbjr0JGBpw" Uri-Path: "mid=123"
Ce GET
/.well-known/dots/mitigate/cuid=dz6pHjaADkaFTbjr0JGBpw/mid=123
va renvoyer de l'information sur l'action d'identificateur (MID)
123 :
{ "ietf-dots-signal-channel:mitigation-scope": { "scope": [ { "mid": 123, "mitigation-start": "1507818393", "target-prefix": [ "2001:db8:6401::1/128", "2001:db8:6401::2/128" ], "target-protocol": [ 6 ], "lifetime": 1755, "status": "attack-stopped", "bytes-dropped": "0", "bps-dropped": "0", "pkts-dropped": "0", "pps-dropped": "0" } ] } }
Les différents champs de la réponse sont assez évidents. Par
exemple, pkts-dropped
indique le nombre de
paquets qui ont été jetés par le protecteur.
Pour mettre fin aux actions du système de protection, le client utilise évidemment la méthode CoAP DELETE :
Header: DELETE (Code=0.04) Uri-Path: ".well-known" Uri-Path: "dots" Uri-Path: "mitigate" Uri-Path: "cuid=dz6pHjaADkaFTbjr0JGBpw" Uri-Path: "mid=123"
Le client DOTS peut se renseigner sur les capacités du serveur avec
un GET de /.well-known/dots/config
.
Ce RFC décrit le canal de signalisation de DOTS. Le RFC 8783, lui, décrit le canal de données. Le canal de signalisation est prévu pour faire passer des messages de petite taille, dans un environnement hostile (attaque en cours). Le canal de données est prévu pour des données de plus grande taille, dans un environnement où les mécanismes de transport normaux, comme HTTPS, sont utilisables. Typiquement, le client DOTS utilise le canal de données avant l'attaque, pour tout configurer, et le canal de signalisation pendant l'attaque, pour déclencher et arrêter l'atténuation.
Les messages possibles sont modélisés en YANG. YANG est normalisé dans le RFC 7950. Notez que YANG avait été initialement créé pour décrire les commandes envoyées par NETCONF (RFC 6241) ou RESTCONF (RFC 8040) mais ce n'est pas le cas ici : DOTS n'utilise ni NETCONF, ni RESTCONF mais son propre protocole basé sur CoAP. La section 5 du RFC contient tous les modules YANG utilisés.
La mise en correspondance des modules YANG avec l'encodage
CBOR figure dans la section
6. (YANG permet une description abstraite d'un message mais ne dit
pas, à lui tout seul, comment le représenter en bits sur le réseau.)
Les clés CBOR sont toutes des entiers ; CBOR permet d'utiliser des
chaînes de caractères comme clés mais DOTS cherche à gagner de la
place. Ainsi, les tables de la section 6 nous apprennent que le
champ cuid
(Client Unique
IDentifier) a la clé 4, suivie d'une chaîne de caractères
en CBOR. (Cette correspondance est désormais un
registre IANA.) D'autre part, DOTS introduit une étiquette
CBOR, 271 (enregistrée
à l'IANA, cf. RFC 8949, section 3.4)
pour marquer un document CBOR comme lié au protocole DOTS.
Évidemment, DOTS est critique en matière de sécurité. S'il ne fonctionne pas, on ne pourra pas réclamer une action de la part du service de protection. Et s'il est mal authentifié, on risque de voir le méchant envoyer de faux messages DOTS, par exemple en demandant l'arrêt de l'atténuation. La section 8 du RFC rappelle donc l'importance de sécuriser DOTS par TLS ou plutôt, la plupart du temps, par son équivalent pour UDP, DTLS (RFC 9147). Le RFC insiste sur l'authentification mutuelle du serveur et du client, chacun doit s'assurer de l'identité de l'autre, par les méthodes TLS habituelles (typiquement via un certificat). Le profil de DTLS recommandé (TLS est riche en options et il faut spécifier lesquelles sont nécessaires et lesquelles sont déconseillées) est en section 7. Par exemple, le chiffrement intègre est nécessaire.
La section 11 revient sur les questions de sécurité en ajoutant
d'autres avertissements. Par exemple, TLS ne protège pas contre
certaines attaques par déni de service, comme un paquet TCP RST
(ReSeT). On peut sécuriser la communication avec
TCP-AO (RFC 5925) mais c'est un vœu pieux, il
est très peu déployé à l'heure actuelle. Ah, et puis si les
ressources à protéger sont identifiées par un nom de domaine, et pas une adresse ou un
préfixe IP (target-fqdn
au lieu de
target-prefix
), le RFC dit qu'évidemment la
résolution doit être faite avec DNSSEC.
Question mises en œuvre, DOTS dispose d'au moins quatre implémentations, dont l'interopérabilité a été testée plusieurs fois lors de hackathons IETF (la première fois ayant été à Singapour, lors de l'IETF 100) :
Notez qu'il existe des serveurs de test DOTS
publics comme
coaps://dotsserver.ddos-secure.net:4646
.
Ah, et la raison du choix du port 4646 ? C'est parce que 46 est le code ASCII pour le point (dot en anglais) donc deux 46 font deux points donc dots.
L'annexe A de notre RFC résume les principaux changements depuis le RFC 8782. Le principal changement touche les modules YANG, mis à jour pour réparer une erreur et pour tenir compte du RFC 8791. Il y a aussi une nouvelle section (la 9), qui détaille les codes d'erreur à renvoyer, et l'espace des valeurs des attributs a été réorganisé… Rien de bien crucial, donc.
Date de publication du RFC : Janvier 2022
Auteur(s) du RFC : B. Moran, H. Tschofenig (Arm Limited), H. Birkholz (Fraunhofer SIT)
Pour information
Réalisé dans le cadre du groupe de travail IETF suit
Première rédaction de cet article le 15 janvier 2022
On le sait, la sécurité des gadgets nommés « objets connectés » est abyssalement basse. Une de raisons (mais pas la seule, loin de là !) est l'absence d'un mécanisme de mise à jour du logiciel, mécanisme qui pourrait permettre de corriger les inévitables failles de sécurité. Le problème de la mise à jour de ces machins, souvent contraints en ressources, est très vaste et complexe. Le groupe de travail SUIT de l'IETF se focalise sur un point bien particulier : un format de manifeste permettant de décrire une mise à jour. Ce RFC décrit le modèle de données des informations qui seront placées dans ce manifeste.
Donc, que faut-il indiquer pour permettre une mise à jour du logiciel (et des réglages) d'un objet connecté ? Le groupe de travail est parti des expériences concrètes, de scénarios et de menaces, pour arriver à la liste que contient ce RFC. Certes, cette liste n'est pas exhaustive (cela serait impossible) mais donne quand même un bon point de départ. Attention, ce RFC décrit le modèle d'information (cf. RFC 3444), pas le format du manifeste (qui sera en CBOR, et fera l'objet d'un futur RFC).
Pour suivre le contenu de ce RFC, il vaut mieux avoir déjà lu le premier RFC du groupe de travail, le RFC 9019, qui explique l'architecture générale du système, ainsi que le RFC 8240, qui était le compte-rendu d'un atelier de réflexion sur cette histoire de mise à jour des objets connectés..
La (longue) section 3 du RFC est consacrée à énumérer tous les éléments à mettre dans le manifeste. Je ne vais pas reproduire toute la liste, juste quelques éléments intéressants. Notez que pour chaque élément, le RFC précise s'il doit être présent dans le manifeste ou bien s'il est facultatif. Le premier élément listé est l'obligatoire identificateur de version de la structure du manifeste, qui permettra de savoir à quel modèle ce manifeste se réfère.
Autre élément obligatoire, un numéro de séquence, croissant de façon monotone, et qui permettra d'éviter les reculs accidentels (« mise à jour » avec une version plus ancienne que celle installée). On peut utiliser pour cela une estampille temporelle (si on a une horloge sûre).
Ensuite, le RFC recommande (mais, contrairement aux deux précédents éléments, ce n'est pas obligatoire) de placer dans le manifeste un identificateur du fournisseur de la mise à jour. Il n'est pas prévu pour d'autres comparaisons que la simple égalité. Le format recommandé pour cet identificateur est un UUID (RFC 9562) de « version 5 », c'est-à-dire formé à partir d'un nom de domaine, qui est forcément unique. L'avantage des UUID (surtout par rapport au texte libre) est leur taille fixe, qui simplifie analyse et comparaison. Si on veut un identificateur de fournisseur qui soit lisible par des humains, il faut le placer dans un autre élément.
L'identificateur de la classe (class ID), indique un type de machines, les machines d'une même classe acceptant le même logiciel (cette acceptation dépend du matériel mais aussi d'autres facteurs comme la version du microcode). Il doit être unique par identificateur de fournisseur (et, en cas de vente en marque blanche, il doit être fourni par le vrai fournisseur, pas par le vendeur). Là aussi, un UUID de « version 5 » est recommandé. Il ne doit pas dépendre juste du nom commercial, un même nom commercial pouvant recouvrir des produits qui n'acceptent pas les mêmes mises à jour. Si un objet peut recevoir des mises à jour indépendantes, pour différents composants de l'objet, il faut des identificateurs de classe différents (surtout si certains objets du fournisseur utilisent une partie des mêmes composants, mais pas tous ; l'identificateur doit identifier le composant, pas l'objet).
Le manifeste contient aussi (mais ce n'est pas obligatoire), une date d'expiration, indiquant à partir de quand il cesse d'être valable.
Le manifeste peut (mais ce n'est pas obligatoire) contenir directement l'image utilisée pour la mise à jour du logiciel de l'objet. Cela peut être utile pour les images de petite taille ; plus besoin d'une étape supplémentaire de téléchargement.
Par contre, l'indication du format de l'image, qu'elle soit directement incluse ou non, est obligatoire, ainsi que celle de la taille de la dite image.
Question sécurité, le RFC impose également la présence d'une signature du manifeste. Le manifeste peut aussi contenir des éléments qui vont servir à établir si on a une délégation sûre depuis une autorité reconnue : des Web Tokens en CBOR (RFC 8392), avec peut-être des preuves du RFC 8747.
La sécurité est au cœur des problèmes que traite ce RFC. Après cette liste d'élements facultatifs ou obligatoires dans un manifeste, la section 4 de notre RFC expose le modèle des menaces auxquelles il s'agit de faire face. Le RFC rappelle que la mise à jour elle-même peut être une menace : après tout, mettre à jour du logiciel, c'est exécuter du code distant. Juste répéter « il faut mettre à jour le logiciel de sa brosse à dents connectée » ne suffit pas, si le mécanisme de mise à jour permet d'insérer du code malveillant. (Et encore, le RFC est gentil, tendance bisounours, il ne rappelle pas que l'attaquant peut être le fournisseur, envoyant du code nouveau pour désactiver certaines fonctions ou pour espionner l'utilisateur, voir les exemples de Hewlett-Packard et de Sony. Sans même parler de la possibilité d'une attaque contre la chaine de développement comme celle contre SolarWinds.)
L'analyse part du modèle STRIDE. Je ne vais pas citer toutes les menaces possibles (il y en a beaucoup !), lisez le RFC pour avoir une vue complète. Notez que les attaques physiques contre les objets (ouvrir la boite et bricoler à l'intérieur) ne sont pas incluses.
Évidemment, la première menace est celle d'une mise à jour qui serait modifiée par un attaquant. Le code correspondant serait exécuté, avec les conséquences qu'on imagine. La signature prévient cette attaque.
Ensuite, le cas d'une vieille mise à jour, qui était honnête et signée, mais n'est plus d'actualité. Si elle a une faiblesse connue, un attaquant pourrait essayer de faire réaliser une « mise à jour » vers cette vieille version. Le numéro de séquence dans le manifeste, qui est strictement croissant, est là pour protéger de cette attaque.
Autre risque, celui d'une mise à jour signée mais qui concerne en fait un autre type d'appareil. Appliquer cette mise à jour pourrait mener à un déni de service. La protection contre ce risque est assurée par l'identificateur du type d'objet.
Le RFC liste la menace d'une rétro-ingénierie de l'image. Outre que cela ne s'applique qu'au logiciel privateur, du point de vue de la sécurité, cela n'est pas crucial, puisqu'on ne compte pas sur la STO. Si on y tient quand même, le chiffrement de l'image (qui n'est pas obligatoire) pare ce risque.
Sinon la section 4.4 contient des scénarios typiques d'utilisation, où une histoire décrit les acteurs, les menaces, les solutions possibles, rendant ainsi plus vivants et plus concrets les problèmes de sécurité étudiés dans ce RFC.
Date de publication du RFC : Octobre 2021
Auteur(s) du RFC : K. Davies (IANA), J. Arkko (Ericsson)
Pour information
Première rédaction de cet article le 30 octobre 2021
Dernière mise à jour le 4 mai 2022
Le TLD
.arpa
sert pour
différentes fonctions techniques et est géré directement par
l'IAB. Ce
RFC décrit un
changement dans ses serveurs de noms.
Ce TLD
est décrit dans le RFC 3172. Le nom de
.arpa
fait référence à
l'ancien nom de la DARPA, l'agence qui avait
financé le développement de l'Internet. Mais,
aujourd'hui, il veut dire « Address and Routing Parameter
Area » et le TLD sert à diverses fonctions techniques comme
la résolution d'adresses IP
en noms de domaine via
des sous-domaines comme ip6.arpa
. Par exemple,
l'adresse IP du serveur Web de l'IAB a pour nom correspondant :
% dig -x 2001:1900:3001:11::2c ... ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 53150 ;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1 ... ;; ANSWER SECTION: c.2.0.0.0.0.0.0.0.0.0.0.0.0.0.0.1.1.0.0.1.0.0.3.0.0.9.1.1.0.0.2.ip6.arpa. 3600 IN PTR mail.ietf.org.
Comme vous le voyez, dig a inversé l'adresse IP et ajouté
ip6.arpa
à la
fin. .arpa
sert
également à d'autres fonctions comme le
home.arpa
du RFC 8375.
Traditionnellement, le domaine .arpa
était
hébergé sur une partie des serveurs de noms de la
racine. Le 22 octobre 2021, voici quels étaient les
serveurs faisant
autorité pour .arpa
:
% dig +nodnssec NS arpa ... ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 43670 ;; flags: qr rd ra ad; QUERY: 1, ANSWER: 12, AUTHORITY: 0, ADDITIONAL: 1 ... ;; ANSWER SECTION: arpa. 15910 IN NS b.root-servers.net. arpa. 15910 IN NS f.root-servers.net. arpa. 15910 IN NS d.root-servers.net. arpa. 15910 IN NS l.root-servers.net. arpa. 15910 IN NS e.root-servers.net. arpa. 15910 IN NS a.root-servers.net. arpa. 15910 IN NS c.root-servers.net. arpa. 15910 IN NS i.root-servers.net. arpa. 15910 IN NS h.root-servers.net. arpa. 15910 IN NS g.root-servers.net. arpa. 15910 IN NS k.root-servers.net. arpa. 15910 IN NS m.root-servers.net. ;; Query time: 0 msec ;; SERVER: ::1#53(::1) ;; WHEN: Fri Oct 22 20:35:36 CEST 2021 ;; MSG SIZE rcvd: 241
Comme vous pouvez le voir, c'était presque tous les serveurs de la
racine (si vous aimez les jeux : quel serveur de la racine n'hébergeait
pas .arpa
?). La raison pour cela est que
.arpa
est critique et doit donc bénéficier d'un
hébergement solide. Profiter des serveurs de la racine était donc
intéressant. Mais le problème est que ça lie
.arpa
à la racine. On pourrait avoir envie de
faire des changements dans les serveurs faisant autorité pour
.arpa
sans toucher aux serveurs de la racine,
celle-ci étant encore plus critique (cf. RFC 7720). Le principe de base de notre nouveau RFC est donc : découpler les
serveurs DNS de .arpa
de ceux de la racine (ce
qui avait été fait pour ip6.arpa
il y a dix ans,
voir le RFC 5855).
Le changement ne concerne que l'hébergement DNS, pas la gestion
de .arpa
(section 2 de notre RFC), qui reste un
TLD
critique, et soumis au RFC 3172. Le choix des sous-domaines
et de leur administration est inchangé (par exemple, pour
ip6.arpa
, c'est décrit dans le RFC 5855).
La section 3 du RFC décrit le nouveau système. Le principe est de commencer par utiliser des
noms différents pour les serveurs, mais qui pointeront vers les
mêmes machines, au moins au début. Les nouveaux noms sont dans le
sous-domaine ns.arpa
, on a donc
a.ns.arpa
, b.ns.arpa
,
etc. Aucune modification dans les serveurs ne sera nécessaire pour
cette première étape, qui n'affectera que la zone racine et la zone
.arpa
. Comme tous les noms seront dans la zone qu'ils
servent, il faudra ajouter de la colle aux réponses DNS (ne pas
juste dire « le serveur est c.ns.arpa
» mais
également indiquer son adresse
IP). Il n'y a désormais
plus de nom de serveur commun à la racine et à
.arpa
, et il sera possible dans le futur de
migrer vers d'autres serveurs. (Toujours en respectant le RFC 3172.) Voici l'état actuel :
% dig +nodnssec NS arpa ; <<>> DiG 9.16.1-Ubuntu <<>> +nodnssec NS arpa ;; global options: +cmd ;; Got answer: ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 15962 ;; flags: qr rd ra ad; QUERY: 1, ANSWER: 12, AUTHORITY: 0, ADDITIONAL: 1 ;; OPT PSEUDOSECTION: ; EDNS: version: 0, flags:; udp: 1232 ;; QUESTION SECTION: ;arpa. IN NS ;; ANSWER SECTION: arpa. 517877 IN NS h.ns.arpa. arpa. 517877 IN NS i.ns.arpa. arpa. 517877 IN NS k.ns.arpa. arpa. 517877 IN NS l.ns.arpa. arpa. 517877 IN NS m.ns.arpa. arpa. 517877 IN NS a.ns.arpa. arpa. 517877 IN NS b.ns.arpa. arpa. 517877 IN NS c.ns.arpa. arpa. 517877 IN NS d.ns.arpa. arpa. 517877 IN NS e.ns.arpa. arpa. 517877 IN NS f.ns.arpa. arpa. 517877 IN NS g.ns.arpa. ;; Query time: 3 msec ;; SERVER: 192.168.2.254#53(192.168.2.254) ;; WHEN: Wed May 04 08:57:47 CEST 2022 ;; MSG SIZE rcvd: 228
Vous pouvez également voir cet état actuel de .arpa
à l'IANA
ou bien
dans le DNS.
Notez enfin que certains des serveurs de
.arpa
autorisent le transfert de zones. Voici une copie faite le 24 octobre 2021.
Date de publication du RFC : Août 2021
Auteur(s) du RFC : J. Uttaro (AT&T), J. Alcaide, C. Filsfils, D. Smith (Cisco), P. Mohapatra (Sproute Networks)
Chemin des normes
Réalisé dans le cadre du groupe de travail IETF idr
Première rédaction de cet article le 28 septembre 2021
Le RFC 8955 spécifie comment utiliser BGP pour diffuser des règles de filtrage (dites « FlowSpec ») aux routeurs. On voit facilement qu'une annonce de règles de filtrage maladroite ou malveillante pourrait faire bien des dégâts et c'est pour cela que le RFC 8955 insiste sur l'importance de valider ces annonces. Mais les règles de validation étaient trop strictes et ce nouveau RFC les adoucit légèrement.
Le changement ? Le RFC 8955 imposait que la machine qui annonce une règle de filtrage pour un préfixe de destination donné soit également le routeur suivant (next hop) pour le préfixe en question. Cela limitait l'annonce aux routeurs situés sur le trajet des données. En iBGP (BGP interne à un AS, où les routeurs qui annoncent ne sont pas toujours ceux qui transmettent les paquets), cette règle était trop restrictive. (Pensez par exemple à un réflecteur de routes interne, par exemple géré par le SOC, réflecteur qui n'est même pas forcément un routeur.)
La règle nouvelle, plus libérale, ne concerne que du iBGP, interne à un AS ou à une confédération d'AS (RFC 5065). Entre des AS gérés par des organisations différentes (par exemple un AS terminal informant son opérateur Internet de ses désirs de filtrage), la règle de validation reste inchangée.
Plus formellement, la section 6 du RFC 8955, sur la validation des annonces de filtrage, est modifiée, l'étape « l'annonceur doit être également l'annonceur de la meilleure route » devient « l'annonceur doit être l'annonceur de la meilleure route ou bien le chemin d'AS doit être vide (ou n'inclure que des AS de la confédération, s'il y en a une) ». Le RFC précise également que cette validation relâchée doit pouvoir être désactivée par l'administrateur réseaux (s'ielle sait qu'il n'y aura pas d'annonces de filtrage par un routeur qui n'est pas sur le chemin des paquets). Vous noterez plus bas que, pour l'instant, seul Huawei le permet.
Il y a également un léger changement des règles de validation du chemlin d'AS, pour les cas des serveurs de route (a priori, ils ne relaient pas ce genre d'annonces et peuvent donc les rejeter). Le RFC note que, même avec une validation stricte, certaines faiblesses de BGP permettent quand même dans certains cas à tort l'envoi de règles de filtrage. C'est un problème fondamental du RFC 8955, qui n'est donc pas nouveau.
Les nouvelles règles de ce RFC sont déjà largement mises en œuvre chez les routeurs.
Date de publication du RFC : Avril 2022
Auteur(s) du RFC : E. Foudil, Y. Shafranovich (Nightwatch Cybersecurity)
Pour information
Première rédaction de cet article le 28 avril 2022
Lorsqu'on repère un problème de sécurité sur un site Web ou, plus
généralement, dans l'informatique d'une organisation, de deux choses
l'une : soit on est un méchant et on exploite l'information à son
profit, soit on fait partie des bons et on va essayer de faire en
sorte que le problème soit résolu. Une solution évidente est de
signaler le problème à l'organisation qui gère le site Web. Mais
comment ? Entre le formulaire de contact cassé qui refuse votre
adresse de courrier en prétendant qu'elle est illégale, et le
message envoyé à un humain sous-payé et sous-qualifié qui cliquera
tout de suite sur « Problème résolu » pour faire grimper ses
chiffres de résolution, prévenir quelqu'un de compétent et de
responsable est souvent un parcours du combattant ! D'où le choix de
ce RFC de
proposer encore une nouvelle méthode : un fichier à un endroit bien
connu sur le site Web, au format structuré, et contenant les
informations essentielles, le security.txt
.
Comme beaucoup de techniques utilisées sur l'Internet, ce format a été déployé bien avant d'être officiellement décrit dans un RFC. On trouve aujourd'hui de tels fichiers sur plusieurs sites, y compris sur ce blog (regardez-le tout de suite si vous voulez avoir une idée de ce qu'on y met).
La section 1 du RFC décrit plus en détail le problème. Toute personne qui a déjà essayé de signaler un problème de sécurité à une organisation reconnaitra ses propres mésaventures. Le RFC commence par rappeler que trouver un contact, quoique très difficile, n'est pas tout : il faut aussi s'informer sur la politique de traitement des signalements de cette organisation, car plus d'un citoyen ayant voulu signaler une vulnérabilité s'est retrouvé accusé d'être un vilain pirate, et a parfois réellement été poursuivi en justice. Et enfin il faut s'informer sur les moyens de communications sécurisés puisque, par définition, on va transmettre des informations délicates. Est-ce qu'on peut utiliser PGP (RFC 9580), par exemple ? Pour ce qui est des contacts, il existe en théorie plusieurs solutions :
security@LE-DOMAINE
pour les signalements de
sécurité. Il y a peu d'organisations où cette adresse fonctionne
techniquement et, même quand c'est le cas, elle n'est pas
forcément lue.
Le dernier point est à la fois une motivation important pour créer
une nouvelle solution et une des principales critiques qui avaient
été formulées à l'encontre du projet
security.txt
lors de la discussion à
l'IETF : pourquoi est-ce que ça serait mieux avec
un nouveau format ? Si une organisation est assez négligente pour ne
pas tenir à jour les informations présentes chez le registre de son
nom de domaine, comment
espérer qu'elle tienne à jour son
security.txt
? Il n'y a évidemment pas de
certitude à ce sujet, juste l'espoir qu'un autre format, et surtout
un autre mécanisme de mise à jour augmentera les chances que
l'information soit correcte.
Le
security.txt
est un fichier analysable par un
programme (mais quand même lisible par un humain), et qui permet
d'indiquer tout ce qui est nécessaire, par exemple au chercheur de
vulnérabilités qui a trouvé quelque chose. Il vise à signaler les
vulnérabilités (pas encore exploitées), pas les
incidents (car un attaquant qui a réussi a
peut-être modifié le fichier security.txt
).
La spécification, maintenant (section 2 du
RFC). Le security.txt
est un simple fichier
texte qui doit être déposé à un endroit précis du site Web,
/.well-known
(RFC 8615,
et security.txt
a été enregistré dans le registre
des URL bien connus). Son type doit être text/plain
et
il doit être accessible en HTTPS, pour d'évidentes raisons de
sécurité. Il doit être en Unicode, utilisant
le profil du RFC 5198. Analysable par un
programme, il doit se conformer à la
grammaire spécifiée dans le RFC. Il comporte
plusieurs champs, chacun sur une ligne, et ayant un nom et une
valeur. La plupart des champs sont optionnels. Le champ le plus
connu est Contact
, qui est obligatoire, et
voici un exemple :
Contact: mailto:stephane%2Bsecurity@bortzmeyer.org
(Vous avez reconnu l'encodage de l'URI spécifié dans la section 2.1 du RFC 3986. %2B
est le signe
plus.) Les
lignes commençant par un croisillon sont des
commentaires.
Le RFC recommande que les security.txt
soient signés avec
OpenPGP (RFC 9580). Naturellement, comme avec n'importe quelle
signature, sa valeur dépend de si le vérificateur connait de manière
certaine la clé publique qui a été utilisée.
Un certain nombre de champs sont définis aujourd'hui, la plupart optionnels. Parmi eux :
Acknowledgments
: un lien vers une page
des remerciements, pour que la personne ayant l'intention de
signaler un problème sache qu'elle pourra être remerciée
publiquement. Question carotte pour les chercheurs de
vulnérabilités, il y a aussi un champ Hiring
☺.Canonical
: l'URL officiel de ce
security.txt
. Cela peut permettre de détecter
certaines erreurs de configuration
(security.txt
copié sur le mauvais
site).Contact
: sans doute le champ le plus
important, et un des rares qui soit obligatoire. Il contient un
URI
indiquant comment contacter quelqu'un de responsable, à qui
confier le problème de sécurité. L'intérêt de définir la valeur de
ce champ comme un URI est que cela permet de ne pas se limiter à
une méthode de contact particulière. Puisque c'est un URI, les
adresses de courrier
électronique doivent être mises sous forme d'URI
mailto:
(RFC 6068). Pareil pour les numéros de téléphone (RFC 3966). Le champ Contact
peut être répété, afin d'avoir plusieurs mécanismes de signalement
(dans ce cas, l'ordre est important, la méthode recommandée est la
première).Encryption
: indique la clé de
chiffrement à utiliser pour
contacter. C'est encore un URI (qui peut être un URI de plan
dns:
, pour les clés stockées dans le
DNS du RFC 7929).Expires
: ce champ est obligatoire et
indique (au format du RFC 3339) la date
limite d'utilisation de ce security.txt
(pour
éviter que de vieux fichiers pas maintenus restent
utilisés).Policy
: un lien vers la page décrivant
la politique de l'organisation en matière de signalement de
vulnérabilités. Rappelez-vous qu'un problème de beaucoup de
personnes qui ont détecté une vulnérabilité est le risque que le
signalement de celle-ci leur vaille des ennuis juridiques, en
raison de la politique de beaucoup d'organisations qui est de nier
les problèmes et de poursuivre devant les tribunaux ceux qui les
signalent.Preferred-Languages
: des
étiquettes de langue (RFC 5646) indiquant dans quelles langues on préfère recevoir
les rapports (et là, je pense à cet informaticien étatsunien qui
s'était étonné publiquement qu'un webmestre chinois ne répondait
pas à ses signalements de vulnérabilité faits en anglais…).Les champs figurent dans un
registre IANA. D'autres champs pourront être rajoutés à ce
registre dans le futur, en suivant la politique « Examen par un
expert » (RFC 8126). Pour faciliter ces futurs
ajouts, il faut ignorer les champs qu'on ne connait pas. Un exemple
réel de security.txt
? Regardez celui de ce blog.
Le security.txt
est publié via le site Web
mais ne s'applique pas forcément qu'au Web, il peut aussi servir
pour l'organisation en général. Par contre, il ne s'applique pas aux
sous-domaines : un security.txt
sur
eu.org
n'est pas valable pour
exodus-privacy.eu.org
.
Vu le but de ce security.txt
, il n'est pas
étonnant que la section 5 du RFC, consacrée à la
sécurité, soit si détaillée, d'autant plus qu'elle a fait l'objet de
vigoureuses discussions à l'IETF. Je n'en cite ici qu'une partie,
n'hésitez pas à lire toute cette section 5. D'abord,
l'objection la plus évidente : si le site Web a été piraté, le
security.txt
ne peut plus être considéré comme
digne de confiance. Les signalements risquent d'être perdus ou même
envoyés à l'attaquant qui aura mis son adresse dans le
security.txt
. C'est vrai, mais c'est le cas de
toutes les informations de contact. Par exemple, si le compte de
l'organisation auprès du BE a été piraté (comme dans l'attaque moyen-orientale
de 2018), les informations attachées au nom de domaine, et
récupérées par whois ou RDAP, sont également suspectes. Le RFC
recommande que les organisations qui ont un
security.txt
le supervisent automatiquement et
vérifient notamment le champ Canonical
. Et,
bien sûr, que le security.txt
soit signé. Les
personnes qui signalent une vulnérabilité ont tout intérêt à
vérifier le security.txt
. Et puis surtout,
security.txt
est conçu pour la
réponse à vulnérabilité, pas la
réponse à incident. L'utiliser pour signaler
« vous avez une faille » est raisonnable, s'en servir pour signaler
« vous êtes piraté » l'est moins.
Comme toutes les informations mises en ligne (cf. l'exemple des
informations sociales sur un nom de domaine…), le
security.txt
peut être faux, soit dès le début,
soit parce qu'il n'a pas été maintenu correctement. En tapant cet
article, je regardais le security.txt
d'une
entreprise française de sécurité informatique et le champ
Encryption
contenait un URL qui pointait… vers
un 404. Dans une très grosse entreprise française travaillant sur de
la haute technologie, notamment en sécurité, un URL dans le
security.txt
donne… 403 ! Au même endroit, la
signature du security.txt
est incorrecte… Le champ Expires
permet de détecter les
security.txt
trop vieux et pas maintenus mais
le problème est vaste et on peut s'attendre à se casser souvent le
nez sur des informations incorrectes. Comme pour toutes les
informations en ligne, les organisations qui publient un
security.txt
devraient s'assurer, dans leurs
procédures, qu'il est maintenu à jour.
Certains croient qu'ils ont le droit de se livrer à des
tests d'attaques sur tout
site Web trouvé sur l'Internet. C'est évidemment faux, de même qu'on
n'a pas le droit dans le rue de tenter de crocheter toutes les
serrures pour voir lesquelles sont vulnérables. Même l'existence
d'un security.txt
ne vaut pas autorisation de
tester le site. Le champ Policy
peut indiquer
si des tests d'attaque sont autorisés et dans quelles
conditions. Sinon, on doit se limiter aux failles découvertes dans
le cours de l'utilisation normale du site (ceci n'est pas un conseil
juridique : la loi est compliquée et dépend du pays).
Naturellement, comme toujours lorsqu'on publie une adresse de
courrier, elle va recevoir du spam. Plus
gênant, le fait que le security.txt
soit
analysable par un programme pourrait amener certains
bots qui font des soi-disant tests de
sécurité à envoyer des messages automatiques de faible
valeur. (Par exemple, j'ai vu un bot qui regardait la version
d'Apache et
envoyait automatiquement un courrier si la version lui semblait trop
vieille. Ce genre d'avertissements mécaniques n'a aucune valeur,
notamment parce que certains systèmes d'exploitation bouchent les
failles de sécurité sans changer la version et, surtout, parce qu'il
y a peu de chance qu'un logiciel puisse faire avec succès quelque
chose d'aussi délicat qu'une analyse de sécurité ; les logiciels
sont utiles pour une première reconnaissance, mais il ne faut pas
envoyer de message d'avertissement avant qu'un humain compétent
n'ait vérifié.)
Voilà, nous avons fait le tour du RFC. Si vous êtes responsable
de la sécurité d'une organisation, à vous de vous mettre au travail
pour concevoir et documenter une politique de signalement des
failles de sécurité (c'est le gros du travail), de rédiger un
security.txt
(c'est trivial) et de faire en
sorte qu'il soit maintenu (ce n'est pas évident). Si vous avez
trouvé une faille de sécurité dans un site Web ou ailleurs chez une
organisation, pensez à regarder si elle a un
security.txt
mais ne l'utilisez pas
aveuglément, comparez avec d'autres sources d'information. Pour vous
instruire, vous pouvez regarder le site Web du projet. Il
comprend notamment un bon formulaire pour aider à fabriquer son
security.txt
(mais rappelez-vous que ce qui est
difficile, c'est l'élaboration de la politique). Ce site contient
également une liste de
logiciels qui peuvent aider. Le moteur de recherche
Shodan lit les
security.txt
et les affiche proprement mais,
avec le virtual hosting,
comme Shodan travaille par adresse IP, ça ne marche pas
souvent. Sinon, YesWeHack fournit une extension
aux navigateurs Web pour afficher le
security.txt
(pas très utile, je trouve, elle
se contente d'afficher le fichier tel quel, sans vraie valeur
ajoutée).
Le security.txt
est-il obligatoire ? Cela
dépend de votre environnement. Le DHS
étatsunien le
recommande pour les organismes qui dépendent de lui. En
France, l'arrêté
du 18 septembre 2018 portant approbation du cahier des clauses
simplifiées de cybersécurité, dans son article 6.4, dit que
« Afin que ces signalements soient effectifs et efficaces, les
conventions d'usage en cybersécurité sont respectées (security.txt,
abuse@). Dans tous les cas, il faut moins d'une minute pour trouver
le point d'entrée approprié du signalement. ».
Terminons par un tour des security.txt
existants (mes commentaires concernent l'état de ce fichier en août 2021 ;
il a pu changer depuis). Commençons par un exemple très simple, mais correct,
celui de la société
Cyberzen :
% curl https://www.cyberzen.com/.well-known/security.txt Contact: contact@cyberzen.com Expires: Sun, 31 Dec 2023 23:59 +0200 Preferred-Languages: fr, en
Ensuite le registre de
.be
a un très bon
security.txt
, très complet, avec commentaires :
% curl https://www.dnsbelgium.be/.well-known/security.txt # Our security address Contact: mailto:csirt@dnsbelgium.be?subject=rdp_dnsbelgium.be # Our OpenPGP key Encryption: https://www.dnsbelgium.be/.well-known/pgp-key.txt # Canonical URL Canonical: https://www.dnsbelgium.be/.well-known/security.txt # Our security policy Policy: https://www.dnsbelgium.be/responsible-disclosure-policy Preferred-Languages: en, nl, fr Expires: Mon, 1 Nov 2021 00:00:00 +0100
Autre exemple, Google a un
security.txt
:
Contact: https://g.co/vulnz Contact: mailto:security@google.com Encryption: https://services.google.com/corporate/publickey.txt Acknowledgements: https://bughunter.withgoogle.com/ Policy: https://g.co/vrp Hiring: https://g.co/SecurityPrivacyEngJobs
Le champ Expires
, pourtant obligatoire, manque
encore (il a été ajouté dans les dernières révisions du projet de
spécification).
En France, la Sécu en a un et, contrairement à celui de Google, il est signé avec OpenPGP :
% curl https://www.ameli.fr/.well-known/security.txt -----BEGIN PGP SIGNED MESSAGE----- Hash: SHA512 Contact: mailto:abuse@assurance-maladie.fr Encryption: https://raw.githubusercontent.com/AssuranceMaladieSec/abuse/master/abuse-gpg-public-key.txt Policy: https://assurancemaladiesec.github.io/abuse/reporting/ Acknowledgments: https://assurancemaladiesec.github.io/abuse/thanks/ Preferred-Languages: fr,en -----BEGIN PGP SIGNATURE----- iQIzBAEBCgAdFiEEDSx9bqnSlmkiIRXbSDqGYCymPFIFAl4V9/cACgkQSDqGYCym PFIB7Q/9EI7fNOfyoCqnEH4chiTIW8fx32ldnlaE6WTgdMeQJmkyJrhd2osPAV/j ...
Ah, profitons-en pour vérifier la signature (la clé publique, curieusement, est hébergé chez un tiers, GitHub) :
% wget https://www.ameli.fr/.well-known/security.txt ... 2020-04-08 18:41:10 (6.87 MB/s) - ‘security.txt’ saved [1189] % wget https://raw.githubusercontent.com/AssuranceMaladieSec/abuse/master/abuse-gpg-public-key.txt ... 2021-08-12 09:03:52 (11,1 MB/s) - ‘abuse-gpg-public-key.txt’ saved [3159/3159] % gpg --import abuse-gpg-public-key.txt gpg: key 483A86602CA63C52: public key "Abuse Assurance Maladie <abuse@assurance-maladie.fr>" imported gpg: Total number processed: 1 gpg: imported: 1 % gpg --verify security.txt gpg: Signature made Wed Jan 8 16:40:39 2020 CET gpg: using RSA key 0D2C7D6EA9D29669222115DB483A86602CA63C52 gpg: Good signature from "Abuse Assurance Maladie <abuse@assurance-maladie.fr>" [unknown] gpg: WARNING: This key is not certified with a trusted signature! gpg: There is no indication that the signature belongs to the owner. Primary key fingerprint: 0D2C 7D6E A9D2 9669 2221 15DB 483A 8660 2CA6 3C52
C'est parfait, tout fonctionne (et c'est documenté).
Autre exemple en France, le service Flus, dont le
security.txt
est décrit dans un bon article. Voici le fichier :
% curl https://flus.fr/.well-known/security.txt Contact: https://flus.fr/contact Contact: security@flus.io Expires: Fri, 01 Apr 2022 00:00 +0000 Policy: https://flus.fr/securite Acknowledgments: https://flus.fr/securite Canonical: https://flus.fr/.well-known/security.txt Preferred-Languages: fr, en
OpenSSL
a bien sûr un security.txt
. À une époque, la
signature PGP était incorrecte…
% gpg --verify security.txt.asc security.txt gpg: Signature made Thu Jan 4 04:22:26 2018 CET gpg: using RSA key EFC0A467D613CB83C7ED6D30D894E2CE8B3D79F5 gpg: BAD signature from "OpenSSL OMC <openssl-omc@openssl.org>" [unknown]
Cela illustre un problème commun à tous les mécanismes de publication d'information de contact : l'information n'est pas facile à maintenir et tend à se dégrader avec le temps.
Quelques lectures supplémentaires qui peuvent être intéressantes :
https://disclose.io/
a plein de ressources si
vous travaillez dans la détection et signalement de
vulnérabilités.security.txt
: celui
de Bruno Kerouanton et celui de
y0no.Date de publication du RFC : Septembre 2021
Auteur(s) du RFC : Y. Sheffer (Intuit), D. López, A. Pastor Perales (Telefonica I+D), T. Fossati (ARM)
Chemin des normes
Réalisé dans le cadre du groupe de travail IETF acme
Première rédaction de cet article le 21 septembre 2021
Ce nouveau RFC décrit un profil du protocole ACME d'obtention de certificat, profil qui permet de déléguer la demande à un tiers. C'est surtout utile pour le cas où vous sous-traitez l'hébergement de votre site Web (par exemple sur un CDN) : le sous-traitant peut alors demander un certificat, avec sa clé privée à lui, pour un nom de domaine que vous contrôlez et prouver qu'il héberge bien le serveur pour ce nom. Serveurs et clients TLS n'ont pas besoin d'être modifiés (seuls les serveurs et clients ACME le seront), et, bien entendu, le titulaire du nom de domaine garde un complet contrôle et peut, par exemple, révoquer les certificats obtenus par ses sous-traitants.
Ce profil utilise lui-même le profil STAR (Short-Term,
Automatically Renewed) décrit dans le RFC 8739 donc faites bien attention à avoir lu le RFC 8739 avant. Le cas typique d'utilisation de ce
mécanisme de délégation est le CDN. Un webmestre (l'IdO pour
Identifier Owner car il est titulaire du
nom de domaine, mettons
foobar.example
) a un site Web et sous-traite
tout ou partie du service à un CDN, appelé ici NDC pour
Name Delegation Consumer (et la ressemblance
entre les sigles CDN et NDC est volontaire). Le CDN devra pouvoir
répondre aux requêtes HTTPS pour
www.foobar.example
et donc présenter un
certificat au nom www.foobar.example
. Avec
ACME, l'IdO peut
obtenir un tel certifiat mais il ne souhaite probablement pas
transmettre la clé
privée correspondante au NDC. La solution de notre RFC
est d'utiliser une extension à ACME, permettant la délégation du
nom. Le NDC pourra alors obtenir un certificat STAR (de courte durée
de vie, donc) pour www.foobar.example
. Pas
besoin de partager une clé privée, ni de transmettre des secrets de
longue durée de vie (les délégations sont révocables, et les
certificats STAR ne durent pas longtemps, le NDC devra renouveller
souvent et ça cessera en cas de révocation). C'est l'utilisation
typique de la délégation mais d'autres sont possibles (par exemple
avec des certificats ordinaires, non-STAR). Le RFC note que la
solution de délégation ne modifie qu'ACME, et pas TLS, et qu'elle
marche donc avec les clients et serveurs TLS actuels (contrairement
à d'autres propositions qui sont étudiées).
Pour que la délégation fonctionne, l'IdO doit avoir un serveur ACME, auquel le NDC devra se connecter, et s'être mis d'accord avec le NDC sur les paramètres à utiliser. C'est donc une étape relativement nouvelle, l'utilisateur d'ACME typique n'ayant qu'un client ACME, seule l'AC a un serveur. Mais c'est quand même plus simple que de monter une AC. Le serveur ACME chez l'IdO ne signera pas de certificats, il relaiera simplement la requête. Quand le NDC aura besoin d'un certificat, il enverra une demande à l'IdO, qui la vérifiera et, devenant client ACME, l'IdO enverra une demande à l'AC. Si ça marche, l'IdO prévient le NDC, et celui-ci récupérera le certificat chez l'AC (par unauthenticated GET, RFC 8739, section 3.4).
Le protocole ACME gagne un nouveau type d'objet, les délégations, qui indiquent ce qu'on permet au NDC. Comme les autres objets ACME, elles sont représentées en JSON et voici un exemple :
{ "csr-template": { "keyTypes": [ { "PublicKeyType": "id-ecPublicKey", "namedCurve": "secp256r1", "SignatureType": "ecdsa-with-SHA256" } ], "subject": { "country": "FR", "stateOrProvince": "**", "locality": "**" }, "extensions": { "subjectAltName": { "DNS": [ "www.foobar.example" ] }, "keyUsage": [ "digitalSignature" ], "extendedKeyUsage": [ "serverAuth" ] } } }
(Les champs des extensions comme keyUsage
sont
dans un
nouveau registre IANA ; on peut ajouter des champs, selon la
politique « spécification nécessaire ».)
Ici, le NDC est autorisé à demander des certificats ECDSA
pour le nom www.foobar.example
. Quand le NDC
enverra sa requête de certificat à l'IdO, il devra inclure cet objet
« délégation », que l'IdO pourra comparer avec ce qu'il a configuré
pour ce NDC. Voici un exemple partiel, envoyé lors d'un POST HTTPS
au serveur ACME de l'IdO :
{ "protected": base64url({ "alg": "ES256", "kid": "https://acme.ido.example/acme/acct/evOfKhNU60wg", "nonce": "Alc00Ap6Rt7GMkEl3L1JX5", "url": "https://acme.ido.example/acme/new-order" }), "payload": base64url({ "identifiers": [ { "type": "dns", "value": "www.foobar.example" } ], "delegation": "https://acme.ido.example/acme/delegation/gm0wfLYHBen" }), "signature": ...
(Le nouveau champ delegation
a été placé dans
le
registre IANA.) Le NDC enverra ensuite le CSR, et l'IdO
relaiera la requête vers le serveur ACME de l'AC (moins l'indication
de délégation, qui ne regarde pas l'AC).
Quand on utilise un CDN, il est fréquent qu'on doive configurer un alias dans le DNS pour pointer vers un nom indiqué par l'opérateur du CDN. Voici par exemple celui de l'Élysée :
% dig CNAME www.elysee.fr ... ;; ANSWER SECTION: www.elysee.fr. 3600 IN CNAME 3cifmt6.x.incapdns.net. ...
L'extension au protocole ACME spécifiée dans notre RFC permet au NDC d'indiquer cet alias dans sa requête, l'IdO peut alors l'inclure dans sa zone DNS.
Tous les serveurs ACME ne seront pas forcément capables de gérer
des délégations, il faudra donc l'indiquer dans les capacités du
serveur, avec le champ delegation-enabled
(mis
dans le
registre IANA).
Comme indiqué plus haut, l'IdO peut arrêter la délégation quand il veut, par exemple parce qu'il change de CDN. Cet arrêt se fait par une interruption explicite de la demande STAR (RFC 8739, section 3.1.2). Si les certificats ne sont pas des STAR, le mécanisme à utiliser est la révocation normale des certificats.
Après cet examen du protocole, la section 3 de notre RFC décrit le comportement de l'AC. Il n'y a pas grand'chose à faire pour l'AC (le protocole est entre le NDC et l'IdO) à part à être capable d'accepter des récupérations non authentifiées de certificats (car le NDC n'a pas de compte à l'AC).
On a parlé plus haut du CSR. Il doit se conformer à un certain gabarit, décidé par l'IdO. Ce gabarit est évidemment au format JSON, comme le reste d'ACME. La syntaxe exacte est décrite avec le langage CDDL (RFC 8610) et figure dans l'annexe A ou bien, si vous préférez, avec le langage JSON Schema, utilisé dans l'annexe B. Voici l'exemple de gabarit du RFC :
{ "keyTypes": [ { "PublicKeyType": "rsaEncryption", "PublicKeyLength": 2048, "SignatureType": "sha256WithRSAEncryption" }, { "PublicKeyType": "id-ecPublicKey", "namedCurve": "secp256r1", "SignatureType": "ecdsa-with-SHA256" } ], "subject": { "country": "CA", "stateOrProvince": "**", "locality": "**" }, "extensions": { "subjectAltName": { "DNS": [ "abc.ido.example" ] }, "keyUsage": [ "digitalSignature" ], "extendedKeyUsage": [ "serverAuth", "clientAuth" ] } }
Dans cet exemple, l'IdO impose au NDC un certificat RSA ou ECDSA et rend impérative (c'est le sens des deux astérisques) l'indication de la province et de la ville. L'IdO doit évidemment vérifier que le CSRT reçu se conforme bien à ce gabarit.
Le RFC présente (en section 5) quelques autres cas d'utilisation de cette délégation. Par exemple, un IdO peut déléguer à plusieurs CDN, afin d'éviter que la panne d'un CDN n'arrête tout. Avec la délégation, ça se fait tout seul, chacun des CDN est authentifié, et demande séparément son certificat.
Autre cas rigolo, celui où le CDN délègue une partie du service à un CDN plus petit. Le modèle de délégation ACME peut s'y adapter (le petit CDN demande un certificat au gros, qui relaie à l'IdO…), si les différentes parties sont d'accord.
Enfin, la section 7 du RFC revient sur les propriétés de sécurité de ces délégations. En gros, il faut avoir confiance en celui à qui on délègue car, pendant la durée de la délégation, il pourra faire ce qu'il veut avec le nom qu'on lui a délégué, y compris demander d'autres certificats en utilisant sa délégation du nom de domaine. Il existe quelques mesures techniques que l'IdO peut déployer pour empêcher le NDC de faire trop de bêtises. C'est le cas par exemple des enregistrements DNS CAA (RFC 8659) qui peuvent limiter le nombre d'AC autorisées (voir aussi le RFC 8657).
Je ne connais pas encore d'opérateur de CDN qui mette en œuvre cette solution.
Date de publication du RFC : Juin 2022
Auteur(s) du RFC : M. Bishop (Akamai)
Chemin des normes
Réalisé dans le cadre du groupe de travail IETF quic
Première rédaction de cet article le 7 juin 2022
Le protocole de transport QUIC, bien que permettant plusieurs protocoles applicatifs au-dessus de lui, a été surtout conçu pour HTTP. Il est donc logique que le premier protocole applicatif tournant sur QUIC et normalisé soit HTTP. Voici donc HTTP/3, la nouvelle version d'HTTP, et la première qui tourne sur QUIC, les précédentes tournant sur TCP (et, souvent, sur TLS sur TCP). À part l'utilisation de QUIC, HTTP/3 est très proche de HTTP/2.
Un petit point d'histoire sur HTTP : la version 1.1 (dont la norme précédente datait de 2014) utilise du texte, ce qui est pratique pour les humains (on peut se servir de netcat ou telnet comme client HTTP), mais moins pour les programmes. En outre, il n'a aucun multiplexage, encourageant les auteurs de navigateurs à ouvrir plusieurs connexions TCP simultanées avec le serveur, connexions qui ne partagent pas d'information entre elles et sont donc inefficaces. La version 2 de HTTP (RFC 9113) est binaire et donc plus efficace, et elle inclut du multiplexage. Par contre, les limites de TCP (une perte de paquet va affecter toutes les ressources en cours de transfert, une ressource lente peut bloquer une rapide, etc) s'appliquent toujours. D'où le passage à QUIC pour HTTP/3. QUIC améliore la latence, notamment grâce à la fusion avec TLS, et fournit du « vrai » multiplexage. Autrement, HTTP/3 est très proche de HTTP/2, mais il est plus simple, puisque le multiplexage est délégué à la couche transport.
QUIC offre en effet plusieurs fonctions très pratiques pour HTTP/3, notamment le multiplexage (complet : plus de head-of-line blocking), le contrôle de débit par ruisseau et pas par connexion entière, et l'établissement de connexion à faible latence. QUIC a été conçu en pensant à HTTP et aux problèmes spécifiques du Web.
Nous avons maintenant toute une panoplie de versions de HTTP, du HTTP/1.1 du RFC 9112 et suivants, au HTTP/2 du RFC 9113, puis désormais à notre HTTP/3. De même que HTTP/2 n'a pas supprimé HTTP/1, HTTP/3 ne supprimera pas HTTP/2, ne serait-ce que parce qu'il existe encore beaucoup de réseaux mal gérés et/ou restrictifs, où QUIC ne passe pas. Toutes ces versions de HTTP ont en commun les mêmes sémantiques, décrites dans le RFC 9110.
La section 2 du RFC fait un panorama général de HTTP/3. Quand le
client HTTP sait (on verra plus tard comment il peut savoir) que le
serveur fait du HTTP/3, il ouvre une connexion QUIC avec ce
serveur. QUIC fera le gros du travail. À l'intérieur de chaque
ruisseau QUIC, il y aura un seul couple requête/réponse HTTP. La
requête et la réponse seront placées dans des trames
QUIC de type STREAM
(les trames de
contenu dans QUIC). À l'intérieur des trames QUIC, il y aura des
trames
HTTP, dont deux types sont particulièrement importants,
HEADERS
et DATA
. QUIC
étant ce qu'il est, chaque couple requête/réponse est séparé et, par
exemple, la lenteur à envoyer une réponse n'affectera pas les autres
requêtes, mêmes envoyées après celle qui a du mal à avoir une
réponse. (Il y a aussi la possibilité pour le serveur d'envoyer des
données spontanément, avec les trames HTTP de type
PUSH_PROMISE
et quelques autres.) Les requêtes
et les réponses ont un encodage binaire comme en HTTP/2, et sont
comprimées avec QPACK (RFC 9114), alors que HTTP/2 utilisait HPACK (qui nécessitait
une transmission ordonnées des octets, qui n'existe plus dans une
connexion QUIC).
Après cette rapide présentation, voyons les détails, en
commençant par le commencement, l'établissement des connexions entre
client et serveur. D'abord, comment savoir si le serveur veut bien
faire du HTTP/3 ? Le client HTTP a reçu consigne de l'utilisateur
d'aller en
https://serveur-pris-au-hasard.example/
,
comment va t-il choisir entre HTTP/3 et des versions plus classiques
de HTTP ? Il n'y a rien dans l'URL qui indique que QUIC est possible mais il y
a plusieurs méthodes de découverte, permettant au client une grande
souplesse. D'abord, le client peut simplement tenter sa chance : on
ouvre une connexion QUIC vers le port 443 et
on voit bien si ça marche ou si on reçoit un message ICMP nous
disant que c'est raté. Ici, un exemple vu avec
tcpdump :
11:07:20.368833 IP6 (hlim 64, next-header UDP (17) payload length: 56) 2a01:e34:ec43:e1d0:554:492d:1a13:93e4.57926 > 2001:41d0:302:2200::180.443: [udp sum ok] UDP, length 48 11:07:20.377878 IP6 (hlim 52, next-header ICMPv6 (58) payload length: 104) 2001:41d0:302:2200::180 > 2a01:e34:ec43:e1d0:554:492d:1a13:93e4: [icmp6 sum ok] ICMP6, destination unreachable, unreachable port, 2001:41d0:302:2200::180 udp port 443
Si ça rate, on se rabat en une version de HTTP sur TCP (les premiers tests menés par Google indiquaient qu'entre 90 et 95 % des utilisateurs avaient une connectivité UDP correcte et pouvaient donc utiliser QUIC). Mais le cas ci-dessus était le cas idéal où on avait reçu le message ICMP et où on avait pu l'authentifier. Comme il est possible qu'un pare-feu fasciste et méchant se trouve sur le trajet, et jette silencieusement les paquets UDP, sans qu'on reçoive de réponse, même négative, il faut que le client soit plus intelligent que cela, et essaie très vite une autre version de HTTP, suivant le principe des globes oculaires heureux (RFC 8305). L'Internet est en effet farci de middleboxes qui bloquent tout ce qu'elles ne connaissent pas, et le client HTTP ne peut donc jamais être sûr qu'UDP passera. C'est même parfois un conseil explicite de certains vendeurs.
Sinon, le serveur
peut indiquer explicitement qu'il gère HTTP/3 lors d'une connexion
avec une vieille version de HTTP, via l'en-tête
Alt-Svc:
(RFC 7838). Le
client essaie d'abord avec une vieille version de HTTP, puis est
redirigé, et se souviendra ensuite de la redirection. Par exemple,
si la réponse HTTP contient :
Alt-Svc: h3=":443"
Alors le client sait qu'il peut essayer HTTP/3 sur le port
443. Il peut y avoir plusieurs services alternatifs dans un
Alt-Svc:
(par exemple les versions
expérimentales de HTTP/3).
Dernière possibilité, celle décrite dans le RFC 8164. (Par contre, l'ancien mécanisme
Upgrade:
et sa réponse 101 n'est plus utilisé
par HTTP/3.)
À propos de port, j'ai cité jusqu'à
présent le 443 car c'est le port par défaut, mais on peut évidemment
en utiliser un autre, en l'indiquant dans
l'URL, ou via
Alt-Svc:
. Quant aux URL de plan
http:
(tout court, sans le S), ils ne sont pas
utilisables directement (puisque QUIC n'a pas de mode en clair, TLS
est obligatoire) mais peuvent quand même rediriger vers du HTTP/3,
via Alt-Svc:
.
Le client a donc découvert le serveur, et il se connecte. Il doit
utiliser l'ALPN TLS (RFC 7301,
qui est quasi-obligatoire avec QUIC, et indiquer comme application
h3
(cf. le registre
IANA des applications). Les réglages divers qui
s'appliqueront à toute la connexion (la liste des réglages possibles
est dans un
registre IANA) sont envoyés dans une trame HTTP de type
SETTINGS
. La connexion QUIC peut évidemment
rester ouverte une fois les premières requêtes envoyées et les
premières réponses reçues, afin d'amortir le coût de connexion sur
le plus grand nombre de requêtes possible. Évidemment, le serveur
est autorisé à couper les connexions qui lui semblent inactives (ce
qui se fait normalement en envoyant une trame HTTP de type
GOAWAY
), le client doit donc être prêt à les
réouvrir.
Voilà pour la gestion de connexions. Et, une fois qu'on est
connecté, comment se font les requêtes (section 4 du RFC) ? Pour
chaque requête, on envoie une trame HTTP de type
HEADERS
contenant les en-têtes HTTP (encodés,
je le rappelle, en binaire) et la méthode utilisée
(GET
, POST
, etc), puis une
trame de type DATA
si la requête contient des
données. Puis on lit la réponse envoyée par le serveur. Le ruisseau
est fermé ensuite, chaque ruisseau ne sert qu'à un seul couple
requête/réponse. (Rappelez-vous que, dans QUIC, envoyer une trame
QUIC de type STREAM
suffit à créer le ruisseau
correspondant. Tout ce qui nécessite un état a été fait lors de la
création de la connexion QUIC.)
Comme expliqué plus haut, les couples requête/réponse se font sur un ruisseau, qui ne sert qu'une fois. Ce ruisseau est bidirectionnel (section 6), ce qui permet de corréler facilement la requête et la réponse : elles empruntent le même ruisseau. C'est celui de numéro zéro dans l'exemple plus loin, qui n'a qu'un seul couple requête/réponse. La première requête se fera toujours sur le ruisseau 0, les autres seront que les ruisseaux 4, 8, etc, selon les règles de génération des numéros de ruisseau de QUIC.
Voici,
un exemple, affiché par tshark d'un échange HTTP/3. Pour
pouvoir le déchiffrer, on a utilisé la méthode classique, en
définissant la variable
d'environnement SSLKEYLOGFILE
avant
de lancer le client HTTP (ici, curl), puis en disant à Wireshark d'utiliser
ce fichier contenant la clé (tls.keylog_file:
/tmp/quic.key
dans
~/.config/wireshark/preferences
). Cela donne :
% tshark -n -r /tmp/quic.pcap 1 0.000000 10.30.1.1 → 45.77.96.66 QUIC 1294 Initial, DCID=94b8a6888cb47e3128b13d875980b557d9e415f0, SCID=57e2ce80152d30c1f66a9fcb3c3b49c81c98f329, PKN: 0, CRYPTO, PADDING 2 0.088508 45.77.96.66 → 10.30.1.1 QUIC 1242 Handshake, DCID=57e2ce80152d30c1f66a9fcb3c3b49c81c98f329, SCID=3bd5658cf06b1a5020d44410ab9682bcf277610f, PKN: 0, CRYPTO[Malformed Packet] 3 0.088738 10.30.1.1 → 45.77.96.66 QUIC 185 Handshake, DCID=3bd5658cf06b1a5020d44410ab9682bcf277610f, SCID=57e2ce80152d30c1f66a9fcb3c3b49c81c98f329, PKN: 0, ACK 4 0.089655 45.77.96.66 → 10.30.1.1 QUIC 1239 Handshake, DCID=57e2ce80152d30c1f66a9fcb3c3b49c81c98f329, SCID=3bd5658cf06b1a5020d44410ab9682bcf277610f, PKN: 1, CRYPTO 5 0.089672 10.30.1.1 → 45.77.96.66 QUIC 114 Handshake, DCID=3bd5658cf06b1a5020d44410ab9682bcf277610f, SCID=57e2ce80152d30c1f66a9fcb3c3b49c81c98f329, PKN: 1, ACK, PING 6 0.090740 45.77.96.66 → 10.30.1.1 QUIC 931 Handshake, DCID=57e2ce80152d30c1f66a9fcb3c3b49c81c98f329, SCID=3bd5658cf06b1a5020d44410ab9682bcf277610f, PKN: 2, CRYPTO 7 0.091100 10.30.1.1 → 45.77.96.66 HTTP3 257 Protected Payload (KP0), DCID=3bd5658cf06b1a5020d44410ab9682bcf277610f, PKN: 0, NCI, STREAM(2), SETTINGS, STREAM(10), STREAM(6) 8 0.091163 10.30.1.1 → 45.77.96.66 HTTP3 115 Protected Payload (KP0), DCID=3bd5658cf06b1a5020d44410ab9682bcf277610f, PKN: 1, STREAM(0), HEADERS 9 0.189511 45.77.96.66 → 10.30.1.1 HTTP3 631 Protected Payload (KP0), DCID=57e2ce80152d30c1f66a9fcb3c3b49c81c98f329, PKN: 0, ACK, DONE, CRYPTO, STREAM(3), SETTINGS 10 0.190684 45.77.96.66 → 10.30.1.1 HTTP3 86 Protected Payload (KP0), DCID=57e2ce80152d30c1f66a9fcb3c3b49c81c98f329, PKN: 1, STREAM(7) 11 0.190792 10.30.1.1 → 45.77.96.66 QUIC 85 Protected Payload (KP0), DCID=3bd5658cf06b1a5020d44410ab9682bcf277610f, PKN: 2, ACK 12 0.192047 45.77.96.66 → 10.30.1.1 HTTP3 86 Protected Payload (KP0), DCID=57e2ce80152d30c1f66a9fcb3c3b49c81c98f329, PKN: 2, STREAM(11) 13 0.193299 45.77.96.66 → 10.30.1.1 HTTP3 604 Protected Payload (KP0), DCID=57e2ce80152d30c1f66a9fcb3c3b49c81c98f329, PKN: 3, STREAM(0), HEADERS, DATA 14 0.193421 10.30.1.1 → 45.77.96.66 QUIC 85 Protected Payload (KP0), DCID=3bd5658cf06b1a5020d44410ab9682bcf277610f, PKN: 3, ACK
On y voit au début l'ouverture de la connexion QUIC. Puis, à partir
du datagramme 7 commence HTTP/3, avec la création des ruisseaux
nécessaires et l'envoi de la trame SETTINGS
(ruisseau 3) et de HEADERS
(ruisseau 0). Pas de
trame DATA
, c'était une simple requête HTTP
GET
, sans corps. Le serveur répond par son
propre SETTINGS
, une trame
HEADERS
et une DATA
(la
réponse est de petite taille et tient dans un seul datagramme). Vous
avez l'analyse complète et détaillée de cette session QUIC dans le fichier
http3-get-request.txt
.
La section 7 décrit les différents types de trames définis pour
HTTP/3 (rappelez-vous que ce ne sont pas les mêmes que les trames
QUIC : toutes voyagent dans des trames QUIC de type
STREAM
), comme :
HEADERS
(type 1), qui transporte les
en-têtes HTTP, ainsi que les « pseudo en-têtes » comme la méthode
HTTP. Ils sont comprimés avec QPACK (RFC 9114).DATA
(type 0) qui contient le corps des
messages. Une requête GET
ne sera sans doute
pas accompagnée de ce type de trames mais la réponse aura, dans la
plupart des cas, un corps et donc une ou plusieurs trames de type
DATA
.SETTINGS
(type 4), qui définit des
réglages communs à toute la connexion. Les réglages possibles
figurent dans un
registre IANA.GOAWAY
(type 7) sert à dire qu'on s'en va.Les différents types de trames QUIC sont dans un registre IANA. Ce registre est géré selon les politiques (cf. RFC 8126) « action de normalisation » pour une partie, et « spécification nécessaire » pour une autre partie de l'espace des types, plus ouverte. Notez que les types de trame HTTP/3 ne sont pas les mêmes que ceux de HTTP/2, même s'ils sont très proches. Des plages de types sont réservées (section 7.2.8) pour le graissage, l'utilisation délibérée de types non existants pour s'assurer que le partenaire respecte bien la norme, qui commande d'ignorer les types de trame inconnus. (Le graissage et ses motivations sont expliqués dans le RFC 8701.)
Wireshark peut aussi analyser graphiquement les données et vous
pouvez voir ici une trame QUIC de type STREAM
(ruisseau 0) contenant une trame HTTP/3 de type
HEADERS
. Notez que cette version de Wireshark
ne décode pas encore la requête HTTP (ici, un simple
GET
) :
Et une fois qu'on a terminé ? On ferme la connexion (section 5), ce qui peut arriver pour plusieurs raisons, par exemple :
GOAWAY
,Bien sûr, des tas de choses peuvent aller mal pendant une connexion HTTP/3. La section 8 du RFC définit donc un certain nombre de codes d'erreurs pour signaler les problèmes. Ils sont stockés dans un registre IANA.
L'un des buts essentiels de QUIC était d'améliorer la
sécurité. La section 10 du RFC discute de ce qui a été fait. En
gros, la sécurité de HTTP/3 est celle de HTTP/2 quand il est combiné
avec TLS, mais il y a quelques points à garder en tête. Par exemple,
HTTP/3 a des fonctions, comme la compression d'en-têtes (RFC 9114) ou comme le
contrôle de flux de chaque ruisseau qui, utilisées sans précaution,
peuvent mener à une importante allocation de ressources. Les
réglages définis dans la trame SETTINGS
servent
entre autres à mettre des limites strictes à la consommation de
ressources.
Autre question de sécurité, liée cette fois à la protection de la
vie privée, la taille des
données. Par défaut, TLS ne cherche pas à dissimuler la taille des
paquets. Si on ne sait pas quelle page a chargé un client HTTPS,
l'observation de la taille des données reçues, comparée à ce qu'on
obtient en se connectant soi-même au serveur, permet de trouver avec
une bonne probabilité les ressources demandées (c'est ce qu'on nomme
l'analyse de trafic). Pour contrer cela, on peut utiliser le
remplissage. HTTP/3 peut utiliser celui de
QUIC (avec les trames QUIC de type PADDING
) ou
bien faire son propre remplissage avec des trames HTTP utilisant des
types non alloués, mais dont l'identificateur est réservé (les
trames HTTP de type inconnu doivent être ignorées par le
récepteur).
Toujours question sécurité, l'effort de QUIC et de HTTP/3 pour diminuer la latence, avec notamment la possibilité d'envoyer des données dès le premier paquet (early data), a pour conséquences de faciliter les attaques par rejeu. Il faut donc suivre les préconisations du RFC 8470.
La possibilité qu'offre QUIC de faire migrer une session d'une
adresse IP vers une autre (par exemple quand un ordiphone passe de
4G en WiFi ou réciproquement) soulève également des questions de
sécurité. Ainsi, journaliser l'adresse IP du
client (le access_log
d'Apache…) n'aura plus forcément le même
sens, cette adresse pouvant changer en cours de route. Idem pour les
ACL.
Ah, et question vie privée, le fait que HTTP/3 et QUIC encouragent à utiliser une seule session pour un certain nombre de requêtes peut permettre de corréler entre elles des requêtes. Sans cookie, on a donc une traçabilité des utilisateurs.
HTTP/3 a beaucoup de ressemblances avec HTTP/2. L'annexe A de
notre RFC détaille les différences entre ces deux versions et
explique comment passer de l'une à l'autre. Ainsi, la gestion des
ruisseaux qui, en HTTP/2, était faite par HTTP, est désormais faite
par QUIC. Une des conséquences est que l'espace des identificateurs
de ruisseau est bien plus grand, limitant le risque qu'on tombe à
cours. HTTP/3 a moins de types de trames que HTTP/2 car une partie
des fonctions assurées par HTTP/2 le sont par QUIC et échappent donc
désormais à HTTP (comme PING
ou WINDOW_UPDATE
).
HTTP/3 est en plein déploiement actuellement et vos logiciels
favoris peuvent ne pas encore l'avoir, ou bien n'avoir qu'une
version expérimentale de HTTP/3 et encore pas par défaut. Par
exemple, pour Google
Chrome, il faut le lancer google-chrome
--enable-quic --quic-version=h3-24
(h3-24
étant une version de développement de
HTTP/3). Pour Firefox, vous pouvez suivre
cet article ou celui-ci. Quand vous lirez ces lignes, tout sera
peut-être plus simple, avec le HTTP/3 officiel dans beaucoup de
clients HTTP.
J'ai montré plus haut quelques essais avec curl. Pour avoir HTTP/3 avec curl, il faut actuellement quelques bricolages, HTTP/3 n'étant pas forcément compilé dans le curl que vous utilisez. Déjà, il faut utiliser un OpenSSL spécial, disponible ici. Sinon, vous aurez des erreurs à la compilation du genre :
openssl.c:309:7: warning: implicit declaration of function ‘SSL_provide_quic_data’ [-Wimplicit-function-declaration] 309 | if (SSL_provide_quic_data(ssl, from_ngtcp2_level(crypto_level), data, | ^~~~~~~~~~~~~~~~~~~~~
ngtcp2 peut se compiler avec GnuTLS et pas le
OpenSSL spécial, mais curl ne l'accepte pas (configure:
error: --with-ngtcp2 was specified but could not find
ngtcp2_crypto_openssl pkg-config file.
). Ensuite, il
faut les bibliothèques ngtcp2 et nghttp3. Une fois
celles-ci prêtes, vous configurez curl :
% ./configure --with-ngtcp2 --with-nghttp3 ... HTTP2: enabled (nghttp2) HTTP3: enabled (ngtcp2 + nghttp3) ...
Vérifiez bien que vous avez la ligne HTTP3
indiquant que HTTP3 est activé. De même, un curl
--version
doit vous afficher HTTP3
dans les protocoles gérés. Vous pourrez enfin faire du HTTP/3 :
% curl -v --http3 https://quic.tech:8443 * Trying 45.77.96.66:8443... * Connect socket 5 over QUIC to 45.77.96.66:8443 * Connected to quic.tech () port 8443 (#0) * Using HTTP/3 Stream ID: 0 (easy handle 0x55879ffd4c40) > GET / HTTP/3 > Host: quic.tech:8443 > user-agent: curl/7.76.1 > accept: */* > * ngh3_stream_recv returns 0 bytes and EAGAIN * ngh3_stream_recv returns 0 bytes and EAGAIN * ngh3_stream_recv returns 0 bytes and EAGAIN < HTTP/3 200 < server: quiche < content-length: 462 < <!DOCTYPE html> <html> ...
Pour davantage de lecture sur HTTP/3 :
Si vous utilisez Tor, notez que QUIC et HTTP/2 (j'ai bien dit HTTP/2, puisque Tor ne gère pas UDP et donc pas QUIC) peuvent mettre en cause la protection qu'assure Tor. Une analyse sommaire est disponible En gros, si QUIC, grâce à son chiffrement systématique, donne moins d'infos au réseau, il en fournit peut-être davantage au serveur.
Ah, sinon j'ai présenté HTTP/3
à ParisWeb et
les supports sont disponibles en parisweb2021-http3-bortzmeyer.pdf
. Et Le blog de curl est désormais accessible
en HTTP/3. Voici d'autres parts quelques articles
intéressants annonçant la publication de HTTP/3 :
Date de publication du RFC : Juin 2022
Auteur(s) du RFC : M. Thomson (Mozilla), C. Benfield (Apple)
Chemin des normes
Réalisé dans le cadre du groupe de travail IETF httpbis
Première rédaction de cet article le 7 juin 2022
Le protocole HTTP, à la base des échanges sur le Web, a plusieurs versions, 1, 2 et 3. Toutes ont en commun la même sémantique, décrite dans le RFC 9110. Mais l'encodage sur le câble est différent. HTTP/2 a un encodage binaire et un transport spécifique (binaire, multiplexé, avec possibilité de push). Fini de déboguer des serveurs HTTP avec telnet. En échange, cette version promet d'être plus rapide, notamment en diminuant la latence lors des échanges. Ce RFC remplace l'ancienne norme HTTP/2 (RFC 7540), mais le protocole ne change pas, il s'agit surtout d'une réécriture pour suivre le nouveau cadre de normalisation, où un RFC générique, le RFC 9110 spécifie la sémantique de HTTP et où il y a un RFC spécifique par version.
La section 1 de notre RFC résume les motivations derrère cette version 2, et notamment les limites de HTTP/1.1, normalisé dans le RFC 9112 :
Au contraire, HTTP/2 n'utilise toujours qu'une seule connexion TCP, de longue durée (ce qui sera plus sympa pour le réseau). L'encodage étant entièrement binaire, le traitement par le récepteur est normalement plus rapide. Le RFC note toutefois que HTTP/2, qui dépend de TCP et n'utilise qu'une seule connexion TCP, ne résout pas le problème du head-of-line blocking. (Pour cela, il faudrait HTTP/3, normalisé dans le RFC 9114.)
La section 2 de notre RFC résume l'essentiel de ce qu'il faut
savoir sur HTTP/2. Il garde la sémantique générale de HTTP (donc,
par exemple, un GET
de
/cetteressourcenexistepas
fait un 404). La
norme HTTP/2 ne normalise que le transport des messages, pas les
messages ou leurs réponses (qui sont décrits par le RFC 9110). Notez que HTTP/2 s'appelait il y a très longtemps
SPDY (initialement lancé par
Google).
Avec HTTP/2, l'unité de base de la communication est la
trame (frame, et, dans
HTTP/2, vous pouvez oublier la définition traditionnelle qui en fait
l'équivalent du paquet, mais pour la couche 2). Chaque trame a un type et, par exemple,
les échanges HTTP traditionnels se feront avec simplement une trame
HEADERS
en requête et une
DATA
en réponse. Certains types sont
spécifiques aux nouvelles fonctions de HTTP/2, comme
SETTINGS
ou
PUSH_PROMISE
.
Les trames voyagent ensuite dans des ruisseaux
(streams), chaque ruisseau hébergeant un et un
seul échange requête/réponse. On crée donc un ruisseau à chaque fois
qu'on a un nouveau GET
ou
POST
à faire. Les petits ruisseaux sont ensuite
multiplexés dans une grande rivière, l'unique connexion TCP entre un
client HTTP et un serveur. Les ruisseaux ont des mécanismes de
contrôle du trafic (ils avaient aussi un mécanisme de prioritisation
entre eux, que notre RFC abandonne).
Les en-têtes sont comprimés, en favorisant le cas le plus courant, de manière à s'assurer, par exemple, que la plupart des requêtes HTTP tiennent dans un seul paquet de la taille des paquets Ethernet.
Bon, maintenant, les détails pratiques (le RFC fait 90
pages). D'abord, l'établissement de la connexion. HTTP/2 tourne
au-dessus de TCP. Comment on fait pour savoir si le serveur
accepte HTTP/2 ? C'est marqué dans l'URL ? Non, les URL sont les mêmes,
avec les plans http:
et
https:
. On utilise un nouveau port, succédant au 80 de HTTP ?
Non. Les ports sont les mêmes, 80 et 443. On regarde dans le
DNS ou ailleurs si
le serveur sait faire du HTTP/2 ? Pas encore, bien que le futur
type de données DNS HTTPS
permettra cela. Aujourd'hui, les
méthodes pour savoir si le client doit tenter HTTP/2 sont :
--http2
de curl dans les exemples plus loin),Alt-Svc:
du RFC 7838, qui nécessite de tenter en HTTP/1 d'abord,h2
(HTTP/2 sur TLS). Le serveur, recevant
l'extension ALPN avec h2
, saura ainsi qu'on
fait du HTTP/2 et on pourra tout de suite commencer l'échange de
trames HTTP/2. (h2
est dans le
registre IANA. Dans le RFC 7540, il y
avait aussi un h2c
dont l'usage est
maintenant abandonné.)Upgrade:
qui était dans la
section 6.7 du RFC 7230 est désormais
abandonné.Une fois qu'un client aura réussi à établir une connexion avec un serveur en HTTP/2, il sait que le serveur gère ce protocole. Il peut s'en souvenir, pour les futures connexions (mais attention, ce n'est pas une indication parfaite : un serveur peut abandonner HTTP/2, par exemple).
Maintenant, c'est parti, on s'envoie des trames (il y a d'abord
une préface, un nombre magique qui permet de
s'assurer que tout le monde comprend bien HTTP/2, mais je n'en
parlerai pas davantage). À quoi
ressemblent ces trames (section 4) ? Elles commencent par un en-tête
indiquant leur longueur, leur type (comme
SETTINGS
, HEADERS
,
DATA
… cf. section 6), des options (comme
ACK
qui sert aux trames de type
PING
à distinguer requête et réponse) et
l'identificateur du ruisseau auquel la trame appartient (un nombre
sur 31 bits). Le format complet est en section 4.1.
Les en-têtes HTTP sont comprimés selon la méthode normalisée dans le RFC 7541.
Les ruisseaux (streams), maintenant. Ce sont donc des suites ordonnées de trames, bi-directionnelles, à l'intérieur d'une connexion HTTP/2. Une connexion peut comporter plusieurs ruisseaux, chacun identifié par un stream ID (un entier de quatre octets, pair si le ruisseau a été créé par le serveur et impair autrement). Les ruisseaux sont ouverts et fermés dynamiquement et leur durée de vie n'est donc pas celle de la connexion HTTP/2. Contrairement à TCP, il n'y a pas de « triple poignée de mains » : l'ouverture d'un ruisseau est unilatérale et peut donc se faire très vite (rappelez-vous que chaque échange HTTP requête/réponse nécessite un ruisseau qui lui est propre ; pour vraiment diminuer la latence, il faut que leur création soit rapide). Les identificateurs ne sont jamais réutilisés (si on tombe à cours, la seule solution est de fermer la connexion TCP et d'en ouvrir une autre).
Un mécanisme de contrôle du flot s'assure que les ruisseaux se
partagent pacifiquement la connexion. C'est donc une sorte de TCP
dans le TCP, réinventé pour les besoins de HTTP/2 (section 5.2 et
relire aussi le RFC 1323). Le récepteur
indique (dans une trame WINDOWS_UPDATE
) combien
d'octets il est prêt à recevoir (64 Kio par défaut) et l'émetteur
s'arrête dès qu'il a rempli cette fenêtre d'envoi. (Plus exactement,
s'arrête d'envoyer des trames DATA
: les
autres, les trames de contrôle, ne sont pas soumises au contrôle du
flot).
Comme si ce système des connexions dans les connexions n'était pas assez compliqué comme cela, il y a aussi des dépendances entre ruisseaux. Un ruisseau peut indiquer qu'il dépend d'un autre et, dans ce cas, les ressources seront allouées d'abord au ruisseau dont on dépend. Par exemple, le code JavaScript ne peut en général commencer à s'exécuter que quand toute la page est chargée, et on peut donc le demander dans un ruisseau dépendant de celle qui sert à charger la page. On peut dépendre d'un ruisseau dépendant, formant ainsi un arbre de dépendances.
Il peut bien sûr y avoir des erreurs dans la
communication. Certaines affectent toute la connexion, qui devra
être abandonnée, mais d'autres ne concernent qu'un seul
ruisseau. Dans le premier cas, celui qui détecte l'erreur envoie une
trame GOAWAY
(dont on ne peut pas garantir
qu'elle sera reçue, puisqu'il y a une erreur) puis coupe la
connexion TCP. Dans le second cas, si le problème ne concerne qu'un
seul ruisseau, on envoie la trame RST_STREAM
qui arrête le traitement du ruisseau.
HTTP/2 avait (RFC 7540, section 5.3) un mécanisme de priorité entre trames, qui permettait d'éviter, par exemple, que la récupération d'une grosse image ne ralentisse le chargement d'une feuille de style. Mais il était trop complexe, et a été peu mis en œuvre, la plupart des serveurs ignoraient les demandes de priorité des clients. Un nouveau mécanisme est décrit dans le RFC 9218.
Notre section 5 se termine avec des règles qui indiquent comment gérer des choses inconnues dans le dialogue. Ces règles permettent d'étendre HTTP/2, en s'assurant que les vieilles mises en œuvre ne pousseront pas des hurlements devant les nouveaux éléments qui circulent. Par exemple, les trames d'un type inconnu doivent être ignorées et mises à la poubelle directement, sans protestation.
On a déjà parlé plusieurs fois des trames, la section 6 du RFC détaille leur définition. Ce sont aux ruisseaux ce que les paquets sont à IP et les segments à TCP. Les trames ont un type (un entier d'un octet). Les types possibles sont enregistrés à l'IANA. Les principaux types actuels sont :
DATA
(type 0), les trames les plus
nombreuses, celles qui portent les données, comme les pages
HTML (elles
peuvent aussi contenir du
remplissage, pour éviter qu'un observateur ne
déduise de la taille des réponses la page qu'on regardait,
cf. section 10.7),HEADERS
(type 1), qui portent les
en-têtes HTTP, dûment comprimés selon le RFC 7541,PRIORITY
(type 2) indiquait la priorité que
l'émetteur donne au ruisseau qui porte cette trame, mais ce type de trame
n'est désormais plus utilisé,RST_STREAM
(type 3), dont j'ai parlé plus
haut à propos des erreurs, permet de terminer un ruisseau (filant la
métaphore, on pourrait dire que cela assèche le ruisseau ?),SETTINGS
(type 4), permet d'envoyer des
paramètres, comme SETTINGS_HEADER_TABLE_SIZE
,
la taille de la table utilisée pour la compression des en-têtes,
SETTINGS_MAX_CONCURRENT_STREAMS
pour indiquer
combien de ruisseaux est-on prêt à gérer, etc (la liste des
paramètres est dans un
registre IANA),PUSH_PROMISE
(type 5) qui indique qu'on
va transmettre des données non sollicitées
(push), du moins si le paramètre
SETTINGS_ENABLE_PUSH
est à 1,PING
(type 6) qui permet de tester le
ruisseau (le partenaire va répondre avec une autre trame
PING
, ayant l'option ACK
à
1),GOAWAY
(type 7) que nous avons déjà vu
plus haut, sert à mettre fin proprement (le pair est informé de ce
qui va se passer) à une connexion,WINDOW_UPDATE
(type 8) sert à faire
varier la taille de la fenêtre (le nombre d'octets qu'on peut encore
accepter, cf. section 6.9.1),CONTINUATION
(type 9), indique la suite
d'une trame précédente. Cela n'a de sens que pour certains types
comme HEADERS
(ils peuvent ne pas tenir dans
une seule trame) ou CONTINUATION
lui-même. Mais
une trame CONTINUATION
ne peut pas être
précédée de DATA
ou de
PING
, par exemple.Dans le cas vu plus haut d'erreur entrainant la fin d'un ruisseau
ou d'une connexion entière, il est nécessaire d'indiquer à son
partenaire en quoi consistait l'erreur en question. C'est le rôle
des codes d'erreur de la section 7. Stockés sur quatre octets (et
enregistrés dans un
registre IANA), ils sont transportés par les trames
RST_STREAM
ou GOAWAY
qui
terminent, respectivement, ruisseaux et connexions. Parmi ces
codes :
NO_ERROR
(code 0), pour les cas de
terminaison normale,PROTOCOL_ERROR
(code 1) pour ceux où le
pair a violé une des règles de HTTP/2, par exemple en envoyant une
trame CONTINUATION
qui n'était pas précédée de
HEADERS
, PUSH_PROMISE
ou
CONTINUATION
,INTERNAL_ERROR
(code 2), un malheur est
arrivé,ENHANCE_YOUR_CALM
(code 11), qui ravira
les amateurs de spam et de
Viagra, demande au partenaire en face de se
calmer un peu, et d'envoyer moins de requêtes.Toute cette histoire de ruisseaux, de trames, d'en-têtes
comprimés et autres choses qui n'existaient pas en HTTP/1 est bien
jolie mais HTTP/2 n'a pas été conçu comme un remplacement de TCP,
mais comme un moyen de faire passer des dialogues HTTP. Comment met-on
les traditionnelles requêtes/réponses HTTP sur une connexion
HTTP/2 ? La section 8 répond à cette question. D'abord, il faut se
rappeler que HTTP/2 est du HTTP. La sémantique est donc celle du
RFC 9110. Il y a quelques différences comme le
fait que certains en-têtes disparaissent, par exemple
Connection:
(section 8.2.2) qui n'est plus
utile en HTTP/2 ou Upgrade:
(section 8.6).
HTTP est requête/réponse. Pour envoyer une requête, on utilise un
nouveau ruisseau (envoi d'une trame avec un numéro de ruisseau non
utilisé), sur laquelle on lira la réponse (les ruisseaux ne sont pas
persistents). Dans le cas le plus fréquent, la requête sera composée
d'une trame HEADERS
contenant les en-têtes
(comme User-Agent:
ou
Host:
, cf. RFC 9110,
section 10.1) et les « pseudo-en-têtes » comme la méthode
(GET
, POST
, etc), avec
parfois des trames DATA
(cas d'un
POST
). La réponse comprendra une trame
HEADERS
avec les en-têtes (comme
Content-Length:
) et les pseudo-en-têtes comme
le code de retour HTTP (200, 403, 500, etc) suivie de plusieurs
trames DATA
contenant les données (HTML, CSS, images,
etc). Des variantes sont possibles (par exemple, les trames
HEADERS
peuvent être suivies de trames
CONTINUATION
). Les en-têtes ne sont pas
transportés sous forme texte (ce qui était le cas en HTTP/1, où on
pouvait utiliser telnet comme client HTTP)
mais encodés en binaire, et comprimés selon
le RFC 7541. À noter que cet encodage implique
une mise du nom de l'en-tête en minuscules.
J'ai parlé plus haut des pseudo-en-têtes : c'est le mécanisme
HTTP/2 pour traiter des informations qui ne sont pas des en-têtes
en HTTP (section 8.3). Ces informations sont mises dans les
HEADERS
HTTP/2, précédés d'un
deux-points. C'est le cas de la méthode (RFC 9110, section 9.3), donc GET
sera encodé :method GET
. L'URL sera éclaté dans
les pseudo-en-têtes :scheme
,
:path
, etc. Idem pour la réponse HTTP, le
fameux code à trois lettres est désormais un pseudo-en-tête,
:status
.
Le RFC met en garde les programmeur·ses : certains caractères peuvent être dangereux car profitant des faiblesses de certains analyseurs ou bien utilisant le fait que HTTP n'est pas toujours de bout en bout et qu'un message peut être traduit de HTTP/1 en HTTP/2 (ou réciproquement). Un deux-points dans le nom d'un champ, par exemple, pourrait produire un message dont l'interprétation ne serait pas celle attendue (ce qu'on nomme le request smuggling).
Voici des exemples de requêtes HTTP (mais vous ne le verrez pas ainsi si vous espionnez le réseau, en raison de la compression du RFC 7541) :
### HTTP/1, pas de corps dans la requête ### GET /resource HTTP/1.1 Host: example.org Accept: image/jpeg ### HTTP/2 (une trame HEADERS) :method = GET :scheme = https :path = /resource host = example.org accept = image/jpeg
Puis une réponse qui n'a pas de corps :
### HTTP/1 ### HTTP/1.1 304 Not Modified ETag: "xyzzy" Expires: Thu, 23 Jan ... ### HTTP/2, une trame HEADERS ### :status = 304 etag = "xyzzy" expires = Thu, 23 Jan ...
Une réponse plus traditionnelle, qui inclut un corps :
### HTTP/1 ### HTTP/1.1 200 OK Content-Type: image/jpeg Content-Length: 123 {binary data} ### HTTP/2 ### # trame HEADERS :status = 200 content-type = image/jpeg content-length = 123 # trame DATA {binary data}
Plus compliqué, un cas où les en-têtes de la requête ont été mis dans deux trames, et où il y avait un corps dans la requête :
### HTTP/1 ### POST /resource HTTP/1.1 Host: example.org Content-Type: image/jpeg Content-Length: 123 {binary data} ### HTTP/2 ### # trame HEADERS :method = POST :path = /resource :scheme = https # trame CONTINUATION content-type = image/jpeg host = example.org content-length = 123 # trame DATA {binary data}
Nouveauté introduite par HTTP/2, la possibilité pour le serveur
de pousser (push, section 8.4 de notre RFC) du
contenu non sollicité vers le client (sauf si cette possibilité a
été coupée par le paramètre
SETTINGS_ENABLE_PUSH
). Pour cela, le serveur
(et lui seul) envoie une trame de type
PUSH_PROMISE
au client, en utilisant le
ruisseau où le client avait fait une demande originale (donc, la
sémantique de PUSH_PROMISE
est « je te promets
que lorsque le moment sera venu, je répondrai plus longuement à ta
question »). Cette trame contient une requête HTTP. Plus tard,
lorsque le temps sera venu, le serveur tiendra sa promesse en
envoyant la « réponse » de cette « requête » sur le ruisseau qu'il
avait indiqué dans le PUSH_PROMISE
.
Et enfin, à propos des méthodes HTTP/1 et de leur équivalent en
HTTP/2, est-ce que CONNECT
(RFC 9110, section 9.3.6) fonctionne toujours ? Oui, on peut
l'utiliser pour un tunnel sur un
ruisseau. (Un tunnel sur un ruisseau... Beau défi pour le
génie civil.)
La section 9 de notre RFC rassemble quelques points divers. Elle rappelle que, contrairement à HTTP/1, toutes les connexions sont persistentes et que le client n'est pas censé les fermer avant d'être certain qu'il n'en a plus besoin. Tout doit passer à travers une connexion vers le serveur et les clients ne doivent plus utiliser le truc d'ouvrir plusieurs connexions HTTP avec le serveur. De même, le serveur laisse les connexions ouvertes le plus longtemps possible, mais a le droit de les fermer s'il doit économiser des ressources.
À noter qu'on peut utiliser une connexion prévue pour un autre
nom, du moment que cela arrive au même serveur (même adresse IP). Le
pseudo-en-tête :authority
sert à départager les
requêtes allant à chacun des serveurs. Mais attention si la session
utilise TLS !
L'utilisation d'une connexion avec un autre
:authority
(host + port)
n'est possible que si le certificat serveur qui a été utilisé est
valable pour tous (par le biais des
subjectAltName
, ou bien d'un joker).
À propos de TLS, la section
9.2 prévoit quelques règles qui n'existaient pas en HTTP/1 (et dont
la violation peut entrainer la coupure de la connexion avec l'erreur
INADEQUATE_SECURITY
) :
Puisqu'on parle de sécurité, la section 10 traite un certain nombre de problèmes de sécurité de HTTP/2. Elle rappelle que TLS est fortement recommandé (mais il n'est devenu obligatoire qu'avec HTTP/3, HTTP/2 permettant toutefois une utilisation « opportuniste », cf. RFC 8164). Parmi les problèmes qui sont spécifiques à HTTP/2, on note que ce protocole demande plus de ressources que HTTP/1, ne serait-ce que parce qu'il faut maintenir un état pour la compression. Il y a donc potentiellement un risque d'attaque par déni de service. Une mise en œuvre prudente veillera donc à limiter les ressources allouées à chaque connexion.
Enfin, il y a la question de la vie privée, un sujet chaud dans le monde HTTP depuis longtemps. Les options spécifiques à HTTP/2 (changement de paramètres, gestion du contrôle de flot, traitement des innombrables variantes du protocole) peuvent permettre d'identifier une machine donnée par son comportement. HTTP/2 facilite donc le fingerprinting.
En outre, comme une seule connexion TCP est utilisée pour toute une visite sur un site donné, cela peut rendre explicite une information comme « le temps passé sur un site », information qui était implicite en HTTP/1, et qui devait être reconstruite.
Comme on le voit, HTTP/2 est bien plus complexe que HTTP/1. On ne peut pas espérer programmer un client ou un serveur en quelques heures, comme on le fait avec HTTP/1. C'est en partie pour cela que personne ne prévoit un abandon de HTTP/1, qui continuera à coexister avec HTTP/2 (et HTTP/3 !) pendant très longtemps encore.
Question mises en œuvre, HTTP/2 est désormais présent dans la quasi-totalité des clients, serveurs et bibliothèques HTTP. Ici, avec curl, en forçant l'utilisation de HTTP/2 dès le début :
% curl -v --http2 https://www.bortzmeyer.org/7540.html * Trying 2001:4b98:dc0:41:216:3eff:fe27:3d3f:443... * Connected to www.bortzmeyer.org (2001:4b98:dc0:41:216:3eff:fe27:3d3f) port 443 (#0) * ALPN, offering h2 * ALPN, offering http/1.1 ... * ALPN, server accepted to use h2 * Server certificate: * subject: CN=www.bortzmeyer.org ... * Using HTTP2, server supports multiplexing * Copying HTTP/2 data in stream buffer to connection buffer after upgrade: len=0 * h2h3 [:method: GET] * h2h3 [:path: /7540.html] * h2h3 [:scheme: https] * h2h3 [:authority: www.bortzmeyer.org] * h2h3 [user-agent: curl/7.82.0] * h2h3 [accept: */*] * Using Stream ID: 1 (easy handle 0x5639a5d10cf0) > GET /7540.html HTTP/2 > Host: www.bortzmeyer.org > user-agent: curl/7.82.0 > accept: */* ... < HTTP/2 200 ... < etag: "b4b0-5de09d5830d11" < content-type: text/html; charset=UTF-8 < date: Thu, 05 May 2022 13:07:08 GMT < server: Apache/2.4.53 (Debian) < <?xml version="1.0" ?> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> <html xml:lang="fr" lang="fr" xmlns="http://www.w3.org/1999/xhtml"> <head> ...
Vous voulez voir un joli pcap de HTTP/2 ? La plupart des sites accessibles en HTTP/2 (et parfois des clients) imposent TLS. Il faut donc, si on veut voir « à l'intérieur » des paquets, utiliser la technique classique d'exportation de la clé :
% export SSLKEYLOGFILE=/tmp/http2.key % curl --http2 https://www.bortzmeyer.org/7540.html
Puis vérifiez que dans les préférences de
Wireshark, on a cette clé (par exemple dans
~/.config/wireshark/preferences
, une ligne
tls.keylog_file: /tmp/http2.key
). On peut alors
regarder le pcap en détail. Voici un tel
pcap et la clé
correspondante. Cela permet de regarder le contenu des
messages avec Wireshark :
Cela permet aussi, avec une commande comme
tshark -V -r http2.pcap > http2.txt
, de
produire un joli fichier
d'analyse. Notez les identificateurs de ruisseaux
(Stream ID) : il n'y en a que deux, 0 et 1, car
on n'a chargé qu'une ressource (il y a un ruisseau dans chaque direction). Je
vous laisse faire vous-même l'opération pour le cas de deux
ressources, avec une commande comme curl -v --http2
https://www.bortzmeyer.org/7540.html
https://www.bortzmeyer.org/9116.html
. Vous verrez alors
le parallélisme de HTTP/2 et les multiples ruisseaux.
Et, sinon, si vous voulez activer HTTP/2 sur un serveur
Apache, c'est aussi
simple que de charger le module http2
et de
configurer :
Protocols h2 http/1.1
Sur Debian, la commande a2enmod
http2
fait tout cela automatiquement. Pour vérifier que
cela a bien été fait, vous pouvez utiliser curl
-v
comme vu plus haut, ou bien un site de test (comme
KeyCDN) ou
encore la fonction Inspect element (clic droit
sur la page, puis onglet Network puis
sélectionner une des ressources chargées) de Firefox :
L'annexe B liste les principaux changements depuis le RFC 7540, notamment :
Upgrade:
pour passer de HTTP/1 en HTTP/2,Et, sinon, si vous voulez vous instruire sur HTTP/2 sans lire tout le RFC, il y a évidemment le livre de Daniel Stenberg.
Date de publication du RFC : Juin 2022
Auteur(s) du RFC : R. Fielding (Adobe), M. Nottingham (Fastly), J. Reschke (greenbytes)
Chemin des normes
Réalisé dans le cadre du groupe de travail IETF httpbis
Première rédaction de cet article le 7 juin 2022
Ce nouveau RFC normalise HTTP/1.1, la plus ancienne version de HTTP encore en service. Il décrit les détails de comment les messages sont représentés sur le réseau, la sémantique de haut niveau étant désormais dans un document séparé, le RFC 9110. Ensemble, ces deux RFC remplacent le RFC 7230.
HTTP est certainement le protocole Internet le plus connu. Il en existe plusieurs versions, ayant toutes en commun la sémantique normalisée dans le RFC 9110. Les versions les plus récentes, HTTP/2 et HTTP/3 sont loin d'avoir remplacé la version 1, plus précisément 1.1, objet de notre RFC et toujours largement répandue. Un serveur HTTP actuel doit donc gérer au moins cette version. (Par exemple, en octobre 2021, les ramasseurs de Google et Baidu utilisaient toujours exclusivement HTTP/1.1.)
Un des avantages de HTTP/1, et qui explique sa longévité, est que c'est un protocole simple, fondé sur du texte et qu'il est donc relativement facile d'écrire clients et serveurs. D'ailleurs, pour illustrer cet article, je vais prendre exemple sur un simple serveur HTTP/1 que j'ai écrit (le code source complet est disponible ici). Le serveur ne gère que HTTP/1 (les autres versions sont plus complexes) et ne vise pas l'utilisation en production : c'est une simple démonstration. Il est écrit en Elixir. Bien sûr, Elixir, comme tous les langages de programmation sérieux, dispose de bibliothèques pour créer des serveurs HTTP (notamment Cowboy). Le programme que j'ai écrit ne vise pas à les concurrencer : si on veut un serveur HTTP pour Elixir, Cowboy est un bien meilleur choix ! C'est en référence à Cowboy que mon modeste serveur se nomme Indian.
Commençons par le commencement, la section 1 de notre RFC rappelle les bases de HTTP (décrites plus en détail dans le RFC 9110).
La section 2 attaque ce qui est spécifique à la version 1 de
HTTP. Avec les URL de plan http:
, on
commence par établir une connexion TCP avec le serveur. Ensuite, un
message en HTTP/1 commence par une ligne de démarrage, suivie d'un
CRLF (fin de ligne sous la forme des deux octets Carriage
Return et Line Feed), d'une série
d'en-têtes ressemblant à celui de l'IMF du
RFC 5322 (par exemple Accept:
text/*
), d'une ligne vide et peut-être d'un corps du
message. Les requêtes du client au serveur et les réponses du
serveur au client sont toutes les deux des messages, la seule
différence étant que, pour la requête, la ligne de démarrage est une
ligne de requête et, pour la réponse, c'est une ligne d'état. (Le
RFC note qu'on pourrait réaliser un logiciel HTTP qui soit à la fois
serveur et client, distinguant requêtes et réponses d'après les
formats distincts de ces deux lignes. En pratique, personne ne
semble l'avoir fait.)
Pour une première démonstration de HTTP, on va utiliser le module http.server du langage Python, qui permet d'avoir un serveur HTTP opérationnel facilement :
% python3 -m http.server Serving HTTP on 0.0.0.0 port 8000 (http://0.0.0.0:8000/) ...
Hop, nous avons un serveur HTTP qui tourne sur le
port 8000. On va utiliser
curl et son option -v
,
qui permet de voir le dialogue (le > indique ce qu'envoie curl,
le < ce qu'il reçoit du serveur en Python) :
% curl -v http://localhost:8000/ > GET / HTTP/1.1 > Host: localhost:8000 > User-Agent: curl/7.68.0 > Accept: */* > < HTTP/1.0 200 OK < Server: SimpleHTTP/0.6 Python/3.8.10 < Date: Thu, 06 Jan 2022 17:24:13 GMT < Content-type: text/html; charset=utf-8 < Content-Length: 660 < <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd"> <html> <head> ...
La ligne qui commence par GET
est la ligne de
démarrage, ici une requête, curl a envoyé trois lignes d'en-tête. La
ligne qui commence par HTTP/1.0
est la ligne de
démarrage de la réponse, et elle est suivie par quatre lignes
d'en-tête. La requête n'avait pas de corps, mais la réponse en a un
(il commence par <!DOCTYPE HTML PUBLIC
), ici
au format HTML. En dépit du H de son nom, HTTP n'a pas
grand'chose de spécifiquement lié à
l'hypertexte, et peut être utilisé pour tout
type de données (le serveur Indian ne renvoie que du texte
brut).
Pour le corps des messages, HTTP utilise certains concepts de MIME (RFC 2045). Mais HTTP n'est pas MIME : l'annexe B détaille les différences.
Le client est censé lire la réponse, commençant par la ligne
d'état, puis tout l'en-tête jusqu'à une ligne vide, puis le corps,
dont la taille est indiquée par le champ
Content-Length:
, ici 660 octets. (Sans ce
champ, le client va lire jusqu'à la fin de la connexion TCP sous-jacente.)
Notez qu'Indian ne fait pas cela bien : il fait une seule opération
de lecture et analyse ensuite le résultat (alors qu'il faudra
peut-être plusieurs opérations, et que, si on utilise les connexions
persistentes, on ne peut découvrir la fin du corps que si on tient
compte de Content-Length:
, ou des délimiteurs
de Transfer-Encxoding: chunked
). Ce choix a été
fait pour simplifier l'analyse syntaxique
(qui devrait normalement être incrémentale,
contrairement à ce que fait Indian, mais la bibliothèque
utilisée ne le permet pas, contrairement à, par exemple tree-sitter). Rappelez-vous que
ce n'est qu'un programme de démonstration.
Quand la réponse est du texte, le client ne doit pas supposer un encodage particulier, il doit lire des octets, quitte à les convertir dans des concepts de plus haut niveau (comme les caractères) plus tard.
Notez tout de suite qu'on trouve de tout dans le monde HTTP, et que beaucoup de clients et de serveurs ne suivent pas forcément rigoureusement la norme dans ses moindres détails. En général, Indian est plutôt strict et colle à la norme, sauf dans les cas où il était absolument nécessaire d'être plus tolérant pour pouvoir être testé avec les clients que j'ai utilisé. Comme souvent sur l'Internet, ces déviations par rapport à la norme permettent des attaques rigolotes comme le request smuggling (section 11.2 du RFC) ou le response splitting (section 11.1).
La réponse du serveur indique un numéro de version, sous la forme de deux chiffres séparés par un point. Ce RFC spécifie la version 1.1 de HTTP (Indian peut aussi gérer la version 1.0).
Commençons par la requête (section 3 du RFC). Elle commence par
une ligne qui comprend la méthode, le chemin et la version de
HTTP. Elles sont séparées par un espace. Pour analyser les requêtes,
Indian utilise la combinaison
d'analyseurs syntaxiques avec NimbleParsec, l'analyseur de la
requête est donc : method |> ignore(string(" ")) |>
concat(path) |> ignore(string(" ")) |>
concat(version)
. (La norme ne prévoit qu'un seul espace,
autrement, on aurait pu prévoir une répétition de string("
")
. Le RFC suggère que cette version plus laxiste est
acceptable mais peut être dangereuse.) La méthode indique ce
que le client veut faire à la ressource désignée. La plus connue des
méthodes est
GET
(récupérer la ressource) mais il en existe
d'autres, et la
liste peut changer. Indian ne met donc pas un choix limitatif
mais accepte tout nom de méthode (method =
ascii_string([not: ?\ ], min: 1)
), quitte à vérifier plus
tard. La ressource sur laquelle le client veut agir est indiquée par
un chemin (ou, dans certains cas par l'URL complet). Ainsi, un client qui
veut récupérer
http://www.example.org/truc?machin
va envoyer
au serveur au moins :
GET /truc?machin HTTP/1.1 Host: www.example.org
Il existe d'autres formes pour la requête mais je ne les présente pas ici (lisez le RFC).
La première ligne de la requête est suivie de l'en-tête, composée
de plusieurs champs (cf. section 5). Voici la requête que génère
wget pour récupérer
https://cis.cnrs.fr/a-travers-les-infrastructures-c-est-la-souverainete-numerique-des-etats-qui-se-joue/
:
% wget -d https://cis.cnrs.fr/a-travers-les-infrastructures-c-est-la-souverainete-numerique-des-etats-qui-se-joue/ ... GET /a-travers-les-infrastructures-c-est-la-souverainete-numerique-des-etats-qui-se-joue/ HTTP/1.1 User-Agent: Wget/1.20.3 (linux-gnu) Accept: */* Accept-Encoding: identity Host: cis.cnrs.fr Connection: Keep-Alive
Une particularité souvent oubliée de HTTP est qu'il n'y a pas de limite de taille à la plupart des éléments du protocole. Les programmeurs se demandent souvent « quelle place dois-je réserver pour tel élément ? » et la réponse est souvent qu'il n'y a pas de limite, juste des indications. Par exemple, notre RFC dit juste qu'il faut accepter des lignes de requête de 8 000 octets au moins.
Le serveur répond avec une ligne d'état et un autre en-tête (section 4). La ligne d'état comprend la version de HTTP, un code de retour formé de trois chiffres, et un message facultatif (là encore, avec un espace comme séparateur). Voici par exemple la réponse d'Indian :
HTTP/1.1 200 Content-Type: text/plain Content-Length: 18 Server: myBeautifulServerWrittenInElixir
Le message est d'autant plus facultatif (Indian n'en met pas) qu'il n'est pas forcément dans la langue du destinataire et qu'il n'est pas structuré, donc pas analysable. Le RFC recommande de l'ignorer.
Beaucoup plus important est le code de retour. Ces trois chiffres indiquent si tout s'est bien passé ou pas. Ils sont décrits en détail dans le RFC 9110, section 15. Bien qu'il s'agisse normalement d'éléments de protocole, certains sont bien connus des utilisatrices et utilisateurs, comme le célèbre 404. Et ils ont une représentation en chats et on a proposé de les remplacer par des émojis.
L'en-tête, maintenant (section 5 du RFC). Il se compose de
plusieurs lignes, chacune comportant le nom du champ, un
deux-points (pas d'espace avant ce
deux-points, insiste le RFC), puis la valeur du champ. Cela
s'analyse dans Indian avec header_line = header_name |>
ignore(string(":")) |> ignore(repeat(string(" "))) |>
concat(header_value) |> ignore(eol)
. Les noms de champs
possibles sont dans un
registre IANA (on peut noter qu'avant ce RFC, ils étaient
mêlés aux champs du courrier électronique dans un même registre).
Après les en-têtes, le corps. Il est en général absent des
requêtes faites avec la méthode GET
mais il est
souvent présent pour les autres méthodes, et il est en général dans
les réponses. Ici, une réponse d'un serveur avec le corps en
JSON :
% curl -v https://atlas.ripe.net/api/v2/measurements/34762605/results/ ... < HTTP/1.1 200 OK < Server: nginx < Date: Tue, 11 Jan 2022 20:19:31 GMT < Content-Type: application/json < Transfer-Encoding: chunked ... [{"fw":5020,"mver":"2.2.0","lts":4,"resultset":[{"time":1641657433,"lts":4,"subid":1,"submax":1,"dst_addr":"127.0.0.1","dst_port":"53","af":4,"src_addr":"127.0.0.1","proto":"UDP","result":{"rt":487.455,"size":127,"abuf":"9+SBgAABA ...
Le champ Content-Length:
est normalement
obligatoire dans la réponse, sauf s'il y a un champ
Transfer-Encoding:
, comme ici. Il permet au
client de gérer sa mémoire, et de savoir s'il a bien tout
récupéré. (Avec TLS, si on reçoit un signal de fin de
l'application, on sait qu'on a toute les données mais, sans TLS, on
ne pourrait pas être sûr, s'il n'y avait ce
Content-Length:
.)
HTTP/1.1 est un protocole simple (quoiqu'il y ait un certain nombre de pièges pour une mise en œuvre réelle) et on peut donc se contenter de telnet comme client HTTP :
% telnet evil.com 80 Trying 66.96.146.129... Connected to evil.com. Escape character is '^]'. GET / HTTP/1.1 Host: evil.com HTTP/1.1 200 OK Date: Sun, 16 Jan 2022 11:31:05 GMT Content-Type: text/html Content-Length: 4166 Connection: keep-alive Server: Apache/2 Last-Modified: Sat, 15 Jan 2022 23:21:33 GMT Accept-Ranges: bytes Cache-Control: max-age=3600 Etag: "1046-5d5a72e24309e" Expires: Sun, 16 Jan 2022 12:14:45 GMT Age: 980 <HTML> <HEAD> <meta content="Microsoft FrontPage 6.0" name="GENERATOR"> <meta content="FrontPage.Editor.Document" name="ProgId">
Les lignes GET / HTTP/1.1
et Host:
evil.com
ont été tapées à la main, une fois telnet
connecté. HTTP/1.1 (contrairement aux versions 2 et 3) fait partie
de ces protocoles en texte, qu'on peut déboguer à la main avec
telnet.
En plus perfectionné que telnet, il y a netcat :
% echo -n "GET /hello HTTP/1.1\r\nConnection: close\r\n\r\n" | nc ip6-localhost 8080 HTTP/1.1 200 Content-Type: text/plain Content-Length: 12 Server: myBeautifulServerWrittenInElixir Hello, ::1!
On a dit plus haut que HTTP/1.1 fonctionnait au-dessus d'une connexion TCP. La section 9 de notre RFC décrit la gestion de cette connexion. (En HTTP 0.9, c'était simple, une transaction = une connexion, mais ça a changé avec HTTP 1.) HTTP n'a pas forcément besoin de TCP (d'ailleurs, HTTP/3 fonctionne sur QUIC), il lui faut juste une liaison fiable faisant passer les octets dans l'ordre et sans perte. Dans HTTP/1.1, c'est TCP qui fournit ce service. (Avec TLS si on fait du HTTPS.) L'établissement d'une connexion TCP prend du temps, et la latence est un des plus gros ennemis de HTTP. Il est donc recommandé de ne pas établir une connexion TCP par transaction HTTP, mais de réutiliser les connexions. Le problème est délicat car le serveur peut avoir envie de supprimer des connexions pour récupérer des ressources. Clients et serveurs doivent donc s'attendre à des comportements variés de la part de leur partenaire.
HTTP/1 n'a pas d'identificateur de requête (comme
a, par exemple, le DNS). Les transactions doivent donc se faire
dans l'ordre : si on envoie une requête A puis une requête B sur la
même connexion TCP, on recevra forcément la réponse A puis la
B. (HTTP/2 et encore plus HTTP/3 ont par contre une certaine dose de
parallélisme.) Les connexions sont persistentes par défaut dans
HTTP/1.1 (ce n'était pas le cas en HTTP/1.0) et des champs de
l'en-tête servent à contrôler cette persistence
(Connection: close
indique qu'on ne gardera pas
la connexion ouverte, et un client poli qui ne fait qu'une requête
doit envoyer ce champ). Dans le code source d'Indian, les accès à
context["persistent-connection"]
vous
montreront la gestion de connexion.
Si le client et le serveur gère les connexions persistentes, le client peut aussi envoyer plusieurs requêtes à la suite, sans attendre les réponses (ce qu'on nomme le pipelining). Les réponses doivent parvenir dans le même ordre (puisqu'il n'y a pas d'identificateur de requête, qui permettrait de les associer à une requête), donc HTTP/1.1 ne permet pas un vrai parallélisme.
Pour économiser les ressources du serveur, un client ne devrait pas ouvrir « trop » de connexions vers un même serveur. (Le RFC 2616, section 8.1.4, mettait une limite de 2 connexions mais cette règle a disparu par la suite.)
Jusqu'à présent, on a parlé de HTTP tournant directement sur
TCP. Mais cela fait passer toutes les données en clair, ce qui est
inacceptable du point de vue sécurité, dans un monde de surveillance
massive. Aujourd'hui, la grande majorité des connexions HTTP passent
sur TLS, un
mécanisme cryptographique qui assure
notamment la confidentialité et
l'authentification du serveur. HTTPS (HTTP
sur TLS) était autrefois normalisé dans le RFC 2818 mais qui a désormais été abandonné au profit du RFC 9110 et de notre RFC 9112. Le principe
pour HTTP/1.1 est simple : une fois la connexion TCP établie, le
client HTTP démarre une session TLS (RFC 8446)
par dessus et voilà. (L'ALPN à utiliser est
http/1.1
.) Lors de la fermeture de la connexion, TLS envoie normalement
un message qui permet de différencier les coupures volontaires et
les pannes (close_notify
, RFC 8446, section 6.1). (Indian ne gère pas TLS, si on veut
le sécuriser - mais ce n'est qu'un programme de démonstration, il
faut le faire tourner derrière stunnel ou
équivalent.)
Pour tester HTTPS à la main, on peut utiliser un programme
distribué avec GnuTLS, ici pour récupérer https://fr.wikipedia.org/wiki/Hunga_Tonga
:
% gnutls-cli fr.wikipedia.org ... Connecting to '2620:0:862:ed1a::1:443'... - subject `CN=*.wikipedia.org,O=Wikimedia Foundation\, Inc.,L=San Francisco,ST=California,C=US', issuer `CN=DigiCert ... ... - Simple Client Mode: GET /wiki/Hunga_Tonga HTTP/1.1 Host: fr.wikipedia.org Connection: close HTTP/1.1 200 OK Date: Sun, 16 Jan 2022 20:41:56 GMT Content-Type: text/html; charset=UTF-8 Content-Length: 79568 ... <!DOCTYPE html> <html class="client-nojs" lang="fr" dir="ltr"> <head> <meta charset="UTF-8"/> <title>Hunga Tonga — Wikipédia</title> ...
Les trois lignes commençant par GET
ont été
tapées à la main par l'utilisateur.
La section 10 de notre RFC traite d'une fonction plus rare :
l'inclusion d'un message HTTP comme donnée d'un protocole (qui peut
être HTTP ou un autre). Un tel message est étiqueté avec le
type MIME application/http
.
Quelques mots sur la sécurité pour finir (section 11) : en raison de la complexité du protocole (qui est moins simple qu'il n'en a l'air !) et des mauvaises mises en œuvre qu'il faut quand même gérer car elles sont largement présentes sur le Web, deux programmes peuvent interpréter la même session HTTP différemment. Cela permet par exemple l'attaque de response splitting (cf. l'article de Klein « Divide and Conquer - HTTP Response Splitting, Web Cache Poisoning Attacks, and Related Topics »). Autre attaque possible, le request smuggling (cf. l'article de Linhart, Klein, Heled et Orrin, « HTTP Request Smuggling »).
Notre section 11 rappelle aussi que HTTP tout seul ne fournit pas de mécanisme pour assurer l'intégrité et la confidentialité des communications. Il dépend pour cela d'un protocole sous-jacent, en pratique TLS (HTTP+TLS étant appelé HTTPS).
L'annexe C décrit les changements de HTTP jusqu'à cette version
1.1. Ainsi, HTTP/1.0 a introduit la notion d'en-têtes, qui a permis,
entre autres, le virtual
hosting, grâce au champ
Host:
. HTTP/1.1 a notamment changé la
persistence par défaut des connexions (de non-persistente à
désormais persistente). Et notre RFC, par rapport à la précédente
norme de HTTP/1.1, le RFC 7230 ? Le plus gros
changement est éditorial, toutes les parties indépendantes du numéro
de version de HTTP ont été déplacées vers le RFC 9110, notre RFC ne gardant que ce qui est spécifique à
HTTP/1.1. S'il y a beaucoup de changements de détail, le protocole
n'est pas modifié, un client ou un serveur HTTP/1.1 reste
compatible.
Vous noterez que j'ai fait un cours HTTP au CNAM, dont les supports et la vidéo sont disponibles. HTTP/1 est un protocole simple, très simple, et trivial à programmer. Cela en fait un favori des enseignants en informatique car on peut écrire un client (ou même un serveur) HTTP très facilement, et il peut être utilisé contre des serveurs (ou des clients) réels, ce qui est motivant pour les étudiant·es.
Date de publication du RFC : Juin 2022
Auteur(s) du RFC : R. Fielding (Adobe), M. Nottingham (Fastly), J. Reschke (greenbytes)
Chemin des normes
Réalisé dans le cadre du groupe de travail IETF httpbis
Première rédaction de cet article le 7 juin 2022
Le protocole HTTP transporte énormément de données tous les
jours et consomme donc à lui seul une bonne partie des ressources de
l'Internet. D'où l'importance de
l'optimiser. Une des méthodes les plus efficaces pour cela est le
cache (terme anglais qui
fait très bizarre en français : pour mieux accéder à une ressource,
on la cache...). Ce RFC spécifie le modèle de cachage (de
mémorisation ?) de HTTP et comment les clients et les serveurs
peuvent l'utiliser. Il remplace le RFC 7234
avec peu de modifications importantes (la plus spectaculaire étant
l'abandon du champ Warning:
dans
l'en-tête).
Un cache Web est un espace de stockage local où on peut conserver la représentation d'une ressource qu'on a récupérée. Si la même ressource est à nouveau désirée, on pourra la récupérer depuis cette mémoire, le cache, plus proche et donc plus rapide que le serveur d'origine. Outre le temps d'accès, le cachage a l'avantage de diminuer la consommation de capacité réseau. Un cache peut être partagé entre plusieurs utilisateurs, augmentant ainsi les chances qu'une ressource désirée soit présente, ce qui améliore l'efficacité. (S'il n'a qu'un seul utilisateur, on parle de cache privé.) Comme tous les caches, les caches Web doivent gérer le stockage, l'accès et la place disponible, avec un mécanisme pour gérer le cas du cache plein. Comme tous les caches, les caches Web doivent aussi veiller à ne servir que de l'information fraîche. Cette fraîcheur peut être vérifiée de différentes façons, y compris par la validation (vérification auprès du serveur d'origine). Donc, même si l'information stockée dans le cache n'est pas garantie fraîche, on pourra quand même l'utiliser, si le serveur d'origine confirme qu'elle est toujours utilisable (dans ce cas, on aura quand même un accès réseau distant à faire, mais on évitera de transférer une ressource qui peut être de grande taille).
Le cache est optionnel pour HTTP, mais recommandé, et utiliser un cache devrait être le comportement par défaut, afin d'épargner le réseau, pour lequel HTTP représente une bonne part du trafic.
On peut garder en cache plusieurs sortes de réponses HTTP. Bien
sûr, le résultat d'une récupération après un
GET
(code 200, cf. RFC 9110) est cachable et représente l'utilisation la plus
courante. Mais on peut aussi conserver dans le cache le résultat de
certaines redirections, ou bien des résultats négatifs (un code 410,
indiquant que la ressource est définitivement partie), ou même le
résultat de méthodes autres que GET
(bien que
cela soit plus rare en pratique).
Ici, un exemple où une page a été stockée par un cache
Squid, et récupérée ensuite. L'argument de
GET
est l'URI complet, pas juste le chemin :
% curl -v http://www.w3.org/WAI/ ... > GET http://www.w3.org/WAI/ HTTP/1.1 > User-Agent: curl/7.26.0 > Host: www.w3.org ... < HTTP/1.0 200 OK < Last-Modified: Thu, 12 Jun 2014 16:39:11 GMT < ETag: "496a-4fba6335209c0" < Cache-Control: max-age=21600 < Expires: Sun, 15 Jun 2014 15:39:30 GMT < Content-Type: text/html; charset=utf-8 < Age: 118 < X-Cache: HIT from cache.example.org < X-Cache-Lookup: HIT from cache.example.org:3128 < Via: 1.1 cache.example.org:3128 (squid/2.7.STABLE9) ... <?xml version="1.0" encoding="utf-8"?> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
Le client HTTP curl suit la
variable d'environnement
http_proxy
et contacte donc le relais/cache
Squid en cache.example.org
en HTTP. À son tour,
celui-ci se connectera au serveur d'origine si nécessaire (ce ne
l'était pas ici, l'information a été trouvée dans le cache, comme
l'indique la mention HIT.)
Les données stockées dans le cache sont identifiées par une
clé (section 2 de notre RFC). Pour un cache
simple, qui ne gère que GET
, la clé principale
est l'URI
de la ressource convoitée. On verra plus loin que la clé peut en
fait être plus complexe que cela, en raison de certaines fonctions
du protocole HTTP, comme la négociation de contenu, qui
impose d'utiliser comme clé certains en-têtes de la requête. A
priori, un cache ne mémorise que les réponses positives (codes de
retour 200) mais certains mémorisent des réponses négatives comme le
404 (ressource non trouvée).
La section 3 du RFC normalise les cas où le cache a le droit de stocker une réponse, pour réutilisation ultérieure. Le RFC définit ces cas négativement : le cache ne doit pas stocker une réponse sauf si toutes ces conditions sont vraies :
GET
),Cache-Control:
, voir plus
loin),Expires:
).Un cache peut stocker des réponses partielles, résultat de requêtes avec intervalles (cf. RFC 9110, section 14), si lui-même comprend ces requêtes. Il peut concaténer des réponses partielles pour ensuite envoyer une ressource complète.
Une fois la ressource stockée, le cache ne doit pas la renvoyer sauf si (là encore, la norme - section 4 - est formulée de manière négative, ce qui est déroutant) toutes ces conditions sont vraies :
Vary:
correspondent (cela concerne surtout le cas où il y a négociation du
contenu),
L'exigence sur la clé secondaire (les en-têtes sur lesquels se fait
la négociation de contenu) est là pour s'assurer qu'on ne donnera
pas à un client une ressource variable et correspondant aux goûts
d'un autre client. Si le client dont la requête a déclenché la mise
en cache avait utilisé l'en-tête Accept-Language:
fr
indiquant qu'il voulait du français, et que le second
client du cache demande la même ressource, mais avec
Accept-Language: en
, il ne faut évidemment pas
donner la copie du premier client au second. Si la réponse avait
l'en-tête Vary: accept-language
indiquant
qu'elle dépend effectivement de la langue, le cache ne doit la
donner qu'aux clients ayant le même
Accept-Language:
.
Et la fraîcheur, elle se définit comment (section 4.2, une des
plus importantes du RFC) ? Le cas le plus simple est celui où le
serveur d'origine envoie un en-tête Expires
(ou
une directive max-age
), par exemple
Expires: Mon, 15 Jun 2015 09:33:06 GMT
. Dans ce
cas, la ressource gardée en cache est fraîche jusqu'à la date
indiquée. Attention : les formats de date de HTTP sont compliqués
et il faut être prudent en les analysant. Si le
Expires:
indique une date syntaxiquement
incorrecte, le cache doit supposer le pire et considérer que la
ressource a déjà expiré. En pratique, bien des serveurs HTTP ne
fournissent pas cet en-tête Expires:
et le
cache doit donc compter sur des heuristiques. La plus courante est
d'utiliser le champ Last-Modified:
et de
considérer que, plus le document est ancien, plus il restera frais
longtemps (section 4.2.2). (La FAQ
de Squid explique bien l'heuristique de ce
logiciel de cache.) Le RFC ne normalise pas une heuristique
particulière mais met des bornes à l'imagination des programmeurs :
ces heuristiques ne doivent être employées que s'il n'y a pas de
date d'expiration explicite, et la durée de fraîcheur doit être
inférieure à l'âge du document (et le RFC suggère qu'elle ne soit
que 10 % de cet âge).
Dans sa réponse, le cache inclut un en-tête
Age:
, qui peut donner au client une idée de la
durée depuis la dernière validation (auprès du serveur
d'origine). Par exemple, Age: 118
, dans le
premier exemple, indiquait que la page était dans le cache depuis
presque deux minutes.
Une réponse qui n'est pas fraîche peut quand même être renvoyée
au client dans certains cas, notamment lorsque le cache est
déconnecté du réseau et ne peut pas donc valider que sa copie est
toujours bonne. Le client peut empêcher l'envoi de ces réponses
rassises avec Cache-Control: must-revalidate
ou
no-cache
.
Comment se fait cette validation dont on a déjà parlé plusieurs
fois ? Lorsque le serveur a une copie d'une ressource, mais que sa
date maximum de fraîcheur est dépassée, il peut demander au serveur
d'origine. Cela se fait typiquement par une requête conditionnelle
(cf. RFC 9110, section 13.1) : si le serveur a une copie
plus récente, il l'enverra, autrement, il répondra par un 304,
indiquant que la copie du cache est bonne. La requête conditionnelle
peut se faire avec un If-Modified-Since:
(RFC 9110, section 8.8.2) en
utilisant comme date celle qui avait été donnée dans le
Last-Modified:
. Ou bien elle peut se faire avec
l'entity tag (RFC 9110,
section 8.8.3) et un
If-None-Match:
:
% telnet cache 3128 ... GET http://www.w3.org/WAI/ HTTP/1.1 Host: www.w3.org If-None-Match: "496a-4fba6335209c0" HTTP/1.0 304 Not Modified Date: Sun, 15 Jun 2014 09:39:30 GMT Content-Type: text/html; charset=utf-8 Expires: Sun, 15 Jun 2014 15:39:30 GMT Last-Modified: Thu, 12 Jun 2014 16:39:11 GMT ETag: "496a-4fba6335209c0" Age: 418 X-Cache: HIT from cache.example.org X-Cache-Lookup: HIT from cache.example.org:3128 Via: 1.0 cache.example.org:3128 (squid/2.7.STABLE9) Connection: close
Le cache peut aussi utiliser la méthode HEAD
pour tester sa copie locale auprès du serveur d'origine, par exemple
pour invalider la copie locale, sans pour autant transférer la
ressource. Et s'il voit passer un URL connu avec des méthodes qui ont
de fortes chances de changer la ressource, comme
PUT
ou POST
, le cache doit
invalider la ressource stockée.
La section 5 liste tous les en-têtes des requêtes et des réponses
qui sont utilisés pour le bon fonctionnement des caches, comme
Age:
(en secondes), Expires:
, etc. Ils
sont enregistrés à
l'IANA, dans le registre des en-têtes (désormais séparé du registre
utilisé pour les en-têtes du courrier électronique).
Parmi ces en-têtes, Cache-Control:
permet de
spécifier des directives concernant le cache. Un client d'un cache
peut spécifier l'âge maximum qu'il est prêt à accepter (directive
max-age
), une fraîcheur minimum (directive
min-fresh
), que la ressource ne doit pas être
stockée dans le cache (directive no-store
, qui
est là pour des raisons de vie privée mais,
bien sûr, est loin de suffire pour une véritable confidentialité),
ou bien qu'elle peut être stockée mais ne doit pas être servie à un
client sans revalidation (directive no-cache
),
etc. Il y a aussi l'opposé de no-cache
,
only-if-cached
, qui indique que le client ne
veut la ressource que si elle est stockée dans le cache. (Attention,
dans un cache partagé, cela peut permettre à un client de voir ce que
les autres clients ont demandé, ce qu'on nomme le cache
snooping.) L'ensemble des directives possibles sont
stockées dans un
registre IANA. Ainsi, le RFC 8246 avait
ajouté une valeur possible à Cache-Control:
, pour
indiquer l'immuabilité d'une ressource.
L'en-tête
Cache-Control:
peut aussi être utilisé dans des
réponses. Un serveur peut lui aussi indiquer
no-cache
, typiquement parce que ce qu'il envoie
change fréquemment et doit donc être revalidé à chaque fois,
private
s'il veut insister sur le fait que la
réponse n'était destinée qu'à un seul utilisateur et ne doit donc
pas être transmise à d'autres (le RFC insiste que c'est une
protection vraiment minimale de la vie privée), etc.
À noter qu'un cache HTTP n'est pas forcément un serveur spécialisé. Tous les navigateurs Web ont des fonctions d'historique (comme le bouton Back). Est-ce que celles-ci nécessitent des précautions analogues à celles des caches, pour éviter que le navigateur ne serve des données dépassées ? Pas forcément, dit le RFC, qui autorise un navigateur à afficher une page peut-être plus à jour lorsqu'on utilise le retour en arrière dans l'historique (mais lisez la section 6 du RFC : cette autorisation vient avec des limites).
La section 7 détaille les problèmes de sécurité qui peuvent affecter les caches. Un cache, par exemple, peut permettre d'accéder à une information qui n'est plus présente dans le serveur d'origine, et donc de rendre plus difficile la suppression d'une ressource. Un cache doit donc être géré en pensant à ces risques. Plus grave, l'empoisonnement de cache : si un malveillant parvient à stocker une fausse représentation d'une ressource dans un cache (avec une longue durée de fraîcheur), tous les utilisateurs du cache recevront cette information au lieu de la bonne.
Un cache peut aussi avoir des conséquences pour la vie privée : en demandant une ressource à un cache partagé, un utilisateur peut savoir, à partir du temps de chargement et d'autres informations envoyées par le cache, si un autre utilisateur avait déjà consulté cette page. Et si un cache est privé (restreint à un·e seule·e utilisateurice), les données qu'il a stocké permettent d'avoir un panorama complet des activités Web de l'utilisateur.
L'annexe B liste les différences depuis le texte précédent, celui du RFC 7234 :
must-understand
, qui
permet au serveur d'indiquer que la ressource ne doit être
mémorisée que si le cache connait et comprend le code de retour indiqué,Warning
dans les réponses est abandonné, car il était peu utilisé et
souvent redondant avec l'information déjà présente dans la réponse.Question mise en œuvre, notez qu'il existe un projet de tests des caches pour vérifier leur conformité.
Date de publication du RFC : Juin 2022
Auteur(s) du RFC : R. Fielding (Adobe), M. Nottingham (Fastly), J. Reschke (greenbytes)
Chemin des normes
Réalisé dans le cadre du groupe de travail IETF httpbis
Première rédaction de cet article le 7 juin 2022
Que voilà une épaisse lecture (252 pages). Mais c'est parce qu'il s'agit de réécrire complètement la totalité des normes de HTTP. Pas le protocole lui-même, je vous rassure, HTTP ne change pas. Mais la rédaction de ses normes est profondément réorganisée, avec un RFC (notre RFC 9110) qui décrit une vision de haut niveau de HTTP, puis un autre RFC par version majeure de HTTP, décrivant les détails de syntaxe de chaque version.
Par exemple, HTTP/1 (RFC 9112) a un
encodage en texte alors que HTTP/2 (RFC 9113)
a un encodage binaire. Pourtant, tous les deux suivent les mêmes
principes, décrits dans ce RFC 9110 (méthodes comme
GET
, en-têtes de la requête et de la réponse,
codes de retour à trois chiffres…) mais avec des encodages
différents, chacun dans son propre RFC. Notre RFC 9110 est donc la vision de haut niveau de HTTP, commune à
toutes les versions, et d'autres RFC vous donneront les
détails. Inutile de dire que cette réorganisation a été un gros
travail, commencé en 2018.
Les trois versions de HTTP actuellement en large usage (1.1, 2 et 3) reposent toutes sur des concepts communs. Par exemple, les codes d'erreur (comme le fameux 404) sont les mêmes. Il n'est pas prévu, même à moyen terme, que les versions les plus anciennes soient abandonnées (HTTP/1.1 reste d'un usage très courant, et souvent pour de bonnes raisons). D'où cette réorganisations des normes HTTP, avec notre RFC 9110 qui décrit ce qui est commun aux trois versions, d'autres RFC communs aux trois versions, et un RFC par version :
Vous connaissez certainement déjà HTTP, mais notre RFC ne présuppose pas de connaissances préalables et explique tout en partant du début, ce que je fais donc également ici. Donc, HTTP est un protocole applicatif, client/serveur, sans état, qui permet l'accès et la modification de ressources distantes (une ressource pouvant être du texte, une image, et étant générée dynamiquement ou pas, le protocole est indépendant du format de la ressource ou de son mode de création, le RFC insiste bien sur ce point). Le client se connecte, envoie une requête, le serveur répond. HTTP ne fonctionne pas forcément de bout en bout, il peut y avoir des relais sur le trajet, et leur présence contribue beaucoup à certaines complexités de la norme.
S'il fallait résumer HTTP rapidement, on pourrait dire qu'il décrit un moyen d'interagir avec une ressource distante (la ressource peut être un fichier, un programme…). Il repose sur l'échange de messages, avec une requête du client vers le serveur et une réponse en sens inverse. Outre la méthode qui indique ce que le client veut faire avec la ressource, HTTP permet de transporter des métadonnées.
La section 3 du RFC décrit les concepts centraux de ce protocole,
comme celui de ressource présenté plus haut. (Qui est parfois appelé
« page » ou « fichier » mais ces termes ne sont pas assez
génériques. Une ressource n'est pas forcément une page HTML !) HTTP identifie
les ressources par des URI. Une représentation
est la forme concrète d'une ressource, les bits qu'on reçoit ou
envoie. (Du fait de la négociation de contenu et d'autres facteurs,
récupérer une ressource en utilisant le même URI ne donnera pas
forcément les mêmes bits, même s'ils sont censés être sémantiquement
équivalents.) La ressource n'est pas non plus forcément un fichier, pensez à une ressource qui indique l'heure qu'il est,
ou le temps qu'il fait, par exemple. Ou à l'URI
https://www.bortzmeyer.org/apps/random
qui vous
renvoie une page choisie aléatoirement de ce blog. HTTP agit
sur une ressource (dont le type n'est pas forcément connu) via une
méthode qui va peut-être retourner une représentation de cette
ressource. C'est ce qu'on nomme le principe REST et de
nombreuses API se réclament de ce principe.
HTTP est un protocole client/serveur. Le serveur attend le client. (Le client est parfois appelé user agent.) Entre les deux, HTTP utilisera un protocole de transport fiable, comme TCP (HTTP/1 et 2) ou QUIC (HTTP/3). Par défaut, HTTP est sans état : une fois une requête servie, le serveur oublie tout. Les clients sont très variés : il y a bien sûr les navigateurs Web, mais aussi les robots, des outils en ligne de commande comme wget, des objets connectés, des programmes vite faits en utilisant une des zillions de bibliothèques qui permettent de développer rapidement un client HTTP, des applications sur un ordiphone, etc. Notamment, il n'y a pas forcément un utilisateur humain derrière le client HTTP. (Pensez à cela si vous mettez des éléments d'interfaces qui demandent qu'un humain y réponde ; le client ne peut pas forcément faire de l'interactivité.)
Les messages envoyés par le client au serveur sont des requêtes et ceux envoyés par le serveur des réponses.
La section 2 du RFC explique ce qu'on attend d'un client ou d'un serveur HTTP conforme. Un point important et souvent ignoré est que HTTP ne donne pas de limites quantitatives à beaucoup de ses éléments. Par exemple, la longueur maximale de la première ligne de la requête (celle qui contient le chemin de la ressource) n'est pas spécifiée, car il serait trop difficile de définir une limite qui convienne à tous les cas, HTTP étant utilisé dans des contextes très différents. Comme les programmes ont forcément des limites, cela veut dire qu'on ne peut pas toujours compter sur une limite bien connue.
Une mise en œuvre conforme pour HTTP doit notamment bien gérer la notion de version de HTTP. Cette version s'exprime par deux chiffres séparés par un point, le premier chiffre étant la version majeure (1, 2 ou 3) et le second la mineure (il est optionnel, valant 0 par défaut, donc HTTP/2 veut dire la même chose que HTTP/2.0). Normalement, au sein d'une même version majeure, on doit pouvoir interopérer sans trop de problème alors qu'entre deux versions majeures, il peut y avoir incompatibilité totale. La sémantique est forcément la même (c'est du HTTP, après tout) mais la syntaxe peut être radicalement différente (pensez à l'encodage texte de HTTP/1 vs. le binaire de HTTP/2 et 3). Donc, être conforme à HTTP/1.1 veut dire lire ce RFC 9110 mais aussi le RFC 9112, qui décrit la syntaxe spécifique de HTTP/1.1.
Comme, dans la nature, des programmes ne sont pas corrects, le
RFC autorise du bout des lèvres à utiliser le contenu des champs
User-Agent:
ou Server:
de
l'en-tête pour s'ajuster à des bogues connues (mais, normalement, ce
doit être uniquement pour contourner des bogues, pas pour servir un
contenu différent).
De même qu'un client HTTP n'est pas forcément un navigateur Web, un serveur HTTP n'est pas forcément une grosse machine dans un centre de données chez un GAFA. Le serveur HTTP peut parfaitement être une imprimante, un petit objet connecté, une caméra de vidéosurveillance, un Raspberry Pi dans son coin… Le RFC parle de « serveur d'origine » pour le serveur qui va faire autorité pour les données servies. Pourquoi ce concept ? Parce que HTTP permet également l'insertion d'un certain nombre d'intermédiaires, les relais (proxy ou gateway en anglais), entre le client et le serveur d'origine. Leurs buts sont très variés. Par exemple, un relais (proxy, pour le RFC) dans le réseau local où se trouve le client HTTP peut servir à mémoriser les ressources Web les plus souvent demandées, pour améliorer les performances. Un relais (gateway ou reverse proxy, pour le RFC) qui est au contraire proche du serveur d'origine peut servir à répartir la charge entre diverses instances. Revenons à la mémorisation des ressources (caching en anglais). La mémoire (cache en anglais) est un stockage de ressources Web déjà visitées, prêtes à être envoyées aux clients locaux pour diminuer la latence. La mémorisation est un sujet suffisamment fréquent et important pour avoir son propre RFC, le RFC 9111.
On a vu que HTTP servait à agir sur des ressources distantes. Des
ressources, il y en a beaucoup. Comment les identifier ? Le Web va
utiliser des URI comme
identificateurs. Ces URI sont normalisés dans
le RFC 3986, mais qui ne spécifie qu'une
syntaxe générique. Chaque plan d'URI (la chaine
de caractères avant le deux-points, souvent
appelée à tort protocole) doit spécifier un certain nombre de
détails spécifique à ce plan. Pour les plans
http
et https
, cette
spécification est la section 4 de notre RFC. (Tous les plans sont
dans un
registre IANA.) Un URI de plan http
ou
https
indique forcément une autorité (un
identificateur du serveur d'origine, en pratique un nom de machine)
et un chemin (identificateur de la ressource à l'intérieur d'un
autorité. Ainsi, dans
https://www.afnic.fr/observatoire-ressources/consultations-publiques/
,
le plan est https
, l'autorité
www.afnic.fr
et le chemin
/observatoire-ressources/consultations-publiques/
. Le
port par défaut est 80 pour
http
et 443 pour
https
(tous les deux sont enregistrés
à l'IANA). La différence entre les deux plans est que
https
implique l'utilisation du protocole de
sécurité TLS
(RFC 8446), pour assurer notamment la
confidentialité des requêtes.
En théorie, un URI de plan http
et un autre
identique, sauf pour l'utilisation de https
,
sont complètement distincts. Ils ne représentent pas la même origine
(l'origine est un triplet {plan, machine, port}) et les deux
ressources peuvent être complètement différentes. Mais notre RFC
note que certaines normes violent ce principe, notamment celle sur
les cookies (RFC 6265),
avec parfois des conséquences fâcheuses pour la sécurité.
En HTTPS, puisque ce protocole s'appuie sur TLS, le serveur présente un certificat, que le client doit vérifier (section 4.3.4), en suivant les règles du RFC 6125.
Pour expliquer plusieurs des propriétés de HTTP, je vais beaucoup
utiliser le logiciel curl, un
client HTTP en ligne de commande, dont
l'option -v
permet d'afficher tout le dialogue
HTTP. Si vous voulez faire des essais vous aussi, interrompez
momentanément votre lecture pour installer curl. […] C'est fait ? On
peut reprendre ?
% curl -v http://www.hambers.mairie53.fr/ ... > GET / HTTP/1.1 > Host: www.hambers.mairie53.fr > User-Agent: curl/7.68.0 > Accept: */* > < HTTP/1.1 200 OK < Date: Wed, 02 Mar 2022 16:25:02 GMT < Server: Apache ... < Content-Length: 61516 < Content-Type: text/html; charset=UTF-8 < <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> ...
Nous avons vu que la requête et la réponse HTTP contenaient des métadonnées dans un en-tête composé de champs. (Il peut aussi y avoir un pied, une sorte de post-scriptum, mais c'est peu utilisé.) Chaque champ a un nom et une valeur. La section 5 du RFC détaille cet important concept. Les noms de champs sont insensibles à la casse. Ils sont enregistrés dans un registre IANA spécifique à HTTP (ils étaient avant dans le même registre que les champs du courrier électronique). Un client, un serveur ou un relais HTTP doivent ignorer les champs qu'ils ne connaissent pas, ce qui permet d'introduire de nouveaux champs sans tout casser. Le même champ peut apparaitre plusieurs fois. Comme pour d'autres éléments du protocole HTTP, la norme ne fixe pas de limite de taille. La valeur d'un champ peut donc être très grande.
La valeur d'un champ obéit à des règles qui dépendent du champ. Les caractères doivent être de l'ASCII, une limite très pénible de HTTP. Si on veut utiliser Unicode (ou un autre jeu de caractères), il faut l'encoder comme indiqué dans le RFC 8187. Le RFC rappelle qu'autrefois Latin-1 était autorisé (avec l'encodage du RFC 2047 pour les autres jeux) mais cela ne devrait normalement plus être le cas (mais ça se rencontre parfois encore). Si une valeur comprend plusieurs termes, ils doivent normalement être séparés par des virgules (et on met entre guillemets les valeurs qui comprennent des virgules). Les valeurs peuvent inclure des paramètres, écrits sous la forme nom=valeur. Certaines valeurs ont leur propre structure (RFC 9651). Ainsi, plusieurs champs peuvent inclure une estampille temporelle. La syntaxe pour celles-ci n'est hélas pas celle du RFC 3339 mais celle de l'IMF (RFC 5322), plus complexe et plus ambigüe. (Sans compter, vu l'âge de HTTP, qu'on rencontre parfois de vieux formats comme celui du RFC 850.) Voici des exemples de champs vus avec curl :
% curl -v http://confiance-numerique.clermont-universite.fr/ ... > GET / HTTP/1.1 > Host: confiance-numerique.clermont-universite.fr > User-Agent: curl/7.68.0 > Accept: */* < HTTP/1.1 200 OK < Date: Fri, 28 Jan 2022 17:21:38 GMT < Server: Apache/2.4.6 (CentOS) < Last-Modified: Tue, 01 Sep 2020 13:29:41 GMT < ETag: "1fbc1-5ae4082ec730d" < Accept-Ranges: bytes < Content-Length: 129985 < Content-Type: text/html; charset=UTF-8 < <!DOCTYPE HTML SYSTEM> <html> <head> <title>Séminaire Confiance Numérique</title>
Le client HTTP (curl) a envoyé trois champs,
Host:
(le serveur qu'on veut contacter),
User-Agent:
(une chaine de caractères décrivant
le client) et Accept:
(les formats acceptés,
ici tous). Le serveur a répondu avec divers champs comme
Server:
(l'équivalent du
User-Agent:
) et
Content-Type:
(le format utilisé, ici
HTML). Et voici ce qu'envoie le navigateur Firefox :
Host: localhost:8080 User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:96.0) Gecko/20100101 Firefox/96.0 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8 Accept-Language: en-US,en;q=0.5 Accept-Encoding: gzip, deflate Connection: keep-alive Upgrade-Insecure-Requests: 1 Sec-Fetch-Dest: document Sec-Fetch-Mode: navigate Sec-Fetch-Site: none Sec-Fetch-User: ?1
On notera surtout un Accept:
plus complexe
(curl accepte tout car il ne s'occupe pas de l'affichage).
Maintenant, les messages (requêtes et réponses). La façon exacte dont ils sont transmis dépend de la version de HTTP. Par exemple, la version 1 les encode en texte alors que les versions 2 et 3 préfèrent le binaire. Autre exemple, la version 3 ne prévoit pas de mécanisme de début et de fin d'un message car chaque ruisseau QUIC ne porte qu'un seul message, un peu comme les versions 0 de HTTP, avec le ruisseau QUIC au lieu de la connexion TCP (notez qu'avec TCP sans TLS, le client peut ne pas savoir s'il a bien reçu toutes les données). La section 6 de notre RFC ne donne donc qu'une description abstraite. Un message comprend donc une information de contrôle (la première ligne, dans le cas de HTTP/1, un « pseudo en-tête » avec des noms de champs commençant par un deux-points pour les autres versions), un en-tête, un corps (optionnel) et un pied (également optionnel). L'information de contrôle donne plusieurs informations nécessaires pour la suite, comme la version de HTTP utilisée. Le contenu (le corps) est juste une suite d'octets, que HTTP transporte sans l'interpréter (ce n'est pas forcément de l'HTML). Dans la réponse, l'information de contrôle comprend notamment un code numérique de trois chiffres, qui indique comment la requête a été traitée (ou pas).
Beaucoup moins connu que l'en-tête, un message peut aussi comporter un pied, également composé de champs « nom: valeur ». Il est nécessaire de les utiliser dans les cas où l'information est générée dynamiquement et que certaines choses ne peuvent être déterminées qu'après coup (une signature numérique, par exemple).
Dans le cas le plus simple, le client HTTP parle directement au
serveur d'origine et il n'y a pas de complications de routage du
message. Le serveur traite le message reçu, point. Mais HTTP permet
d'autres cas, par exemple avec un relais qui
reçoit le message avant de le transmettre au « vrai » serveur
(section 7 du RFC). Ainsi, dans une requête, l'information de
contrôle n'est pas forcément un simple chemin
(/publications/cahiers-soutenabilites
) mais
peut être un URL complet
(https://www.strategie.gouv.fr/publications/cahiers-soutenabilites
). C'est
ce que fait le client HTTP s'il est configuré pour utiliser un
relais, par exemple pour mémoriser les réponses des requêtes (RFC 9111), ou bien parce que l'accès direct aux
ports 80 et 443 est
bloqué et qu'on est obligé d'utiliser un relais. Dans le cas où la
ressource demandées est identifiée par un URL complet, le relais
doit alors se transformer en client HTTP et faire une requête vers
le serveur d'origine (ou bien vers un autre relais…).
La section 8 de notre RFC s'attaque à une notion cruciale en
HTTP, celle de représentation. La
représentation d'une ressource est la suite d'octets qu'on obtient
en réponse à une requête HTTP (« représentation » est donc plus
concret que « ressource »). Une même ressource peut avoir plusieurs
représentations, par exemple selon les métadonnées que le client a
indiqué dans sa requête. Le type de la représentation est indiqué par le champ
Content-Type:
de l'en-tête (et aussi par
Content-Encoding:
). Sa valeur est un
type MIME (RFC 2046). Voici par exemple le type de la page que vous êtes
en train de lire :
Content-Type: text/html; charset=UTF-8
(Notez que le paramètre charset
est mal nommé,
c'est en fait un encodage, pas un
jeu de caractères. L'erreur vient du fait que
dans les vieilles normes comme ISO-8859-1, les
deux concepts étaient confondus.)
Normalement, du fait de ce Content-Type:
, le
client HTTP n'a pas à deviner le type de la représentation, il se
fie à ce que le serveur raconte. Ceci dit, certains clients ont la
mauvaise idée de chercher à deviner le
type. Cette divination est toujours incertaine (plusieurs
types de données peuvent se ressembler) et ouvre même la possibilité
de failles de sécurité.
Un autre champ, Content-Language:
, indique
la langue de la représentation récupérée. Sa
valeur est une étiquette de langue, au sens
du RFC 5646. Si le texte est multilingue, ce
champ peut prendre plusieurs valeurs. Le RFC illustre cela avec le
traité de Waitangi, qui est en
maori et en anglais :
Content-Language: mi, en
Attention, la seule présence de différentes langues ne signifie pas qu'il faut mettre plusieurs étiquettes de langue. Un cours d'introduction à l'arabe écrit en français, pour un public francophone, sera :
Content-Language: fr
Les étiquettes de langue peuvent être plus complexes que l'indication de la seule langue, mais il me semble que c'est rarement utilisé sur le Web.
La taille de la représentation, elle, est exprimée avec
Content-Length:
, un champ très pratique pour le
client HTTP qui sait ainsi combien d'octets il va devoir lire (avant
HTTP/1, c'était facile, on lisait jusqu'à la fin de la connexion
TCP ;
mais ça ne marche plus depuis qu'il y a des connexions persistentes
et, de toute façon, en l'absence de TLS, cela ne permettait pas de
détecter des coupures prématurées). Évidemment, le client doit
rester paranoïaque et supposer que l'information puisse être
fausse. curl (avec -v
) avertit ainsi, si la
taille indiquée est trop faible :
* Excess found in a read: excess = 1, size = 12, maxdownload = 12, bytecount = 0
Si la taille indiquée est trop grande, curl attend pour essayer de lire davantage sur le connexion qui reste ouverte. Autre raison d'être paranoïaque, la taille indiquée peut être énorme, menant par exemple un client imprudent, qui allouerait la mémoire demandée à épuiser celle-ci. Sans compter l'éventualité d'un débordement d'entier si la taille ne peut pas être représentée dans les entiers utilisés par le client HTTP.
Ensuite vient un autre point pas forcément très connu : les
validateurs. HTTP permet d'indiquer des pré-conditions à la
récupération d'une ressource, pour épargner le réseau. Un client
HTTP peut ainsi demander « donne-moi cette ressource, si elle n'a
pas changé ». Pour cela, HTTP repose sur ces validateurs, qui sont
des métadonnées qui accompagnent la requête (avec des champs qui
expriment la requête conditionnelle, comme
If-Modified-Since:
, et qui sont détaillés en
section 13) et que le serveur vérifiera. Il existe deux sortes de
validateurs, les forts et les faibles. Les faibles sont faciles à
générer mais ne garantissent pas une comparaison réussie, les forts
sont plus difficiles à faire mais sont plus fiables. Par exemple, un
condensat du contenu est fort. Il changera
forcément (sauf malchance inouïe) dès qu'on changera un seul bit du
contenu. Si le contenu est géré par un VCS, celui-ci fournit également des
validateurs forts : l'identificateur de
commit. Au contraire, une
estampille temporelle est un validateur faible. Si sa résolution est
d'une seconde, deux modifications dans la même seconde ne seront pas
détectées et le serveur croira à tort que le contenu n'a pas
changé.
Pour connaitre la valeur actuelle d'un futur validateur, le
client HTTP dispose de champs comme
Last-Modified:
(une estampille temporelle) ou
ETag:
(Entity Tag,
l'étiquette de la ressource, une valeur opaque, qui peut s'utiliser
avec des requêtes conditionnelles comme
If-None-Match:
). Voici un exemple :
Last-Modified: Mon, 07 Feb 2022 12:20:20 GMT ETag: "5278-5d76c9fc1c9f4"
(Le serveur utilisé était un Apache. Par
défaut, Apache génère des étiquettes qui sont un
condensat de divers attributs du fichier
comme l'inœud, la taille et la date de
modification. Apache permet de configurer
cet algorithme. Rappelez-vous que l'étiquette est opaque, le
serveur peut donc la générer comme il veut, il doit juste s'assurer
qu'elle change à chaque modification de la ressource. Le serveur peut par
exemple utiliser un SHA-1 du contenu de la ressource.)
A priori, l'étiquette de la ressource est un validateur fort,
autrement, le serveur doit la préfixer par W/
(W pour Weak).
Passons maintenant aux méthodes (section 9
du RFC). Il y a très longtemps, HTTP n'avait qu'une seule méthode
pour agir sur les ressources, la méthode
GET
. Désormais, il y a nettement plus de
méthodes, chacune agissant sur la ressource indiquée d'une manière
différente et ayant donc une sémantique différente. Par exemple,
GET
va récupérer une représentation de la
ressource, alors que PUT
va au contraire écrire
le contenu envoyé, remplaçant celui de la ressource et que
DELETE
va… détruire la ressource. La liste
complète des méthodes figure dans un
registre IANA.
Certaines des méthodes sont dites sûres car elles ne modifient
pas la ressource et ne casseront donc rien. Bien sûr, une méthode
sûre peut avoir des effets de bord (comme d'écrire une ligne dans le
journal du serveur, mais ce n'est pas la
faute du client). GET
, HEAD
et les moins connues OPTIONS
et
TRACE
sont sûres. Du fait de cette garantie de
sûreté, un programme qui ne fait que des requêtes sûres a moins
d'inquiétudes à avoir, notamment s'il agit sur la base
d'informations qu'il ne contrôle pas. Ainsi, le
ramasseur d'un moteur de
recherche ne fait a priori que des requêtes sûres, pour
éviter qu'une page Web malveillante ne l'entraine à effectuer des
opérations qui peuvent changer le contenu des sites Web visités.
Une autre propriété importante d'une méthode est d'être
idempotente ou pas. Une méthode idempotente a
le même effet qu'on l'exécute une ou N fois. Les méthodes sûres sont
toutes idempotentes mais l'inverse n'est pas vrai :
PUT
et DELETE
sont
idempotentes (qu'on détruise une ressource une ou N fois donnera le
même résultat : la ressource est supprimée) mais pas
sûres. L'intérêt de cette propriété d'idempotence est qu'elles
peuvent être répétées sans risque, par exemple si le réseau a eu un
problème et qu'on n'est pas certain que la requête ait été
exécutée. Les méthodes non-idempotentes ne doivent pas, par contre,
être répétées aveuglément.
La méthode la plus connue et sans doute la plus utilisée,
GET
, permet de récupérer une représentation
d'une ressource. La syntaxe avec laquelle s'exprime le chemin de
cette ressource fait penser à l'arborescence d'un système
de fichiers et c'est en effet souvent ainsi que c'est
mis en œuvre dans les serveurs (par exemple dans Apache, où le chemin, mettons
/foo/bar
, est ajouté à la fin de la variable de
configuration DocumentRoot
, avant d'être
récupéré sur le système de fichiers : si
DocumentRoot
vaut
/var/www
, le fichier demandé sera
/var/www/foo/bar
). Mais ce n'est pas une
obligation de HTTP, qui ne normalise que le protocole entre le client
et le serveur, pas la façon dont le serveur obtient les
ressources.
La méthode HEAD
fait la même chose que
GET
mais sans renvoyer la représentation de la
ressource.
POST
est plus compliquée. Contrairement à
GET
, la requête contient des données qui vont
être envoyés au serveur. Celui-ci va les
traiter. POST
est souvent utilisé pour
soumettre le contenu d'un formulaire Web, par
exemple pour envoyer un texte qui sera le contenu d'un commentaire
lors d'une discussion sur un forum Web. Avec
GET
, POST
est probablement
la méthode la plus souvent vue sur le Web.
PUT
, lui, est également accompagné de
données qui vont être écrites à la place de la ressource
désignée. On peut donc mettre en œuvre un serveur de fichiers
distant avec des PUT
et des
GET
. On peut y ajouter
DELETE
pour supprimer les ressources devenues
inutiles.
La méthode CONNECT
est plus complexe. Elle
n'agit pas sur une ressource mais permet d'établir une connexion
avec un service distant. Sa principale utilité est de permettre
d'établir un tunnel au-dessus de
HTTP. Ainsi :
CONNECT server.example.com:80 HTTP/1.1 Host: server.example.com
va établir une connexion avec
server.example.com
et les octets envoyés par la
suite sur cette connexion HTTP seront relayés aveuglément vers
server.example.com
.
Quant à la méthode OPTIONS
, elle permet
d'obtenir des informations sur les options gérées par le
serveur. curl permet d'indiquer une méthode avec son option
--request
(ou -X
) :
% curl -v --request OPTIONS https://www.bortzmeyer.org/ ... > OPTIONS / HTTP/2 > Host: www.bortzmeyer.org > user-agent: curl/7.68.0 > accept: */* > ... < HTTP/2 200 < permissions-policy: interest-cohort=() < allow: POST,OPTIONS,HEAD,GET ...
La section 10 du RFC est ensuite une longue section qui décrit le contexte des messages HTTP, c'est-à-dire les métadonnées qui accompagnent requêtes et réponses. Je ne vais évidemment pas en reprendre toute la liste ici. Juste quelques exemples de champs intéressants :
From:
permet d'indiquer l'adresse de
courrier du
responsable du logiciel. Il est surtout utilisé par les
bots (par exemple ceux qui ramassent les pages pour le
compte d'un moteur de recherche) pour
indiquer qui contacter si le bot se comporte mal, par exemple en
faisant trop de requêtes. Comme le rappelle le RFC, un navigateur
ordinaire ne doit évidemment pas transmettre une telle donnée
personnelle à tous les sites Web visités !Referer:
(oui, avec une faute d'orthographe) sert à indiquer
l'URL d'où vient le client HTTP. Le Web étant fondé sur l'idée
d'hypertexte, l'utilisateur est peut-être
venu ici en suivant un lien, et il peut ainsi indiquer où il a
trouvé ce lien, ce qui peut permettre au webmestre de voir d'où
viennent ses visiteurs. Lui aussi pose des problèmes de vie privée, et j'ai toujours été
surpris que le Tor Browser l'envoie.User-Agent:
indique le type du client
HTTP. À part s'amuser en regardant le genre de visiteurs qu'on a,
il n'a pas de vraie utilité, le Web reposant sur des normes, et
précisant une structure et pas une présentation, il ne
devrait pas y avoir besoin de changer une ressource en fonction du
logiciel du visiteur. Mais c'est quand même ce que font certains
serveurs HTTP, poussant les clients à mentir pour obtenir un
certain résultat, ce qui donne des champs
User-Agent:
ridicules comme (vu sur ce blog)
Mozilla/5.0 (Windows NT 10.0; Win64; x64)
AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.75
Safari/537.36
(probablement le navigateur
Safari indiquant autant de logiciels que possible ; le RFC dit qu'il ne faut pas le
faire mais c'est courant, pour tenir compte de serveurs qui
interprètent ce champ). Là encore, on a une métadonnée
qui contribue puissamment à la fuite d'information si commune sur
le Web (votre client HTTP est certainement trop bavard). Le
User-Agent:
est très utile pour le
fingerprinting, l'identification d'un visiteur
particulier, comme le démontre le Panopticlick.Server:
est l'équivalent de
User-Agent:
mais pour le serveur.Jusqu'à présent, on a supposé que les ressources servies étaient
accessibles à tous et toutes. Mais en pratique, on souhaite parfois
servir du contenu à accès restreint et on veut donc n'autoriser que certains visiteurs. Il
faut donc disposer de mécanismes
d'authentification, exposés dans la section
11 du RFC. HTTP n'a pas un mécanisme unique
d'authentification. Chaque mécanisme est identifié par un nom (et
les possibilités sont dans un
registre IANA). Le serveur indique le mécanisme à utiliser
dans un champ WWW-Authenticate:
de sa première
réponse. Par exemple, basic
, normalisé dans le
RFC 7617, est un mécanisme simple de
mot de passe, alors que
digest
(normalisé dans le RFC 7616) permet de s'authentifier via un
défi/réponse. Le mécanisme est spécifique à
un royaume, une information donnée par le serveur pour le cas où le
même serveur gérerait des types d'authentification différents selon
la ressource.
Voici un exemple d'authentification (avec le service
d'administration d'un serveur
dnsdist, celui utilisé pour mon résolveur
public) où l'identificateur est admin
et
le mot de passe 2e12
:
% curl -v --user admin:2e12 https://doh.bortzmeyer.fr:8080/ > GET / HTTP/1.1 > Host: doh.bortzmeyer.fr:8080 > Authorization: Basic YWRtbW...OTcyM= > User-Agent: curl/7.68.0 > Accept: */* > ... < HTTP/1.1 200 OK ...
La représentation renvoyée peut dépendre du client, c'est ce
qu'on nomme la négociation de contenu (section 12 du RFC). La
méthode officielle est que le client annonce avec le champ
Accept:
les types
de données qu'il accepte, et le serveur lui envoie de
préférence ce qu'il a demandé. (C'est utilisé sur ce blog pour les
images. En pratique, ça ne se passe pas toujours bien.)
La demande du client n'est pas strictement binaire « je veux du
format WebP ». Elle peut s'exprimer de
manière plus nuancée, via le système de qualité. Ainsi :
Accept: text/plain; q=0.5, text/html
signifie que le client comprend le texte brut et l'HTML mais préfère ce dernier (le poids par défaut est 1, supérieur, donc, au 0,5 du texte brut).
La négociation de contenu ne s'applique pas qu'au format des
représentations, elle peut aussi s'appliquer à la
langue, avec le champ
Accept-Language:
. Ainsi, en disant :
Accept-Language: da, en;q=0.8
veut dire « je préfère le danois (poids de 1 par défaut), mais j'accepte l'anglais ». En pratique, ce n'est pas très utile sur le Web car cela ne permet pas d'indiquer la qualité de la traduction. Si on indique qu'on préfère le français, mais qu'on peut lire l'anglais, en visitant des sites Web d'organisations internationales, on se retrouve avec un texte français mal traduit, alors qu'on aurait préféré la version originale. En outre, comme beaucoup de champs de l'en-tête de la requête, il contribue à identifier le client (fingerprinting). C'est d'autant plus gênant que l'indication des langues préférées peut vous signaler à l'attention de gens peu sympathiques, si ces langues sont celles d'une minorité opprimée.
Comme la représentation envoyée peut dépendre de ces demandes du
client, le serveur doit indiquer dans sa réponse s'il a effectivement
tenu compte de la négociation de contenu. C'est notamment important
pour les relais Web qui mémorisent le contenu des réponses (RFC 9111). Le champ Vary:
permet d'indiquer de quoi a réellement dépendu la réponse. Ainsi :
Vary: accept-language
indique que la réponse ne dépendait que de la langue. Si un client d'un relais Web demande la même ressource mais avec une autre langue, il ne faut pas lui donner le contenu mémorisé.
HTTP permet d'exprimer des requêtes conditionnelles. Un client HTTP peut par exemple demander une ressource « sauf si elle n'a pas été modifiée depuis 08:00 ». Cela permet d'économiser des ressources, notamment dans les relais qui mémorisent RFC 9111. C'est également utile aux clients de syndication, qui récupèrent régulièrement (polling) le flux Atom pour voir s'il a changé. Les requêtes conditionnelles permettent, la plupart du temps, d'éviter tout téléchargement de ce flux.
Un autre scénario d'utilisation des requêtes conditionnelles est
le cas de la mise à jour perdue (lost
update). Prenons un client qui met à jour une ressource,
en récupérant d'abord son état actuel (avec un
GET
), en la modifiant, puis en téléversant la
version modifiée (avec par exemple un PUT
). Si
deux clients font l'opération à peu près en même temps, il y a un
risque d'une séquence :
GET
,PUT
avec sa version
modifiée,
Les mises à jour conditionnelles résoudraient ce problème : si
Client 2 fait sa mise à jour en ajoutant « seulement si la ressource
n'a pas changé », son PUT
sera refusé, il devra
refaire un GET
et il récupérera alors les
changeements de Client 1 avant d'appliquer les siens.
En pratique, les requêtes conditionnelles se font avec des champs
comme If-Modified-Since:
dont la valeur est une
date. Par exemple, le client de syndication qui a récupéré une page
le 24 février à 18:11, va envoyer un If-Modified-Since:
Thu, 24 Feb 2022 18:11:00 GMT
et le serveur ne lui
enverra la ressource Atom ou
RSS que si elle est plus récente (il recevra
un code de retour 304 dans le cas contraire). Autre exemple de
champ d'en-tête pour les requêtes conditionnelles,
If-Match:
. Ce champ demande que la requête ne
soit exécutée que si la ressource correspond à la valeur du
If-Match:
. La valeur est un validateur, comme
expliqué plus haut. If-Match:
permet ainsi de
résoudre le problème de la mise à jour perdue.
Les ressources chargées en HTTP peuvent être de grande
taille. Parfois, le réseau a un hoquet et le transfert s'arrête. Il
serait agréable de pouvoir ensuite reprendre là où on s'était
arrêté, au lieu de tout reprendre à zéro. HTTP le permet via les
requêtes d'un intervalle (section 14). Le client indique par le
champ Range:
quel intervalle de la ressource il
souhaite. Cet intervalle peut se formuler en plusieurs unités, le
champ Accept-Ranges:
permettant au serveur
d'indiquer qu'il gère ces demandes, et dans quelles unités. En
pratique, seuls les octets
marchent réellement. Par exemple, ici, j'utilise curl pour récupérer
50 octets d'un article :
% curl -v --range 3100-3150 https://www.bortzmeyer.org/1.html ... > GET /1.html HTTP/2 > Host: www.bortzmeyer.org > range: bytes=3100-3150 ... ocuments, qui forme l'ossature de la <b><a class="
Passons maintenant aux codes de retour HTTP (section 15). Il y en a au moins un qui est célèbre, 404, qui indique que la ressource demandée n'a pas été trouvée sur ce serveur et qui est souvent directement visible par l'utilisateur humain (pensez aux « pages 404 » d'erreur). Ces codes sont composés de trois chiffres, le premier indiquant la classe :
Les deux autres chiffres fournissent des détails mais un client HTTP simple peut se contenter de comprendre la classe et d'ignorer les deux autres chiffres. Par exemple, 100 signifie que le serveur a compris la requête mais qu'il faut encore attendre pour la vraie réponse, 200 veut dire qu'il n'y a rien à dire, que tout s'est bien passé comme demandé, 308 indique qu'il faut aller voir à un autre URL, 404, comme signalé plus haut, indique que le serveur n'a pas trouvé la ressource, 500 est une erreur générique du serveur, en général renvoyée quand le serveur a eu un problème imprévu. Peut-être connaissez-vous également :
PUT
),De nouveaux codes sont créés de temps en temps, et mis dans le registre IANA.
curl -v
vous affichera entre autres ce code
de retour. Si vous ne voulez que le code, et pas tous les messages
que l'utilisation de -v
entrainera, l'option
--write-out
est bien pratique :
% curl --silent --write-out "%{http_code}\n" --output /dev/null https://www.bortzmeyer.org/1.html 200 % curl --silent --write-out "%{http_code}\n" --output /dev/null https://www.bortzmeyer.org/2.html 404
Sinon, pour rire avec les codes de statut HTTP, il existe des photos de chats et une proposition d'illustrer ces codes par des émojis.
Beaucoup de choses dans HTTP peuvent être étendues (section 16). On peut créer de nouvelles méthodes, de nouveaux codes de retour, etc. Un logiciel client ou serveur ne doit donc pas s'étonner de voir apparaitre des questions ou des réponses qui n'existaient pas quand il a été programmé.
Ainsi, des nouvelles méthodes, en sus des traditionnelles
GET
, POST
,
PUT
, etc , peuvent être créées, comme l'avait
été PATCH
par le RFC 5789. La liste à jour des méthodes est dans un
registre IANA. Si vous programmez côté serveur, et que vous utilisez l'interface
CGI, la méthode est indiquée dans la variable
REQUEST_METHOD
et vous pouvez la tester, par
exemple ici en Python :
if environ["REQUEST_METHOD"] == "FOOBAR": ... Do something useful else: return unsupported(start_response, environ["REQUEST_METHOD"])
Des codes de retour peuvent également être ajoutés, comme le 451 (censure) du RFC 7725. Là aussi, la liste faisant autorité est le registre IANA.
Bien sûr, le registre le plus dynamique, celui qui voit le plus
d'ajouts, est celui des champs de
l'en-tête. Mais attention : du fait qu'il bouge beaucoup, les
nouveaux champs ne seront pas compris et utilisés par une bonne
partie des logiciels. (Au passage, notre RFC rappelle que l'ancienne
convention de préfixer les noms de champs non officiels par un
X-
a été abandonnée, par le RFC 6648.)
Et enfin, on peut étendre les listes de mécanismes d'authentification, et plusieurs autres.
Très utilisé, HTTP a évidemment connu sa part de problèmes de sécurité. La section 17 du RFC analyse les principaux risques. (Certains risques spécifiques sont traités dans d'autres RFC. Ainsi, les problèmes posés par l'analyse de l'encodage textuel de HTTP/1 sont étudiés dans le RFC 9112. Ceux liés aux URL sont dans le RFC 3986.) D'autre part, beaucoup de problème de sécurité du Web viennent :
Cette section 17 se concentre sur les problèmes de sécurité de HTTP, ce qui est déjà pas mal. Le RFC recommande la lecture des documents OWASP pour le reste.
Bon, premier problème, la notion d'autorité. Une réponse fait
autorité si elle vient de l'origine, telle
qu'indiquée dans l'URL. Le client HTTP va donc dépendre du mécanisme
de résolution de nom. Si, par exemple, la machine du client utilise
un résolveur DNS menteur, tout est
fichu. On croit aller sur http://pornhub.com/
et on se retrouve sur une page Web de
l'ARCOM. Il est donc crucial que cette
résolution de noms soit sécurisée, par exemple en utilisant un
résolveur DNS de confiance, et qui valide les réponses avec
DNSSEC. HTTPS protège partiellement. Une des
raisons pour lesquelles sa protection n'est pas parfaite est qu'il
est compliqué de valider proprement (cf. RFC 7525). Et puis les problèmes sont souvent non techniques,
par exemple la plupart des tentatives
d'hameçonnage ne vont pas viser l'autorité
mais la perception que l'utilisateur en a. Une page Web copiée sur
celle d'une banque peut être prise pour celle de la banque même si,
techniquement, il n'y a eu aucune subversion des techniques de
sécurité. Le RFC recommande qu'au minimum, les navigateurs Web
permettent d'examiner facilement l'URL vers lequel va un lien, et de
l'analyser (beaucoup d'utilisateurs vont croire, en voyant
https://nimportequoi.example/banque-de-confiance.com
que le nom de domaine est
banque-de-confiance.com
…).
On a vu qu'HTTP n'est pas forcément de bout en bout et qu'il est même fréquent que des intermédiaires se trouvent sur le trajet. Évidemment, un tel intermédaire est idéalement placé pour certaines attaques. Bref, il ne faut utiliser que des intermédiaires de confiance et bien gérés. (De nombreuses organisations placent sur le trajet de leurs requêtes HTTP des boites noires au logiciel privateur qui espionnent le trafic et font Dieu sait quoi avec les données récoltées.)
HTTP est juste un protocole entre le client et le serveur. Le
client demande une ressource, le serveur lui envoie. D'où le serveur
a-t-il tiré cette ressource ? Ce n'est pas l'affaire de HTTP. En
pratique, il est fréquent que le serveur ait simplement lu un
fichier pré-existant sur ses disques et, en outre, que le chemin
menant à ce fichier vienne d'une simple transformation de l'URL. Par
exemple, Apache,
avec la directive DocumentRoot
valant
/var/doc/mon-beau-site
et une requête HTTP
GET /toto/tata.html
va chercher un fichier
/var/www/mon-beau-site/toto/tata.html
. Dans ce
cas, attention, certaines manipulations sur le chemin donné en
paramètre à GET
peuvent donner au client
davantage d'accès que ce qui était voulu. Ainsi, sans précautions
particulières, une requête GET
/../toto/tata.html
serait traduite en
/var/www/mon-beau-site/../toto/tata.html
, ce
qui, sur Unix, équivaudra à
/var/www/toto/tata.html
, où il n'était
peut-être pas prévu que le client puisse se promener. Les auteurs de
serveurs doivent donc être vigilants : ce qui vient du client n'est
pas digne de confiance.
Autre risque lorsqu'on fait une confiance aveugle aux données envoyées par le client, l'injection. Ces données, par exemple le chemin dans l'URL, sont traitées par des langages qui ont des règles spéciales pour certains caractères. Si un de ces caractères se retrouve dans l'URL, et qe le programme, côté serveur, n'est pas prudent avec les données extérieures, le ou les caractères spéciaux seront interprétés, avec parfois d'intéressantes failles de sécurité à la clé. (Mais, attention, tester la présence de « caractères dangereux » n'est en général pas une bonne idée.)
La liste des questions de sécurité liées à HTTP ne s'arrête pas là. On a vu que HTTP ne mettait pas de limite de taille à des éléments comme l'URL. Un analyseur imprudent, côté serveur, peut se faire attaquer par un client qui enverrait un chemin d'URL très long, déclenchant par exemple un débordement de tableau.
HTTP est un protocole très bavard, et un client HTTP possède
beaucoup d'informations sur l'utilisateur humain qui est
derrière. Le client doit donc faire très attention à ne pas envoyer
ces données. Le RFC ne donne pas d'exemple précis mais on peut par
exemple penser au champ Referer
qui indique
l'URL d'où on vient. Si le client l'envoie systématiquement, et que
l'utilisateur visitait un site Web interne de l'organisation avant de
cliquer vers un lien externe, son navigateur enverra des détails sur
le site Web interne. Autre cas important,
un champ comme Accept-Language
, qu'on peut
estimer utile dans certains cas, est dangereux pour la vie privée,
transmettant une information qui peut être sensible, par exemple si
on a indiqué une langue minoritaire et mal vue dans son pays. Et
User-Agent
facilite le ciblage d'éventuelles
attaques du serveur contre le client.
Du fait de ce caractère bavard, et aussi parce que, sur l'Internet, il y a des choses qu'on ne peut pas dissimuler facilement (comme l'adresse IP source), ce que le serveur stocke dans ses journaux est donc sensible du point de vue de la vie privée. Des lois comme la loi Informatique & Libertés encadrent la gestion de telles bases de données personnelles. Le contenu de ces journaux doit donc être protégé contre les accès illégitimes.
Comme HTTP est bavard et que le client envoie beaucoup de choses
(comme les Accept-Language
et
User-Agent
cités plus haut), le serveur peut
relativement facilement faire du
fingerprinting,
c'est-à-dire reconnaitre un client HTTP parmi des dizaines ou des
centaines de milliers d'aures. (Vous ne me croyez pas ? Regardez le
Panopticlick.) Un serveur peut ainsi suivre un client
à la trace, même sans
cookies (voir
« A
Survey on Web Tracking: Mechanisms, Implications, and
Defenses »).
Voilà, et encore je n'ai présenté ici qu'une partie des questions de sécurité liées à l'utilisation de HTTP. Lisez le RFC pour en savoir plus. Passons maintenant aux différents registres IANA qui servent à stocker les différents éléments du protocole HTTP. Je les ai présenté (au moins une partie d'entre eux !) plus haut mais je n'ai pas parlé de la politique d'enregistrement de nouvaux éléments. En suivant la terminologie du RFC 8126, il y a entre autres le registre des méthodes (pour ajouter une nouvelle méthode, il faut suivre la politique « Examen par l'IETF », une des plus lourdes), le registre des codes de retour (même politique), le registre des champs (désormais séparé de celui des champs du courrier, politique « Spécification nécessaire »), etc.
Ah, et si vous voulez la syntaxe complète de HTTP sous forme d'une grammaire formelle, lisez l'ABNF en annexe A.
Et avec un langage de programmation ? Vu le succès de HTTP et sa présence partout, il n'est pas étonnant que tous les langages de programmation permettent facilement de faire des requêtes HTTP. HTTP, en tout cas HTTP/1, est suffisamment simple pour qu'on puisse le programmer soi-même en appelant les fonctions réseau de bas niveau, mais pourquoi s'embêter ? Utilisons les bibliothèques prévues à cet effet et commençons par le langage Python. D'abord avec la bibliothèque standard http.client :
conn = http.client.HTTPConnection(HOST) conn.request("GET", PATH) result = conn.getresponse() body = result.read().decode()
Et hop, la variable body
contient une
représentation de la ressource demandée (le programme complet est en
sample-http-client.py
). En pratique, la plupart des
programmeurs Python utiliseront sans doute une autre
bibliothèque standard, qui n'est pas spécifique à HTTP et
permet de traiter des URL quelconques (cela
donne le programme sample-http-urllib.py
). D'encore
plus haut niveau (mais pas incluse dans la bibliothèque standard, ce
qui ajoute une dépendance à votre programme) est la bibliothèque
Requests, souvent utilisée (voir par exemple sample-http-requests.py
).
Ensuite, avec le langage Go. Là aussi, il dispose de HTTP dans sa bibliothèque standard :
response, err := http.Get(Url) defer response.Body.Close() body, err := ioutil.ReadAll(response.Body)
Le programme complet est sample-http.go
.
Et ici un client en Elixir, utilisant la bibliothèque HTTPoison :
HTTPoison.start() {:ok, result} = HTTPoison.get(@url)
La version longue est en
.sample-http.ex
L'annexe B de notre RFC fait la liste des principaux changements depuis les précédents RFC. Je l'ai dit, le protocole ne change pas réellement mais il y a quand même quelques modifications, notamment des clarifications de textes trop ambigus (par exemple la définition des intervalles). Et bien sûr le gros changement est qu'il y a désormais une définition abstraite de ce qu'est un message HTTP, séparée des définitions concrètes pour les trois versions de HTTP en service. En outre, il y a désormais des recommandations explicites de taille minimale à accepter pour certains élements (par exemple 8 000 octets pour les URI).
HTTP est, comme vous le savez, un immense succès, dû à la place prise par le Web, dont il est le protocole de référence. Le RFC résume l'histoire de HTTP :
GET
, et qui n'a été
documenté qu'après,Date de publication du RFC : Août 2021
Auteur(s) du RFC : F. Gont, G. Gont (SI6 Networks), M. Lichvar (Red Hat)
Chemin des normes
Première rédaction de cet article le 13 septembre 2021
Le protocole NTP utilisait traditionnellement un port bien connu, 123, comme source et comme destination. Cela facilite certaines attaques en aveugle, lorsque l'attaquant ne peut pas regarder le trafic, mais sait au moins quels ports seront utilisés. Pour compliquer ces attaques, ce RFC demande que NTP utilise des numéros de ports aléatoires autant que possible.
NTP est un très vieux protocole (sa norme actuelle est le RFC 5905 mais sa première version date de 1985, dans le RFC 958). Dans son histoire, il a eu sa dose des failles de sécurité. Certaines des attaques ne nécessitaient pas d'être sur le chemin entre deux machines qui communiquent car elles pouvaient se faire en aveugle. Pour une telle attaque, il faut deviner un certain nombre de choses sur la communication, afin que les paquets de l'attaquant soient acceptés. Typiquement, il faut connaitre les adresses IP source et destination, ainsi que les ports source et destination et le key ID de NTP (RFC 5905, section 9.1). NTP a plusieurs modes de fonctionnement (RFC 5905, sections 2 et 3). Certains nécessitent d'accepter des paquets non sollicités et, dans ce cas, il faut bien écouter sur un port bien connu, en l'occurrence 123. Mais ce n'est pas nécessaire dans tous les cas et notre RFC demande donc qu'on n'utilise le port bien connu que si c'est nécessaire, au lieu de le faire systématiquement comme c'était le cas au début de NTP et comme cela se fait encore trop souvent (« Usage Analysis of the NIST Internet Time Service »). C'est une application à NTP d'un principe général sur l'Internet, documenté dans le RFC 6056 : n'utilisez pas de numéros de port statiques ou prévisibles. Si on suit ce conseil, un attaquant en aveugle aura une information de plus à deviner, ce qui gênera sa tâche. Le fait d'utiliser un port source fixe a valu à NTP un CVE, CVE-2019-11331.
La section 3 du RFC résume les considérations à prendre en compte. L'idée de choisir aléatoirement le port source pour faire face aux attaques en aveugle est présente dans bien d'autres RFC comme le RFC 5927 ou le RFC 4953. Elle est recommandée par le RFC 6056. Un inconvénient possible (mais mineur) est que la sélection du chemin en cas d'ECMP peut dépendre du port source (calcul d'un condensat sur le tuple à cinq éléments {protocole, adresse IP source, adresse IP destination, port source, port destination}, avant d'utiliser ce condensat pour choisir le chemin) et donc cela peut affecter les temps de réponse, troublant ainsi NTP, qui compte sur une certaine stabilité du RTT. D'autre part, le port source aléatoire peut gêner certaines stratégies de filtrage par les pare-feux : on ne peut plus reconnaitre un client NTP à son port source. Par contre, un avantage du port source aléatoire est que certains routeurs NAT sont suffisamment bogués pour ne pas traduire le port source s'il fait partie des ports « système » (inférieurs à 1 024), empêchant ainsi les clients NTP situés derrière ces routeurs de fonctionner. Le port source aléatoire résout le problème.
Assez de considérations, passons à la norme. Le RFC 5905, section 9.1, est modifié pour remplacer la supposition qui était faite d'un port source fixe par la recommandation d'un port source aléatoire.
Cela ne pose pas de problème particulier de mise en œuvre. Par
exemple, sur un système POSIX, ne
pas faire de bind()
sur la
prise suffira à ce que les paquets associés
soient émis avec un port source aléatoirement sélectionné par le
système d'exploitation.
À propos de mise en œuvre, où en sont les logiciels actuels ? OpenNTPD n'a jamais utilisé le port source 123 et est donc déjà compatible avec la nouvelle règle. Même chose pour Chrony. Par contre, à ma connaissance, ntpd ne suit pas encore la nouvelle règle.
Date de publication du RFC : Septembre 2021
Auteur(s) du RFC : L. Lhotka (CZ.NIC), P. Spacek (ISC)
Chemin des normes
Réalisé dans le cadre du groupe de travail IETF dnsop
Première rédaction de cet article le 9 septembre 2021
YANG est le langage standard à
l'IETF
pour décrire des modèles de données, afin de, par exemple, gérer
automatiquement des ressources. Ce RFC décrit le module YANG
iana-dns-class-rr-type
, qui rassemble les
définitions des types d'enregistrements DNS.
YANG est normalisé dans le RFC 7950, et d'innombrables RFC décrivent des modules YANG pour de nombreux protocoles. YANG est utilisé par des protocoles de gestion à distance de ressources, comme RESTCONF (RFC 8040) ou NETCONF (RFC 6241). Mais ce RFC est le premier qui va utiliser YANG pour le DNS. L'un des objectifs (à long terme pour l'instant) est d'utiliser RESTCONF ou un équivalent pour gérer des serveurs DNS, résolveurs ou serveurs faisant autorité. (C'est un très vieux projet que cette gestion automatisée et normalisée des serveurs DNS.) Un mécanisme standard de gestion des serveurs nécessite un modèle de données commun, et c'est là que YANG est utile. Notre RFC est encore loin d'un modèle complet, il ne définit que le socle, le type des données que le DNS manipule. C'est la version YANG de deux registres IANA, celui des types d'enregistrements DNS et celui des classes (même si ce dernier concept est bien abandonné aujourd'hui).
Le registre IANA pour le DNS contient treize sous-registres. Le RFC n'en passe que deux en YANG, les classes et les types d'enregistrement. Les autres devront attendre un autre RFC. Les types d'enregistrement sont modélisés ainsi :
typedef rr-type-name { type enumeration { enum A { value 1; description "a host address"; reference "RFC 1035"; } enum NS { value 2; description "an authoritative name server"; ...
Et les classes (mais, rappelez-vous, seule la classe
IN
compte aujourd'hui) :
typedef dns-class-name { type enumeration { enum IN { value 1; description "Internet (IN)"; reference "RFC 1035"; } enum CH { value 3; description "Chaos (CH)"; reference "D. Moon, 'Chaosnet', A.I. Memo 628, Massachusetts Institute of Technology Artificial Intelligence Laboratory, June 1981."; } ...
Le module YANG complet se retrouve dans le registre IANA (créé par le RFC 6020).
Le module YANG devra suivre l'actuel registre IANA, qui utilise un autre format. Pour faciliter la synchronisation, le RFC contient une feuille de style XSLT pour convertir l'actuel format, qui est la référence, vers le format YANG. La feuille de style est dans l'annexe A du RFC mais vous avez une copie ici. Voici comment produire et vérifier le module YANG :
% wget https://www.iana.org/assignments/dns-parameters/dns-parameters.xml % xsltproc iana-dns-class-rr-type.xsl dns-parameters.xml > iana-dns-class-rr-type.yang % pyang iana-dns-class-rr-type.yang
Le moteur XSLT xsltproc fait partie de la
libxslt. Le vérificateur YANG Pyang
est distribué via
GitHub mais on peut aussi l'installer avec pip (pip
install pyang
).
Date de publication du RFC : Août 2021
Auteur(s) du RFC : R. Raszuk (NTT Network Innovations), B. Decraene (Orange), C. Cassar, E. Aman, K. Wang (Juniper Networks)
Chemin des normes
Réalisé dans le cadre du groupe de travail IETF idr
Première rédaction de cet article le 13 septembre 2021
Les grands AS ont tellement de routeurs BGP qu'ils ne peuvent pas connecter chaque routeur à tous les autres. On utilise alors souvent des réflecteurs de route, un petit nombre de machines parlant BGP auxquelles tout le monde se connecte, et qui redistribuent les routes externes apprises. Mais une machine BGP ne redistribue que les routes qu'elle utiliserait elle-même. Or, le réflecteur risque de faire un choix en fonction de sa position dans le réseau, qui n'est pas la même que celle du routeur « client ». Les routeurs risquent donc d'apprendre du réflecteur des routes sous-optimales (la route optimale étant typiquement celle qui amène à la sortie le plus vite possible, en application de la méthode de la patate chaude). Ce RFC définit une extension de BGP qui va permettre de sélectionner des routes spécifiques à un client, ou à un groupe de clients.
Un petit rappel : un réflecteur de routes (route reflector) fonctionne sur iBGP (Internal BGP), à l'intérieur d'un AS, alors que les serveurs de routes (route server) font de l'eBGP (External BGP), par exemple sur un point d'échange. Ces réflecteurs sont décrits dans le RFC 4456. Ils ne sont pas la seule méthode pour distribuer l'information sur les routes externes à l'intérieur d'un grand AS, mais c'est quand même la solution la plus fréquente.
Le RFC 4456 notait déjà que, vu les coûts attribués aux liens internes à l'AS, le réflecteur ne choisirait pas forcément les mêmes routes que si on avait utilisé un maillage complet. Le routage « de la patate chaude » (qui consiste à essayer de faire sortir le paquet de son réseau le plus vite possible, pour que ce soit un autre qui l'achemine) risque donc de ne pas aussi bien marcher : le point de sortie lorsqu'on utilise le réflecteur sera peut-être plus éloigné que si on avait le maillage complet, surtout si le réflecteur est situé en dehors du chemin habituel des paquets et n'a donc pas la même vision que les routeurs « normaux ». Or, c'est un cas fréquent. Le réflecteur choisira alors des routes qui sont optimales pour lui, mais qui ne le sont pas pour ces routeurs « normaux ».
La solution ? Permettre à l'administrateur réseaux de définir une localisation virtuelle pour le réflecteur, à partir de laquelle il fera ses calculs et choisira ses routes, au lieu d'utiliser sa localisation physique. Cette localisation virtuelle sera une adresse IP. Le réflecteur peut avoir plusieurs de ces localisations virtuelles, adaptées à des publics différents. Bref, le texte du RFC 4271 qui concerne la sélection de la meilleure route (section 9.1.2.2 du RFC 4271) est modifié pour remplacer « en fonction du saut suivant » par « en fonction de l'adresse IP configurée dans le réflecteur ».
Pour que cela marche, il faut que le réflecteur ait une vue complète du réseau, pour pouvoir calculer des coûts à partir de n'importe quel point du réseau. C'est possible avec les IGP à état des liens comme OSPF, ou bien avec BGP-LS (RFC 7752).
Et si le réflecteur a plusieurs clients ayant des desiderata différents, par exemple parce qu'ils sont situés à des endroits différents ? Dans ce cas, il doit faire tourner plusieurs processus de décision, chacun configuré avec une localisation vituelle différente.
Les principales marques de routeurs mettent déjà en œuvre ce RFC, comme on peut le voir sur la liste des implémentations. Du côté des logiciels qui ne sont pas forcément installés sur des routeurs, il semble que BIRD ne sache pas encore faire comme décrit dans ce RFC.
Date de publication du RFC : Septembre 2021
Auteur(s) du RFC : A. Biryukov, D. Dinu (University of Luxembourg), D. Khovratovich (ABDK Consulting), S. Josefsson (SJD AB)
Pour information
Réalisé dans le cadre du groupe de recherche IRTF cfrg
Première rédaction de cet article le 8 septembre 2021
En général, en informatique, on essaie de diminuer la consommation de mémoire et de processeur des programmes, afin de les rendre plus efficaces. Mais il y a aussi des cas où on va tenter de réduire délibérement l'efficacité d'un programme. C'est le cas des fonctions de condensation qui sont utilisées en sécurité : on ne veut pas faciliter la tâche de l'attaquant. Ainsi, Argon2, spécifié dans ce RFC est volontairement très consommatrice de mémoire et d'accès à cette mémoire. Elle peut être utilisée pour condenser des mots de passe, ou pour des sytèmes à preuve de travail.
L'article original décrivant Argon2 est « Argon2: the memory-hard function for password hashing and other applications » et vous pouvez également lire « Argon2: New Generation of Memory-Hard Functions for Password Hashing and Other Applications ». Notre RFC reprend la description officielle, mais en s'orientant davantage vers les programmeurs et programmeuses qui devront mettre en œuvre cette fonction. Argon2 est conçu pour être memory-hard, c'est-à-dire faire souvent appel à la mémoire de la machine. Cela permet notamment aux ordinateurs classiques de tenir tête aux systèmes à base d'ASIC. (Par exemple, nombreuses sont les chaînes de blocs utilisant des preuves de travail. La fonction utilisée par Bitcoin, SHA-256, conçue pour être simple et rapide, n'est pas memory-hard et le résultat est qu'il y a belle lurette qu'un ordinateur, même rapide, n'a plus aucune chance dans le minage de Bitcoin. Seules les machines spécialisées peuvent rester en course, ce qui contribue à centraliser le minage dans peu de fermes de minage.) Argon2 est donc dur pour la mémoire, comme décrit dans l'article « High Parallel Complexity Graphs and Memory-Hard Functions ». À noter qu'Argon2 est très spécifique à l'architecture x86.
Argon2 se décline en trois variantes, Argon2id (celle qui est recommandée par le RFC), Argon2d et Argon2i. Argon2d a des accès mémoire qui dépendent des données, ce qui fait qu'il ne doit pas être employé si un attaquant peut examiner ces accès mémoire (attaque par canal auxiliaire). Il convient donc pour du minage de cryptomonnaie mais pas pour une carte à puce, que l'attaquant potentiel peut observer en fonctionnement. Argon2i n'a pas ce défaut mais est plus lent, ce qui ne gêne pas pour la condensation de mots de passe mais serait un problème pour le minage. Argon2id, la variante recommandée, combine les deux et est donc à la fois rapide et résistante aux attaques par canal auxiliaire. (Cf. « Tradeoff Cryptanalysis of Memory-Hard Functions », pour les compromis à faire en concevant ces fonctions dures pour la mémoire.) Si vous hésitez, prenez donc Argon2id. La section 4 du RFC décrit plus en détail les paramètres des variantes, et les choix recommandés.
Argon repose sur une fonction de condensation existante (qui n'est pas forcément dure pour la mémoire) et le RFC suggère Blake (RFC 7693).
La section 3 du RFC décrit l'algorithme mais je n'essaierai pas de vous le résumer, voyez le RFC.
La section 5 contient des vecteurs de test si vous voulez programmer Argon2 et vérifier les résultats de votre programme.
La section 7 du RFC revient en détail sur certaines questions de sécurité, notamment l'analyse de la résistance d'Argon2, par exemple aux attaques par force brute.
Date de publication du RFC : Août 2021
Auteur(s) du RFC : W. Toorop (NLnet Labs), S. Dickinson (Sinodun IT), S. Sahib (Brave Software), P. Aras, A. Mankin (Salesforce)
Chemin des normes
Réalisé dans le cadre du groupe de travail IETF dprive
Première rédaction de cet article le 12 septembre 2021
Traditionnellement, le transfert d'une zone DNS depuis le serveur maitre vers ses esclaves se fait en clair. Si l'authentification et l'intégrité sont protégées, par exemple par TSIG, le transfert en clair ne fournit pas de confidentialité. Or on n'a pas forcément envie qu'un tiers puisse lire l'entièreté de la zone. Ce RFC normalise un transfert de zones sur TLS, XoT (zone transfer over TLS). Il en profite pour faire quelques ajustement dans l'utilisation de TCP pour les transferts de zone.
Le RFC fait quarante-deux pages mais le principe est très simple. Au lieu de faire le transfert de zones (aussi nommé AXFR, RFC 5936) directement sur TCP, on le fait sur du TLS, comme le fait DoT (RFC 7858). Le reste du RFC est consacré à des détails pratiques, mais le concept tient en une seule phrase.
Le DNS sur TLS est normalisé depuis cinq
ans, mais il ne couvre officiellement que la communication entre la
machine terminale et le résolveur. Bien que des essais aient
été faits pour communiquer en TLS avec des serveurs faisant
autorité, rien n'est encore normalisé. Mais, au fait, est-ce
que les transferts de zone ont vraiment besoin de confidentialité ?
Le RFC 9076 ne traite pas le problème, le
renvoyant aux RFC 5936 (la norme du transfert
de zones, qui parle de la possibilité de restreindre ce transfert à
des machines autorisées) et au RFC 5155 (qui
parle des risques d'énumération de tous les noms d'une
zone). Certes, le DNS étant public, tout le monde peut interroger
les serveurs faisant autorité, et connaitre ainsi les données
associées à un nom de
domaine, mais il faut pour cela connaitre les noms. Si
les petites zones n'ont rien de secret (elles ne contiennent que
l'apex et www
), si certaines zones sont
publiques (la racine, par exemple), pour les autres zones, leur
responsable ne souhaite pas forcément qu'on ait accès à tous les
noms. Certains peuvent être le nom d'une personne
(le-pc-de-henri.zone.example
), et donc être des
données personnelles, à protéger (le RFC note bien sûr que c'est une
mauvaise pratique, mais elle existe), certains noms peuvent
renseigner sur les activités et les projets d'une entreprise. Bref,
il peut être tout à fait raisonnable de ne pas vouloir transmettre
la zone en clair.
À noter qu'une surveillance du réseau pendant un transfert de zones fait en clair n'est pas la seule façon de trouver tous les noms. Une autre technique est l'énumération, utilisant par exemple les enregistrement NSEC de DNSSEC. Les enregistrement NSEC3 du RFC 5155 ont été conçus pour limiter ce risque (mais pas le supprimer ; le projet NSEC5 va essayer de résoudre le problème mais c'est loin d'être garanti).
Comment sécurise-t-on les transferts de zone aujourd'hui ? Les TSIG du RFC 8945 protègent l'intégrité mais pas la confidentialité. Vous pouvez trouver des détails pratiques sur la sécurisation des transferts de zone, combinant TSIG et ACL, dans un document du NIST en anglais et un document de l'ANSSI en français. On peut aussi en théorie utiliser IPsec entre maitre et esclaves mais cela semble irréaliste. D'où l'idée de base de ce nouveau RFC : mettre le transfert de zones sur TLS. Après tout, on utilise déjà TLS pour sécuriser la communication entre le client final et le résolveur : le protocole DoT, normalisé dans le RFC 7858.
Notre RFC introduit quelques nouveaux acronymes rigolos :
Pour la terminologie liée à la vie privée, voir le RFC 6973.
La section 3 du RFC spécifie le modèle de menace : on veut protéger le transfert de zones contre l'espionnage par un tiers, mais on n'essaie pas de cacher l'existence de la zone, ou le fait que telle machine soit serveur faisant autorité pour la zone. En effet, ces informations peuvent être obtenues facilement en interrogeant le DNS ou, dans le cas d'un maitre caché, en regardant le trafic réseau, sans même en décrypter le contenu.
Les principes de conception de XoT ? Fournir de la confidentialité et de l'authentification grâce à TLS, mais aussi en profiter pour améliorer les performances. Un certain nombre de programmes DNS ne peuvent pas, par exemple, utiliser la même connexion TCP pour plusieurs transferts de zone (même si le RFC 5936 le recommande). Il est difficile de résoudre ce problème sans casser les programmes existants et pas encore mis à jour. Par contre, XoT étant nouveau, il peut spécifier dès le début certains comportements qui améliorent les performances, comme l'utilisation d'une seule connexion pour plusieurs transferts.
Petit rappel de comment fonctionne le transfert de zones (RFC 5936, si vous voulez tous les détails). D'abord, le fonctionnement le plus courant : le serveur maitre (ou primaire) envoie un NOTIFY (RFC 1996) à ses esclaves (ou secondaires). Ceux-ci envoient une requête de type SOA au maitre puis, si le numéro de série récupéré est supérieur au leur, ils lancent l'AXFR sur TCP. Il y a des variations possibles : si le maitre n'envoie pas de NOTIFY ou bien s'ils sont perdus, les esclaves testent quand même de temps en temps le SOA (paramètre Refresh du SOA). Autre variante possible : les esclaves peuvent utiliser IXFR (RFC 1995) au lieu de AXFR pour ne transférer que la partie de la zone qui a changé. Et ils peuvent avoir à se rabattre en AXFR si le maitre ne gérait pas IXFR. Et les messages IXFR peuvent être sur UDP ou sur TCP (en pratique, BIND, NSD et bien d'autres font toujours l'IXFR sur TCP). Bref, il y a plusieurs cas. Les messages NOTIFY et SOA ne sont pas considérés comme sensibles du point de vue de la confidentialité et XoT ne les protège pas.
Maintenant, avec XoT, comment ça se passera (section 6 du RFC) ?
Une fois le transfert de zones décidé (via SOA, souvent précédé du
NOTIFY), le client (qui est donc le serveur secondaire) doit établir
une connexion TLS (1.3 minimum), en utilisant ALPN, avec
le nom dot
(on notera que c'est donc le même
ALPN pour XoT et pour DoT, l'annexe A du RFC explique ce choix). Le
port est par défaut le classique 853 de
DoT. Le client authentifie le serveur via les techniques du RFC 8310. Le serveur vérifie le client, soit avec
des mécanismes TLS (plus sûrs mais plus compliqués à mettre en
œuvre), soit avec les classiques ACL. Le client TLS (le serveur DNS esclave) fait
ensuite de l'IXFR ou du AXFR classique sur ce canal TLS. Le serveur
doit être capable d'utiliser les codes d'erreur étendus du RFC 8914.
Voilà, c'est tout. Maintenant, quelques détails
pratiques. D'abord, rappelez-vous qu'il n'existe pas à l'heure
actuelle de norme pour du DoT entre résolveur et serveur faisant
autorité. Mais il y a des expériences en cours. Ici, par exemple, je
fais du DoT avec un serveur faisant autorité pour facebook.com
:
% kdig +tls @a.ns.facebook.com. SOA facebook.com ;; TLS session (TLS1.3)-(ECDHE-X25519)-(RSA-PSS-RSAE-SHA256)-(AES-256-GCM) ... ;; ANSWER SECTION: facebook.com. 3600 IN SOA a.ns.facebook.com. dns.facebook.com. 1631273358 14400 1800 604800 300 ;; Received 86 B ;; Time 2021-09-10 13:35:44 CEST ;; From 2a03:2880:f0fc:c:face:b00c:0:35@853(TCP) in 199.0 ms
Mais on pourrait imaginer des serveurs faisant autorité qui ne fournissent pas ce service, pas encore normalisé, mais qui veulent faire du XoT. Est-ce possible de réserver TLS aux transferts de zone, mais sans l'utiliser pour les requêtes « normales » ? Oui, et dans ce cas le serveur doit répondre REFUSED à ces requêtes normales, avec le code d'erreur étendu 21 (Not supported). Ceci dit, il est difficile de dire comment réagiront les clients ; ils risquent d'ignorer le code d'erreur étendu et de recommencer bêtement (ou au contraire d'arrêter complètement d'utiliser le serveur).
Le but de XoT est de protéger le contenu de la zone contre les regards indiscrets. Mais comme vous le savez, TLS ne dissimule pas la taille des données qui passent entre ses mains et l'observation de cette taille peut donner des informations à un observateur, par exemple la taille des mises à jour en IXFR indique l'activité d'une zone. Le RFC suggère donc de remplir les données. Cela peut nécessiter d'envoyer des réponses vides, ce qui était interdit par les précédents RFC mais est maintenant autorisé.
Et les différentes formes de parallélisme des requêtes que permet le DNS sur TCP (et donc DoT) ? Elles sont décrites dans le RFC 7766. Si elles sont nécessaires pour exploiter le DNS de manière satisfaisante, elles ne sont pas forcément utiles pour XoT et notre RFC explique qu'on n'est pas forcé, par exemple, de permettre le pipelining (envoi d'une nouvelle requête quand la réponse à la précédente n'est pas encore arrivée) quand on fait du XoT. Par contre, comme avec tous les cas où on utilise du DNS sur TCP, il est recommandé de garder les connexions ouvertes un certain temps, par exemple en suivant le RFC 7828.
J'ai parlé plus haut de l'authentification dans une session XoT. La section 9 du RFC revient en détail sur cette question. Quels sont les mécanismes existants pour authentifier un transfert de zones ? Ils diffèrent par les services qu'ils rendent :
Les mécanismes possibles sont :
Quelles sont les mises en œuvres actuelles de XoT ? Une liste existe. Actuellement, il n'y a guère que NSD qui permet le XoT mais le travail est en cours pour d'autres. Voyons comment faire du XoT avec NSD. On télécharge la dernière version (XoT est apparu en 4.3.7), on la compile (pas besoin d'options particulières) et on la configure, par exemple ainsi :
server: port: 7053 ... tls-service-key: "server.key" tls-service-pem: "server.pem" # No TLS service if the port is not explicit in an interface definition ip-address: 127.0.0.1@7153 tls-port: 7153 zone: name: "fx" zonefile: "fx" # NSD 4.3.7 (september 2021) : not yet a way to restrict XoT provide-xfr: 127.0.0.1 NOKEY
Ainsi configuré, NSD va écouter sur le port 7153 en TLS. Notons qu'il n'y a pas de moyen de configurer finement : il accepte forcément XoT et les requêtes « normales ». De même, on ne peut pas obliger un client DNS à n'utiliser que XoT. Testons avec kdig, un client DNS qui accepte TLS, d'abord une requête normale :
% kdig +tls @localhost -p 7153 SOA fx ;; TLS session (TLS1.3)-(ECDHE-SECP256R1)-(RSA-PSS-RSAE-SHA256)-(AES-256-GCM) ;; ->>HEADER<<- opcode: QUERY; status: NOERROR; id: 11413 ... ;; ANSWER SECTION: fx. 600 IN SOA ns1.fx. root.fx. 2021091201 604800 86400 2419200 86400 ... ;; Time 2021-09-12 11:06:39 CEST ;; From 127.0.0.1@7153(TCP) in 0.2 ms
Parfait, tout marche. Et le transfert de zones ?
% kdig +tls @localhost -p 7153 AXFR fx ;; AXFR for fx. fx. 600 IN SOA ns1.fx. root.fx. 2021091201 604800 86400 2419200 86400 ... ;; Received 262 B (1 messages, 9 records) ;; Time 2021-09-12 11:07:25 CEST ;; From 127.0.0.1@7153(TCP) in 2.3 ms
Excellent, nous faisons du XoT. Wireshark nous le confirme :
% tshark -d tcp.port==7153,tls -i lo port 7153 ... 4 0.001200185 127.0.0.1 → 127.0.0.1 TLSv1 439 Client Hello ... 6 0.006305031 127.0.0.1 → 127.0.0.1 TLSv1.3 1372 Server Hello, Change Cipher Spec, Application Data, Application Data, Application Data, Application Data ... 10 0.007385823 127.0.0.1 → 127.0.0.1 TLSv1.3 140 Application Data
Dans ce cas, on utilisait NSD comme serveur primaire, recevant les clients XoT. Il peut aussi fonctionner comme serveur secondaire et il est alors client XoT. Je n'ai pas testé mais John Shaft l'a fait et en gros la configuration ressemble à :
tls-auth: name: un-nom-pour-identifier-ce-bloc auth-domain-name: nom-du-serveur-principal zone: ... request-xfr: AXFR 2001:db8::1 NOKEY un-nom-pour-identifier-ce-bloc
Date de publication du RFC : Août 2021
Auteur(s) du RFC : V. Dukhovni (Two Sigma), S. Huque (Salesforce), W. Toorop (NLnet Labs), P. Wouters (Aiven), M. Shore (Fastly)
Expérimental
Première rédaction de cet article le 12 août 2021
Lorsqu'on authentifie un serveur TLS (par exemple un serveur
DoT) avec DANE, le client TLS
doit utiliser un résolveur DNS
validant et tenir compte de son résultat
(données validées ou pas), ce qui n'est pas toujours facile, ni même
possible dans certains environnements. Ce RFC propose une solution pour cela : une
extension à TLS, dnssec_chain
, qui permet au
serveur TLS d'envoyer l'ensemble des enregistrements DNS pertinents
au client, le dispensant ainsi de solliciter un résolveur
validant. Toute la cuisine de vérification peut ainsi se faire sans
autre canal que TLS.
TLS
(normalisé dans les RFC 5246 et RFC 8446) fournit un canal de communication
sécurisé, si on a authentifié le serveur situé en face. La plupart
du temps, on authentifie via un certificat
PKIX (RFC 5280). Dans certains
cas, ce n'est pas facile d'utiliser PKIX. Par exemple, pour
DoT (RFC 7858), le
résolveur DoT n'est typiquement connu que par une adresse IP, alors que le
certificat ne contient en général qu'un nom
(oui, le RFC 8738 existe, mais n'est pas
toujours activé par les autorités de
certification). Les serveurs DoT sont souvent
authentifiables par DANE (RFC 6698 et
RFC 7671). Par exemple, mon résolveur DoT
public, dot.bortzmeyer.fr
, est authentifiable
avec DANE (regardez les données TLSA de
_853._tcp.dot.bortzmeyer.fr
). Mais DANE dépend
de la disponibilité d'un résolveur DNS
validant. Diverses raisons font que le
client TLS peut avoir du mal à utiliser un résolveur validant, et à
récupérer l'état de la validation (toutes les API de résolution de
noms ne donnent pas accès à cet état). L'article « Discovery
method for a DNSSEC validating stub resolver »
donne des informations sur ces difficultés. Bref, devoir récupérer
ces enregistrements DANE de manière sécurisée peut être un
problème. Et puis utiliser le DNS pour récupérer des données
permettant d'authentifier un résolveur DNS pose un
problème d'œuf et de poule.
D'où l'idée centrale de ce RFC : le client TLS demande au serveur de lui envoyer ces enregistrements. Le client n'a plus « que » à les valider à partir d'une clé de confiance DNSSEC (typiquement celle de la racine, commune à tout le monde). Le serveur peut aussi, s'il n'utilise pas DANE, renvoyer la preuve de non-existence de ces enregistrements, le client saura alors, de manière certaine, qu'il peut se rabattre sur un autre mécanisme d'authentification. L'extension TLS de notre RFC peut servir à authentifier un certificat complet, ou bien une clé brute (RFC 7250). Dans ce dernier cas, cette extension protège en outre contre les attaques dites « Unknown Key-Share ».
Notez le statut de ce RFC : cette spécification est pour l'instant expérimentale, elle n'a pas vraiment réuni de consensus à l'IETF, mais elle fait partie des solutions proposées pour sécuriser DoT, dans le RFC 8310 (section 8.2.2).
Bon, maintenant, l'extension elle-même (section 2). C'est une
extension TLS (ces extensions sont spécifiées dans la section 4.2 du
RFC 8446), elle est nommée
dnssec_chain
et enregistrée
dans le registre IANA, avec le code 59. Son champ de données
(extension_data
) vaut, dans le
ClientHello
, le port de
connexion et, dans le ServerHello
, un ensemble
d'enregistrements DNS au format binaire du DNS (pour TLS 1.2 ; en
1.3, cet ensemble est attaché au certificat du serveur). Dans le
langage de description des données TLS, cela donne, du client vers
le serveur :
struct { uint16 PortNumber; } DnssecChainExtension;
Et du serveur vers le client :
struct { uint16 ExtSupportLifetime; opaque AuthenticationChain<1..2^16-1> } DnssecChainExtension;
(Le ExtSupportLifetime
indique que le serveur
s'engage à continuer à gérer cette extension pendant au moins ce
temps - en heures. Si ce n'est plus le cas avant l'expiration du
délai, cela peut indiquer une usurpation par un faux serveur.)
Cette extension est donc une forme, assez spéciale, de « DNS sur
TLS ». Le client ne doit pas oublier d'envoyer un SNI (RFC 6066), pour que le serveur sache quel nom va
être authentifié et donc quels enregistrements DNS renvoyer. Quant
au serveur, s'il ne gère pas cette extension, il répond au client
avec un ServerHello
n'ayant pas l'extension
dnssec_chain
. Même chose si le serveur accepte
l'extension mais, pour une raison ou pour une autre, n'a pas pu
rassembler tous les enregistrements DNS.
Et pourquoi le client doit-il indiquer le port auquel il voulait se connecter ? Le serveur le connait, non ? Mais ce n'est pas forcément le cas, soit parce qu'il y a eu traduction de port quelque part sur le chemin, soit parce que le client a été redirigé, par exemple via le type d'enregistrement SVCB (RFC 9460). Le port indiqué par le client doit être le port original, avant toute traduction ou redirection.
Revenons à la « chaine », l'ensemble d'enregistrements DNS qui va
permettre la validation DANE. Le format est le format binaire
habituel du DNS, décrit dans le RFC 1035,
section 3.2.1. L'utilisation de ce format, quoiqu'il soit inhabituel
dans le monde TLS, permet, entre autres avantages, l'utilisation des
bibliothèques logicielles DNS existantes. Le RFC recommande
d'inclure dans l'ensemble d'enregistrements une chaine complète, y
compris la racine et incluant donc les clés DNSSEC de celle-ci
(enregistrements DNSKEY), même si le client les connait probablement
déjà. Cela donne par exemple la chaine suivante, pour le serveur
www.example.com
écoutant sur le port 443. Notez
qu'on inclut les signatures (RRSIG) :
_443._tcp.www.example.com
TLSA_443._tcp.www.example.com
TLSA)example.com
. DNSKEYexample.com
DNSKEY)example.com
DSexample.com
DS)com
DNSKEYcom
DNSKEY)com
DScom
DS).
DNSKEY.
DNSKEY)
(L'exemple suppose que
_443._tcp.www.example.com
et
example.com
sont dans la même zone.) Voilà,
avec tous ces enregistrements, le client peut, sans faire appel à un
résolveur DNS validant, vérifier (s'il a confiance dans la clé de la
racine) l'authenticité de l'enregistrement DANE (type TLSA) et donc
le certificat du serveur. De la même façon, si on n'a pas
d'enregistrement TLSA, le serveur peut envoyer les preuves de
non-existence (enregistrements NSEC ou NSEC3). Bon, évidemment, dans
ce cas, c'est moins utile, autant ne pas gérer l'extension
dnssec_chain
… Face à ce déni, le client TLS
n'aurait plus qu'à se rabattre sur une autre méthode
d'authentification.
Le serveur TLS construit la chaine des enregistrements comme il veut, mais pour l'aider, la section 3 du RFC fournit une procédure possible, à partir d'interrogations du résolveur de ce serveur. Une autre possibilité, plus simple pour le serveur, serait d'utiliser les requêtes chainées du RFC 7901 (mais qui n'ont pas l'air très souvent déployées aujourd'hui).
Comme tous les enregistrements DNS, ceux inclus dans l'extension
TLS dnssec_chain
ont une durée de vie. Et les signatures DNSSEC ont
une période de validité (en général plus longue que la durée de
vie). Le serveur TLS qui construit l'ensemble d'enregistrements qu'il
va renvoyer peut donc mémoriser cet ensemble, dans la limite du
TTL et de l'expiration
des signatures DNSSEC. Le client TLS peut lui aussi mémoriser ces
informations, dans les mêmes conditions.
On a vu plus haut que les données de l'extension TLS incluaient
un ExtSupportLifetime
qui indiquait combien de
temps le client pouvait s'attendre à ce que le serveur TLS continue
à gérer cette extension. Car le client peut épingler
(pinning) cette information. Cela permet de
détecter certaines attaques (« ce serveur gérait l'extension
dnssec_chain
et ce n'est maintenant plus le
cas ; je soupçonne un détournement vers un serveur pirate »). Cet
engagement du serveur est analogue au HSTS de HTTPS (RFC 6797) ; dans les deux cas, le serveur s'engage à rester
« sécurisé » pendant un temps minimum (mais pas infini, car on doit
toujours pouvoir changer de politique). À noter que « gérer
l'extension dnssec_chain
» n'est pas la même
chose que « avoir un enregistrement TLSA », on peut accepter
l'extension mais ne pas avoir de données à envoyer (il faudra alors
envoyer la preuve de non-existence mentionnée plus haut). Bien sûr,
le client TLS est libre de sa politique. Il peut aussi décider
d'exiger systématiquement l'acceptation de l'extension TLS
dnssec_chain
(ce qui, aujourd'hui, n'est
réaliste que si on ne parle qu'à un petit nombre de serveurs).
Si un serveur gérait l'extension
dnssec_chain
mais souhaite arrêter, il doit
bien calculer son coup : d'abord réduire
ExtSupportLifetime
à zéro puis attendre que
la durée annoncée dans le précédent
ExtSupportLifetime
soit écoulée, afin que tous
les clients aient arrêté de l'épingler comme serveur à
dnssec_chain
. Il peut alors proprement stopper
l'extension (cf. section 10 du RFC).
Un petit problème se pose si on fait héberger le serveur TLS chez
un tiers. Par exemple, imaginons que le titulaire du domaine
boulanger.example
ait un serveur chez la
société JVL (Je Vous Loge) et que
server.boulanger.example
soit un alias
(enregistrement de type CNAME) vers
clients.jvl.example
. Le client TLS va envoyer
server.boulanger.example
comme SNI. Il ne sera
pas pratique du tout pour JVL de coordonner la chaine
d'enregistrements DNS et le certificat. Il est donc conseillé que
l'enregistrement TLSA du client soit lui aussi un alias vers un
enregistrement TLSA de l'hébergeur. Cela pourrait donner (sans les
signatures, pour simplifier la liste) :
server.boulanger.example
CNAME (vers
clients.jvl.example
)_443._tcp.server.boulanger.example
CNAME
(vers _dane443.node1.jvl.example
)clients.jvl.example
AAAA_dane443.node1.jvl.example
TLSA
Les deux premiers enregistrements sont gérés par l'hébergé, les deux
derniers par l'hébergeur. (La section 9 du RFC explique pourquoi
l'enregistrement TLSA de l'hébergeur n'est pas en
_443._tcp
…)
Comme dit plus haut, le client TLS est maitre de sa politique : il peut exiger l'extension TLS, il peut l'accepter si elle existe, il peut l'ignorer. Si l'authentification par DANE échoue mais que celle par PKIX réussit, ou le contraire, c'est au client TLS de décider, en fonction de sa politique, ce qu'il fait.
Est-ce que le serveur TLS qui gère cette extension doit envoyer la chaine complète de certificats ? S'il veut pouvoir également être identifié avec PKIX, oui. Si non, s'il se contente de DANE et plus précisément de DANE-EE ou DANE-TA (ces deux termes sont définis dans le RFC 7218), il peut envoyer uniquement le certificat du serveur (pour DANE-EE).
Question mise en œuvre, l'excellente bibliothèque ldns a (je n'ai pas testé…) tout ce qu'il faut pour générer et tester ces chaines d'enregistrements DNS. Si vous voulez développer du code pour gérer cette extension, l'annexe A du RFC contient des vecteurs de test qui vous seront probablement bien utiles.
Date de publication du RFC : Août 2021
Auteur(s) du RFC : E. Vyncke (Cisco), K. Chittimaneni (Square), M. Kaeo (Double Shot Security), E. Rey (ERNW)
Pour information
Réalisé dans le cadre du groupe de travail IETF opsec
Première rédaction de cet article le 7 septembre 2021
Tous les gens qui gèrent des réseaux IPv4 savent comment les sécuriser (normalement…). Et pour IPv6 ? Si ce n'est pas un protocole radicalement différent, il a quand même quelques particularités de sécurité. Ce RFC donne des conseils pratiques et concrets sur la sécurisation de réseaux IPv6. (Je le répète, mais c'est juste une nouvelle version d'IP : l'essentiel du travail de sécurisation est le même en v4 et en v6.)
Les différences entre IPv4 et IPv6 sont subtiles (mais, en sécurité, la subtilité compte, des petits détails peuvent avoir de grandes conséquences). Ceci dit, le RFC a tendance à les exagérer. Par exemple, il explique que la résolution d'adresses de couche 3 en adresses de couche 2 ne se fait plus avec ARP (RFC 826) mais avec NDP (RFC 4861). C'est vrai, mais les deux protocoles fonctionnent de manière très proche et ont à peu près les mêmes propriétés de sécurité (en l'occurrence, aucune sécurité). Plus sérieusement, le remplacement d'un champ d'options de taille variable dans IPv4 par les en-têtes d'extension d'IPv6 a certainement des conséquences pour la sécurité. Le RFC cite aussi le cas du NAPT (Network Address and Port Translation, RFC 2663). Souvent appelée à tort NAT (car elle ne traduit pas que l'adresse mais également le port), cette technique est très utilisée en IPv4. En IPv6, on ne fait pas de traduction ou bien on fait du vrai NAT, plus précisement du NPT (Network Prefix Translation, RFC 6296) puisqu'on change le préfixe de l'adresse. La phrase souvent entendue « en IPv6, il n'y a pas de NAT » est doublement fausse : c'est en IPv4 qu'on n'utilise pas le NAT, mais le NAPT, et on peut faire de la traduction d'adresses en IPv6 si on veut. NAT, NAPT ou NPT ne sont évidemment pas des techniques de sécurité mais elles peuvent avoir des conséquences pour la sécurité. Par exemple, toute traduction d'adresse va complexifier le réseau, ce qui introduit des risques. Autre facteur nouveau lié à IPv6 : la plupart des réseaux vont devoir faire de l'IPv4 et de l'IPv6 et les différentes techniques de coexistence de ces deux versions peuvent avoir leur propre impact sur la sécurité (RFC 4942).
La section 2 du RFC est le gros morceau du RFC ; c'est une longue liste de points à garder en tête quand on s'occupe de la sécurité d'un réseau IPv6. Je ne vais pas tous les citer, mais en mentionner quelques-uns qui donnent une bonne idée du travail du ou de la responsable de la sécurité.
D'abord, l'adressage. Aux débuts d'IPv6, l'idée était que les spécificités d'IPv6 comme le SLAAC allaient rendre la renumérotation des réseaux tellement triviale qu'on pourrait changer les adresses souvent. En pratique, ce n'est pas tellement le cas (RFC 7010) et il faut donc prêter attention à la conception de son adressage : on aura du mal à en changer. Heureusement, c'est beaucoup plus facile qu'en IPv4 : l'abondance d'adresses dispense des astuces utilisées en IPv4 pour essayer d'économiser chaque adresse. Par exemple, on peut donner à tous ses sous-réseaux la même longueur de préfixe, quelle que soit leur taille, ce qui simplifie bien les choses (le RFC 6177 est une lecture instructive). Comme en IPv4, les adresses peuvent être liées au fournisseur d'accès (PA, Provider Allocated) ou bien obtenues indépendamment de ce fournisseur (PI, Provider Independent). Le RFC 7381 discute ce cas. Cela peut avoir des conséquences de sécurité. Par exemple, les adresses PA sont en dernier ressort gérées par l'opérateur à qui il faudra peut-être demander certaines actions. Et ces adresses PA peuvent pousser à utiliser la traduction d'adresses, qui augmente la complexité. (D'un autre côté, les adresses PI sont plus difficiles à obtenir, surtout pour une petite organisation.) Ah, et puis, pour les machines terminales, le RFC 7934 rappelle à juste titre qu'il faut autoriser ces machines à avoir plusieurs adresses (il n'y a pas de pénurie en IPv6). Une des raisons pour lesquelles une machine peut avoir besoin de plusieurs adresses est la protection de la vie privée (RFC 8981).
Les gens habitués à IPv4 demandent souvent quel est l'équivalent du RFC 1918 en IPv6. D'abord, de telles adresses privées ne sont pas nécessaires en IPv6, où on ne manque pas d'adresses. Mais si on veut, on peut utiliser les ULA (Unique Local Addresses) du RFC 4193. Par rapport aux adresses privées du RFC 1918, elles ont le gros avantage d'être uniques, et donc d'éviter toute collision, par exemple quand deux organisations fusionnent leurs réseaux locaux. Le RFC 4864 décrit comment ces ULA peuvent être utilisés pour la sécurité.
Au moment de concevoir l'adressage, un choix important est celui de donner ou non des adresses IP stables aux machines. (Ces adresses stables peuvent leur être transmises par une configuration statique, ou bien en DHCP.) Les adresses stables facilitent la gestion du réseau mais peuvent faciliter le balayage effectué par un attaquant pendant une reconnaissance préalable. Contrairement à ce qu'on lit parfois encore, ce balayage est tout à fait possible en IPv6, malgré la taille de l'espace d'adressage, même s'il est plus difficile qu'en IPv4. Le RFC 7707 explique les techniques de balayage IPv6 possibles. Même chose dans l'article « Mapping the Great Void ». Bref, on ne peut pas compter sur la STO et donc, adresses stables ou pas, les machines connectées à l'Internet peuvent être détectées, et doivent donc être défendues.
Cela n'empêche pas de protéger la vie privée des utilisateurs en utilisant des adresses temporaires pour contacter l'extérieur. L'ancien système de configuration des adresses par SLAAC était mauvais pour la vie privée puisqu'il utilisait comme partie locale de l'adresse (l'IID, Interface IDentifier) l'adresse MAC, ce qui permettait d'identifier une machine même après qu'elle ait changé de réseau. Ce système est abandonné depuis longtemps (RFC 8064) au profit d'adresses aléatoires (RFC 8981). Ceci dit, ces adresses aléatoires posent un autre problème : comme elles changent fréquemment, elles ne permettent pas à l'administrateur réseau de suivre les activités d'une machine (c'est bien leur but), et on ne peut plus utiliser de mécanismes comme les ACL. Une solution possible est d'utiliser des adresses qui sont stables pour un réseau donné mais qui ne permettent pas de suivre une machine quand elle change de réseau, les adresses du RFC 7217. C'est dans beaucoup de cas le meilleur compromis. (Le RFC 7721 contient des détails sur les questions de sécurité liées aux adresses IP.)
On peut aussi couper SLAAC (cela peut se faire à distance avec des annonces RA portant le bit M) et n'utiliser que DHCP mais toutes les machines terminales n'ont pas un client DHCP.
En parlant de DHCP, lui aussi pose des problèmes de vie privée (décrits dans le RFC 7824) et si on veut être discret, il est recommandé d'utiliser le profil restreint défini dans le RFC 7844.
Après les adresses, la question des en-têtes d'extension, un concept nouveau d'IPv6. En IPv4, les options sont placées dans l'en-tête, qui a une longueur variable, ce qui complique et ralentit son analyse. En IPv6, l'en-tête a une longueur fixe (40 octets) mais il peut être suivi d'un nombre quelconque d'en-têtes d'extension (RFC 8200, section 4). Si on veut accéder à l'information de couche 4, par exemple pour trouver le port TCP, il faut suivre toute la chaîne d'en-têtes. Ce système était conçu pour être plus facile et plus rapide à analyser que celui d'IPv4, mais à l'usage, ce n'est pas le cas (le RFC estime même que le système d'IPv6 est pire). En partie pour cette raison, certains nœuds intermédiaires jettent tous les paquets IPv6 contenant des en-têtes d'extension (RFC 7872, RFC 9288). D'autres croient que la couche Transport suit immédiatement l'en-tête, sans tenir compte de la possibilité qu'il y ait un en-tête d'extension, ce qui fausse leur analyse. L'en-tête d'extension Hop-by-hop options était particulièrement problématique, car devant être traité par tous les routeurs (le RFC 8200 a adouci cette règle).
Pour faciliter la tâche des pare-feux, plusieurs règles ont été ajoutées aux en-têtes d'extension depuis les débuts d'IPv6 : par exemple le premier fragment d'un datagramme doit contenir la totalité des en-têtes d'extension.
À l'interface
d'IPv6 et de la couche 2, on
trouve quelques problèmes supplémentaires. D'abord, concernant la
résolution d'adresses IP en adresses
MAC, pour laquelle IPv6 utilise le protocole NDP
(Neighbor Discovery Protocol, RFC 4861). NDP partage avec ARP un problème fondamental :
n'importe quelle machine du réseau local peut répondre ce qu'elle
veut. Par exemple, si une machine demande « qui est
2001:db8:1::23:42:61
? », toutes les machines
locales peuvent techniquement répondre « c'est moi » et donner leur
adresse MAC. Ce problème et quelques autres analogues sont
documentés dans les RFC 3756 et RFC 6583. DHCP pose le même genre de problèmes,
toute machine peut se prétendre serveur DHCP et répondre aux
requêtes DHCP à la place du serveur légitime. Pour se prémunir
contre les attaques faites par des machines envoyant de faux
RA, on peut aussi isoler les machines, par
exemple en donnant un /64 à chacune, ou bien en configurant
commutateurs ou points d'accès Wifi pour bloquer les communications
de machine terminale à machine terminale (celles qui qui ne sont pas
destinées au routeur). Le RFC recommande le RA
guard du RFC 6105.
Et sur les mobiles ? Un lien 3GPP est un lien point-à-point, l'ordiphone qui est à un bout ne peut donc pas parler aux autres ordiphones, même utilisant la même base. On ne peut parler qu'au routeur (GGSN - GPRS Gateway Support Node, ou bien PGW - Packet GateWay). Pour la même raison, il n'y a pas de résolution des adresses IP et donc pas de risque liés à cette résolution. Ce mécanisme empêche un grand nombre des attaques liées à NDP. Si vous voulez en apprendre plus à ce sujet, il faut lire le RFC 6459.
Le
multicast peut être
dangereux sur un réseau local, puisqu'il permet d'écrire à des
machines qui n'ont rien demandé. Certains réseaux WiFi bloquent le
multicast. En IPv6, cela empêche des actions
néfastes comme de pinguer
ff01::1
(l'adresse multicast
qui désigne toutes les machines du réseau local), mais cela bloque
aussi des protocoles légitimes comme mDNS
(RFC 6762).
Compte-tenu de la vulnérabilité du réseau local, et des risques associés, il a souvent été proposé de sécuriser l'accès à celui-ci et IPv6 dispose d'un protocole pour cela, SEND, normalisé dans le RFC 3971, combiné avec les CGA du RFC 3972. Très difficile à configurer, SEND n'a connu aucun succès, à part dans quelques environnements ultra-sécurisés. On ne peut clairement pas compter dessus.
Voyons maintenant la sécurité du plan de contrôle : les routeurs et le routage (RFC 6192). Les problèmes de sécurité sont quasiment les mêmes en IPv4 et en IPv6, la principale différence étant le mécanisme d'authentification pour OSPF. Un routeur moderne typique sépare nettement le plan de contrôle (là où on fait tourner les protocoles comme OSPF, mais aussi les protocoles de gestion comme SSH qui sert à configurer le routeur) et le plan de transmission, là où se fait la transmission effective des paquets. Le second doit être très rapide, car il fonctionne en temps réel. Il utilise en général du matériel spécialisé, alors que le plan de contrôle est la plupart du temps mis en œuvre avec un processeur généraliste, et un système d'exploitation plus classique. Pas toujours très rapide, il peut être submergé par un envoi massif de paquets. Le plan de contrôle ne gère que les paquets, relativement peu nombreux, qui viennent du routeur ou bien qui y arrivent, le plan de transmission gérant les innombrables paquets que le routeur transmet, sans les garder pour lui. Chaque paquet entrant est reçu sur l'interface d'entrée, le routeur consulte une table qui lui dit quel est le routeur suivant pour ce préfixe IP, puis il envoie ce paquet sur l'interface de sortie. Et le tout très vite.
Notre RFC conseille donc de protéger le plan de contrôle en bloquant le plus tôt possible les paquets anormaux, comme des paquets OSPF qui ne viendraient pas d'une adresse locale au lien, ou les paquets BGP qui ne viennent pas d'un voisin connu. (Mais, bien sûr, il faut laisser passer l'ICMP, essentiel au débogage et à bien d'autres fonctions.) Pour les protocoles de gestion, il est prudent de jeter les paquets qui ne viennent pas du réseau d'administration. (Tout ceci est commun à IPv4 et IPv6.)
Protéger le plan de contrôle, c'est bien, mais il faut aussi protéger le protocole de routage. Pour BGP, c'est pareil qu'en IPv4 (lisez le RFC 7454). Mais OSPF est une autre histoire. La norme OSPFv3 (RFC 4552) comptait à l'origine exclusivement sur IPsec, dont on espérait qu'il serait largement mis en œuvre et déployé. Cela n'a pas été le cas (le RFC 8504 a d'ailleurs supprimé cette obligation d'IPsec dans IPv6, obligation qui était purement théorique). Le RFC 7166 a pris acte de l'échec d'IPsec en créant un mécanisme d'authentification pour OSPFv3. Notre RFC recommande évidemment de l'utiliser.
Sinon, les pratiques classiques de sécurité du routage tiennent toujours. Ne pas accepter les routes « bogons » (conseil qui n'est plus valable en IPv4, où tout l'espace d'adressage a été alloué), celles pour des adresses réservées (RFC 8190), etc.
Pas de sécurité sans surveillance (c'est beau comme du Ciotti, ça) et journalisation. En IPv6 comme en IPv4, des techniques comme IPFIX (RFC 7011), SNMP (RFC 4293), etc sont indispensables. Comme en IPv4, on demande à son pare-feu, son serveur RADIUS (RFC 2866) et à d'autres équipements de journaliser les évènements intéressants. À juste titre, et même si ça va chagriner les partisans de la surveillance massive, le RFC rappelle que, bien que tout cela soit très utile pour la sécurité, c'est dangereux pour la vie privée, et que c'est souvent, et heureusement, encadré par des lois comme le RGPD. Administrateur réseaux, ne journalise pas tout, pense à tes responsabilités morales et légales !
Bon, mais cela, c'est commun à IPv4 et IPv6. Qu'est-ce qui est spécifique à IPv6 ? Il y a le format textuel canonique des adresses, normalisé dans le RFC 5952, qui permet de chercher une adresse sans ambiguité. Et la mémoire des correspondances adresses IP adresses MAC dans les routeurs ? Elle est très utile à enregistrer, elle existe aussi en IPv4, mais en IPv6 elle est plus dynamique, surtout si on utilise les adresses favorables à la vie privée du RFC 8981. Le RFC recommande de la récupérer sur le routeur au moins une fois toutes les 30 secondes. Et le journal du serveur DHCP ? Attention, en IPv6, du fait de l'existence de trois mécanismes d'allocation d'adresses (DHCP, SLAAC et statique) au lieu de deux en IPv4, le journal du serveur DHCP ne suffit pas. Et puis il ne contiendra pas l'adresse MAC mais un identificateur choisi par le client, qui peut ne rien vous dire. (Ceci dit, avec les machines qui changent leur adresse MAC, DHCPv4 a un problème du même genre.) En résumé, associer une adresse IP à une machine risque d'être plus difficile qu'en IPv4.
Une autre spécificité d'IPv6 est l'existence de nombreuses technologies de transition entre les deux protocoles, technologies qui apportent leurs propres problèmes de sécurité (RFC 4942). Normalement, elles n'auraient dû être que temporaires, le temps que tout le monde soit passé à IPv6 mais, comme vous le savez, la lenteur du déploiement fait qu'on va devoir les supporter longtemps, les réseaux purement IPv6 et qui ne communiquent qu'avec d'autres réseaux IPv6 étant une petite minorité. La technique de coexistence la plus fréquente est la double pile, où chaque machine a à la fois IPv4 et IPv6. C'est la plus simple et la plus propre, le trafic de chaque version du protocole IP étant natif (pas de tunnel). Avec la double pile, l'arrivée d'IPv6 n'affecte pas du tout IPv4. D'un autre côté, il faut donc gérer deux versions du protocole. (Anecdote personnelle : quand j'ai commencé dans le métier, IP était très loin de la domination mondiale, et il était normal, sur un réseau local, de devoir gérer cinq ou six protocoles très différents. Prétendre que ce serait une tâche insurmontable de gérer deux versions du même protocole, c'est considérer les administrateurs réseaux comme très paresseux ou très incompétents.) L'important est que les politiques soient cohérentes, afin d'éviter, par exemple, que le port 443 soit autorisé en IPv4 mais bloqué en IPv6, ou le contraire. (C'est parfois assez difficile à réaliser sans une stricte discipline : beaucoup de pare-feux n'ont pas de mécanisme simple pour indiquer une politique indépendante de la version du protocole IP.)
Notez que certains réseaux peuvent être « double-pile » sans que l'administrateur réseaux l'ait choisi, ou en soit conscient, si certaines machines ont IPv6 activé par défaut (ce qui est fréquent et justifié). Des attaques peuvent donc être menées via l'adresse locale au lien même si aucun routeur du réseau ne route IPv6.
Mais les plus gros problèmes de sécurité liés aux techniques de coexistence/transition viennent des tunnels. Le RFC 6169 détaille les conséquences des tunnels pour la sécurité. Sauf s'ils sont protégés par IPsec ou une technique équivalente, les tunnels permettent bien des choses qui facilitent le contournement des mesures de sécurité. Pendant longtemps, l'interconnexion entre réseaux IPv6 isolés se faisait via des tunnels, et cela a contribué aux légendes comme quoi IPv6 posait des problèmes de sécurité. Aujourd'hui, ces tunnels sont moins nécessaires (sauf si un réseau IPv6 n'est connecté que par des opérateurs archaïques qui n'ont qu'IPv4).
Les tunnels les plus dangereux (mais aussi les plus pratiques) sont les tunnels automatiques, montés sans configuration explicite. Le RFC suggère donc de les filtrer sur le pare-feu du réseau, en bloquant le protocole IP 41 (ISATAP - RFC 5214, 6to4 - RFC 3056, mais aussi 6rd - RFC 5969 - qui, lui, ne rentre pas vraiment dans la catégorie « automatique »), le protocole IP 47 (ce qui bloque GRE, qui n'est pas non plus un protocole « automatique ») et le port UDP 3544, pour neutraliser Teredo - RFC 4380. D'ailleurs, le RFC rappelle plus loin que les tunnels statiques (RFC 2529), utilisant par exemple GRE (RFC 2784), sont plus sûrs (mais IPsec ou équivalent reste recommandé). 6to4 et Teredo sont de toute façon très déconseillés aujourd'hui (RFC 7526 et RFC 3964).
Et les mécanismes de traduction d'adresses ? On peut en effet traduire des adresses IPv4 en IPv4 (le traditionnel NAT, qui est plutôt du NAPT en pratique, puisqu'il traduit aussi le port), ce qui est parfois présenté à tort comme une fonction de sécurité, mais on peut aussi traduire de l'IPv4 en IPv6 ou bien de l'IPv6 en IPv6. Le partage d'adresses que permettent certains usages de la traduction (par exemple le CGNAT) ouvre des problèmes de sécurité bien connus. Les techniques de traduction d'IPv4 en IPv6 comme NAT64 - RFC 6146 ou 464XLAT - RFC 6877 apportent également quelques problèmes, décrits dans leurs RFC.
J'ai parlé plus haut du fait que les systèmes d'exploitation modernes ont IPv6 et l'activent par défaut. Cela implique de sécuriser les machines contre les accès non voulus faits avec IPv6. Du classique, rien de spécifique à IPv6 à part le fait que certains administrateurs système risqueraient de sécuriser les accès via IPv4 (avec un pare-feu intégré au système d'exploitation, par exemple) en oubliant de le faire également en IPv6.
Tous les conseils donnés jusqu'à présent dans cette section 2 du RFC étaient communs à tous les réseaux IPv6. Les sections suivantes s'attaquent à des types de réseau spécifiques à certaines catégories d'utilisateurs. D'abord (section 3), les « entreprises » (en fait, toutes les organisations - RFC 7381, pas uniquement les entreprises capitalistes privées, comme le terme étatsunien enterprise pourrait le faire penser). Le RFC contient quelques conseils de sécurité, proches de ce qui se fait en IPv4, du genre « bloquer les services qu'on n'utilise pas ». (Et il y a aussi le conseil plus évident : bloquer les paquets entrants qui ont une adresses IP source interne et les paquets sortants qui ont une adresse IP source externe.)
Et pour les divers opérateurs (section 4 du RFC) ? C'est plus délicat car ils ne peuvent pas, contrairement aux organisations, bloquer ce qu'ils ne veulent pas (sauf à violer la neutralité, ce qui est mal). Le RFC donne des conseils de sécurisation BGP (identiques à ceux d'IPv4) et RTBH (RFC 5635). Il contient également une section sur l'« interception légale » (le terme politiquement correct pour les écoutes et la surveillance). Légalement, les exigences (et les problèmes qu'elles posent) sont les mêmes en IPv4 et en IPv6. En IPv4, le partage d'adresses, pratique très répandue, complique sérieusement la tâche des opérateurs quand ils reçoivent un ordre d'identifier le titulaire de telle ou telle adresse IP (RFC 6269). En IPv6, en théorie, la situation est meilleure pour la surveillance, une adresse IP n'étant pas partagée. Par contre, l'utilisateur peut souvent faire varier son adresse au sein d'un préfixe /64.
Quand au réseau de l'utilisateur final, il fait l'objet de la section 5. Il n'y a pas actuellement de RFC définitif sur la délicate question de la sécurité de la maison de M. Michu. Notamment, les RFC 6092 et RFC 7084 ne prennent pas position sur la question très sensible de savoir si les routeurs/pare-feux d'entrée de ce réseau devraient bloquer par défaut les connexions entrantes. La sécurité plaiderait en ce sens mais ça casserait le principe de bout en bout.
Voilà, nous avons terminé cette revue du long RFC. Je résumerai mon opinion personnelle en disant : pour la plupart des questions de sécurité, les ressemblances entre IPv4 et IPv6 l'emportent sur les différences. La sécurité n'est donc pas une bonne raison de retarder la migration si nécessaire vers IPv6. J'ai développé cette idée dans divers exposés et articles.
Date de publication du RFC : Septembre 2021
Auteur(s) du RFC : F. Gont (SI6 Networks), N. Hilliard (INEX), G. Doering (SpaceNet AG), W. Kumari (Google), G. Huston (APNIC), W. Liu (Huawei Technologies)
Pour information
Réalisé dans le cadre du groupe de travail IETF v6ops
Première rédaction de cet article le 20 septembre 2021
IPv6 souffre de réseaux mal gérés qui se permettent de jeter les paquets ayant des caractéristiques normales, mais qui déplaisent à certains équipements réseau. C'est par exemple le cas des paquets utilisant les en-têtes d'extension. Pourquoi les paquets utilisant ces en-têtes sont-ils souvent jetés ?
Ces en-têtes d'extension (qui n'ont pas d'équivalent en IPv4) sont normalisés dans le RFC 8200, section 4. Ils servent à la fois à des fonctions de base d'IPv6 (comme la fragmentation) et à étendre le protocole si nécessaire. Mais, en pratique, on observe trop souvent que les paquets IPv6 utilisant ces en-têtes d'extension ne passent pas sur certains réseaux. Ce RFC vise à étudier pourquoi, et à expliquer les conséquences. Comme toujours quand on explique un phénomène (la délinquance, par exemple), des gens vont comprendre de travers et croire qu'on justifie le phénomène en question (« expliquer, c’est déjà vouloir un peu excuser », avait stupidement dit un ancien premier ministre français, à propos du terrorisme djihadiste). Le cas est d'autant plus fréquent qu'en français, « comprendre » est ambigu, car il peut désigner l'explication (qui est utile aussi bien aux partisans qu'aux adversaires d'un phénomène) que la justification. Bref, ce RFC explique pourquoi des paquets innocents sont détruits, il ne dit pas qu'il faut continuer à le faire !
La section 4 du RFC explique la différence entre l'en-tête d'un paquet IPv6 et celui d'un paquet de l'ancienne version. Dans les deux versions, le problème est de placer des options dans les en-têtes. Le gros changement est l'utilisation par IPv6 d'un en-tête de taille fixe (40 octets), suivi d'une liste chainée d'en-têtes d'extension permettant d'encoder ces options. Au contraire, IPv4 avait un en-tête de taille variable (entre 20 et 60 octets), avec un champ indiquant sa longueur, champ qu'il fallait lire avant d'accéder à l'en-tête. Le mécanisme d'IPv4 n'était ni pratique, ni rapide, mais celui d'IPv6 n'est pas plus satisfaisant : il faut lire toute la liste chainée (dont la longueur est quelconque et non limitée) si on veut accéder aux informations au-dessus de la couche 3. Ce n'est pas un problème pour les routeurs, à qui la couche 3 suffit (à part le cas de l'en-tête Hop-by-hop options), mais c'est ennuyeux pour les autres boitiers situés sur le chemin, comme les pare-feux. Les évolutions récentes d'IPv6 ont aidé, comme l'obligation que tous les en-têtes d'extension soient dans le premier fragment, si le datagramme est fragmenté (RFC 7112), ou comme l'imposition d'un format commun aux futurs en-têtes d'extension (dans le RFC 6564), mais l'analyse de la liste des en-têtes d'extension reste un travail pénible pour les machines.
Le problème est identifié depuis longtemps. Il est analysé dans
l'Internet-Draft
draft-taylor-v6ops-fragdrop
,
dans draft-wkumari-long-headers
,
dans draft-kampanakis-6man-ipv6-eh-parsing
mais aussi dans des RFC, le RFC 7113, le
RFC 8900 et le RFC 9288. Cela a mené à plusieurs changements
dans la norme, je vous renvoie aux RFC 5722,
RFC 7045, RFC 8021,
etc. Plusieurs changements ont été intégrés dans la dernière
révision du RFC principal de la norme, le RFC 8200. Ainsi, celui-ci dispense désormais les routeurs de
chercher un éventuel en-tête d'extension Hop-by-hop
options.
Parallèlement à ces analyses du protocole, diverses études ont porté sur le phénomène des paquets jetés s'ils contenaient des en-têtes d'extension. Cela a été documenté dans « Discovering Path MTU black holes on the Internet using RIPE Atlas », dans « IPv6 Extension Headers in the Real World v2.0 », également dans « Dealing with IPv6 fragmentation in the DNS » et « Measurement of IPv6 Extension Header Support » ainsi que dans le RFC 7872.
Comment est-ce que les équipements intermédiaires du réseau fonctionnent, et pourquoi est-ce que les en-têtes d'extension IPv6 peuvent parfois les défriser ? La section 6 du RFC rappelle l'architecture de ces machines. Un routeur de cœur de réseau est très différent de l'ordinateur de base, avec son processeur généraliste et sa mémoire de grande taille, ce qui le rend lent, mais souple. Au contraire, la transmission des paquets par le routeur est en général faite par du matériel spécialisé, ASIC ou NPU. (Vous pouvez consulter des explications dans l'exposé fait à l'IETF « Modern Router Architecture for Protocol Designers » et dans l'article « Modern router architecture and IPv6 ».) Avant de transmettre un paquet reçu sur l'interface de sortie, le routeur doit trouver quelle interface utiliser. Une méthode courante est de prendre les N premiers octets du paquet et de les traiter dans une TCAM ou une RLDRAM, où se fera la détermination du saut suivant (et donc de l'interface de sortie). Le choix du nombre N est crucial : plus il est petit, plus le routeur pourra traiter de paquets, mais moins il aura d'information sur le paquet. (En pratique, on observe des N allant de 192 à 384 octets.) Du fait qu'un routeur se limite normalement à la couche Réseau, et que l'en-tête IPv6 a une taille fixe, envoyer simplement les 40 octets de cet en-tête devrait suffire. Mais lisez plus loin : certains routeurs ou autres équipements intermédiaires sont configurés pour regarder au-delà, et voudraient des informations de la couche Transport, plus dure à atteindre.
Une solution, pour les routeurs qui ne lisent qu'un nombre limité d'octets avant de prendre une décision, est de lire les en-têtes d'extension un par un, et de réinjecter le reste du paquet dans le dispositif de traitement. Ça se nomme la recirculation, c'est plus lent, mais ça marche quelle que soit la longueur de la chaîne des en-têtes d'extension.
Comme le routeur comprend toujours un processeur généraliste (ne serait-ce que pour faire tourner les fonctions de contrôle comme SSH et les protocoles de routage), une autre solution serait d'envoyer à ce processeur les paquets « compliqués » (mais lire le RFC 6192 d'abord). L'énorme différence de performance entre le processeur généraliste et les circuits spécialisés fait que ce ne serait pas une solution réaliste sauf pour un trafic très modéré. (Le RFC note que certains routeurs ont trois dispositifs de traitement des paquets, et pas seulement deux comme présenté plus haut : outre le processeur généraliste, qui ne leur sert qu'aux fonctions de contrôle, et des circuits matériels spécialisés, ils ont un dispositif logiciel de transmission des paquets. Plus souple que les circuits spécialisés, il n'est pas forcément plus rapide que le processeur généraliste mais, au moins, il n'interfère pas avec les fonctions de contrôle.)
Bon, on l'a déjà dit, mais cela vaut la peine de le répéter : à part le cas (agaçant) de l'en-tête Hop-by-hop options, un routeur, équipement de couche 3 n'a normalement aucun besoin d'aller regarder tous les en-têtes d'extension (qui sont presque tous pour la machine terminale de destination uniquement). Encore moins de faire du DPI et d'aller chercher des informations dans les couches 4 à 7. Mais la section 7 du RFC explique pourquoi certains routeurs le font quand même (il s'agit d'une description de leurs pratiques, pas une approbation ; beaucoup des raisons données sont mauvaises).
D'abord, la répartition de charge et l'ECMP. Si un routeur peut faire passer un paquet via deux interfaces différentes, laquelle choisir ? Alterner (un coup à gauche, un coup à droite) augmenterait la probabilité d'une arrivée des paquets dans le désordre. On préfère la prédictabilité : tous les paquets d'un même flot doivent suivre le même chemin. Comment déterminer le « flot », notion parfois un peu floue ? En restant dans les couches basses, on peut utiliser uniquement les adresses IP de source et de destination mais elles ne sont pas assez discriminantes. En IPv6, on peut en théorie utiliser l'étiquette de flot (normalisé dans le RFC 6437, ainsi que les RFC 6438 et RFC 7098 pour son utilisation) mais elle n'est pas toujours présente, en partie (encore !) par la faute de middleboxes qui, ignorant ce concept d'étiquette de flot, jetaient les paquets qui en avaient une. (Voir les articles de I. Cunha, « IPv4 vs IPv6 load balancing in Internet routes » et J. Jaeggli, « IPv6 flow label: misuse in hashing ».) En pratique, les systèmes qui ont besoin d'identifier les flots utilisent plutôt une heuristique : le tuple à cinq élements {protocole de transport, adresse IP source, adresse IP destination, port source, port destination}. Ce n'est pas parfait mais, en pratique, cela marche à peu près. On voit que cela nécessite d'accéder à de l'information de la couche Transport, les ports. (Cette heuristique a d'autres limites : par exemple elle ne marche pas si une partie de la couche Transport est chiffrée, comme c'est le cas avec QUIC.)
Autre cas où un boitier intermédiaire ressent le besoin d'accéder aux informations des couches supérieures, les ACL. La plupart des pare-feux permettent d'utiliser comme critère de filtrage les numéros de ports, ce qui va nécessiter d'analyser la chaine des en-têtes d'extension IPv6 pour parvenir à la couche Transport. Entre les pare-feux mal faits qui cherchent les informations de la couche Transport immédiatement après l'en-tête fixe (car le programmeur a tout simplement sauté la partie du RFC qui parlait des en-têtes d'extension) et les difficultés posées par le fait, qu'avant le RFC 6564, les en-têtes d'extension inconnus étaient impossibles à analyser, on voit qu'arriver à la couche Transport n'est pas si facile que ça (le RFC dit qu'il est plus compliqué d'analyser IPv6 qu'IPv4, ce qui est très contestable ; mais ce n'est en effet pas plus facile). Cela peut avoir des conséquences en terme de sécurité, par exemple si un méchant ajoute des en-têtes d'extension dans l'espoir que ses paquets ne soient pas correctement identifiés (cf. RFC 7113).
Autre exemple, le réassemblage des paquets fragmentés nécessite un état (mémoriser les fragments en cours de réassemblage), ce qui peut être fatal à la mémoire du pare-feu, notamment en cas d'attaque. Pour les coûts de ce filtrage, voir l'article d'E. Zack « Firewall Security Assessment and Benchmarking IPv6 Firewall Load Tests ». Cette difficulté peut encourager les pare-feux à jeter les paquets ayant des en-têtes d'extension, surtout si un administrateur réseaux ignorant dit bien fort « ces en-têtes d'extension ne servent à rien de toute façon » (RFC 7872).
Un problème proche est celui de la protection contre les attaques par déni de service. Filtrer avec les adresses IP peut être fait rès efficacement (cf. RFC 5635) mais ne marche pas, par exemple si l'adresse IP source est usurpée. Une des méthodes utilisées alors est de repérer un motif récurrent dans les paquets envoyés par l'attaquant, et de filtrer sur ce motif (vous pouvez lire ici un exemple avec le DNS). Là encore, des informations au-delà de la couche Réseau sont nécessaires. Toujours en sécurité, les NIDS ont aussi besoin de plus d'informations que ce que fournit la couche Réseau.
Et, bien sûr, comme toute complexité, les en-têtes d'extension augmentent les risques de sécurité, d'autant plus qu'il y a un cercle vicieux : comme ils sont peu utilisés, le code qui les traite est peu testé, donc il y a des bogues, donc ils sont peu utilisés, etc.
Donc, un certain nombre de machines intermédiaires, qui assurent des fonctions allant au-delà de ce que fait un « simple » routeur, ont des problèmes avec les en-têtes d'extension IPv6 et choisissent souvent la solution de facilité, qui est de les jeter systématiquement.
Date de publication du RFC : Novembre 2021
Auteur(s) du RFC : A. Morton (AT&T Labs), R. Geib (Deutsche Telekom), L. Ciavattone (AT&T Labs)
Chemin des normes
Réalisé dans le cadre du groupe de travail IETF ippm
Première rédaction de cet article le 10 novembre 2021
Ce RFC
revisite la notion de capacité du RFC 5136 et spécifie une nouvelle métrique,
Type-P-One-way-Max-IP-Capacity
.
Il y a déjà plusieurs RFC qui spécifient rigoureusement des métriques pour déterminer la capacité réseau. (Attention, les publicités des FAI et les articles dans les médias parlent souvent à tort de débit pour désigner ce qui est en fait la capacité, c'est-à-dire le débit maximum.) Une définition rigoureuse est en effet nécessaire car il peut y avoir, pour une même connexion et un même chemin, des capacités différentes selon ce qu'on mesure. Par exemple, est-ce qu'on compte les bits/s à la couche 3 ou à la couche 7 ? Compte-tenu de l'encapsulation et des retransmissions, la seconde est forcément plus basse. Les définitions actuelles sont surtout dans le RFC 5136 et le RFC 3148 mais le travail n'est jamais terminé et ce nouveau RFC vient donc compléter le RFC 5136. Vu l'importance de cette notion de capacité dans les publicités (« Nouveau : accès Premium Gold Plus à 10 Gb/s ! »), il est crucial que des associations de consommateurs ou des régulateurs puissent mesurer et vérifier les annonces.
Une des raisons pour lesquelles le travail sur la définition des métriques ne cesse jamais est que l'Internet évolue. Ainsi, note le RFC, depuis quelques années :
Première métrique défnie dans notre RFC (section 5), et venant
s'ajouter à celles du RFC 5136,
Type-P-One-way-IP-Capacity
. C'est le nombre de
bits par seconde, mesurés en couche 3 et au-dessus (donc incluant
l'en-tête IP, par exemple). Le Type-P
est là
pour rappeler que cette métrique n'a de sens que pour un certain
type de trafic, car des équipements réseau « intelligents » peuvent
faire que, par exemple, les paquets vers le port 22 passent plus vite que ceux pour un
autre port. Type-P
est donc la description
complète des paquets utilisés. À noter que le RFC recommande
d'indiquer la valeur de cette métrique en mégabits par seconde (et
pas en mébibits, on
utilise plutôt les préfixes des télécoms que ceux de
l'informatique).
Deuxième métrique (section 6),
Type-P-One-way-Max-IP-Capacity
, qui indique la
capacité maximum (la différence avec la métrique précédente vient du
fait que certains réseaux peuvent avoir une capacité variable, par
exemple les réseaux radio, oui, je sais, c'est compliqué).
Troisième métrique (section 7),
Type-P-IP-Sender-Bit-Rate
. Elle désigne la
capacité de l'émetteur à envoyer des bits. En effet, lors d'une
mesure, le goulet d'étranglement peut être l'expéditeur et on croit
alors que le réseau a une capacité inférieure à ce qu'elle est
réellement.
La section 8 se penche sur la mesure effective de ces valeurs. Il faut une mesure active (envoi de bits sur le réseau uniquement pour faire la mesure), et elle possiblement très perturbatrice puisqu'on va chercher à remplir les tuyaux le plus possible. Le RFC inclut un algorithme d'ajustement du trafic mais qui n'est pas un vrai algorithme de contrôle de congestion. Le pseudo-code de cet algorithme est dans l'annexe A.
La mesure est bidirectionnelle (envoyeur et récepteur doivent
coopérer) même si la métrique est unidirectionnelle (le
One-Way
dans le nom). Le RFC recommande de la
faire sur UDP
(attention au RFC 8085, UDP n'ayant pas de
contrôle de congestion propre, c'est à l'application de mesure de
faire attention à ne pas écrouler le réseau).
Jouons maintenant un peu avec une mise en œuvre de ce RFC, le
programme udpst, sur
deux machines Arch Linux (un
Raspberry Pi 1, donc avec un réseau très
lent) et Debian, sur le même
commutateur
. On l'installe :
% git clone https://github.com/BroadbandForum/obudpst.git % cd obudpst % cmake . % make
On peut alors lancer le serveur :
% ./udpst UDP Speed Test Software Ver: 7.2.1, Protocol Ver: 8, Built: Sep 28 2021 15:46:40 Mode: Server, Jumbo Datagrams: Enabled, Authentication: Available, sendmmsg syscall: Available
Et le client, le Raspberry Pi :
% ./udpst -u 2001:db8:fafa:35::1 UDP Speed Test Software Ver: 7.2.1, Protocol Ver: 8, Built: Sep 28 2021 18:25:23 Mode: Client, Jumbo Datagrams: Enabled, Authentication: Available, sendmmsg syscall: Available Upstream Test Interval(sec): 10, DelayVar Thresholds(ms): 30-90 [RTT], Trial Interval(ms): 50, Ignore OoO/Dup: Disabled, SendingRate Index: <Auto>, Congestion Threshold: 3, High-Speed Delta: 10, SeqError Threshold: 10, IPv6 TClass: 0 ... Sub-Interval[10](sec): 10, Delivered(%): 100.00, Loss/OoO/Dup: 0/0/0, OWDVar(ms): 2/8/18, RTTVar(ms): 2-16, Mbps(L3/IP): 65.73 Upstream Summary Delivered(%): 100.00, Loss/OoO/Dup: 0/0/0, OWDVar(ms): 0/3/23, RTTVar(ms): 0-16, Mbps(L3/IP): 49.87 Upstream Minimum One-Way Delay(ms): 2 [w/clock difference], Round-Trip Time(ms): 1 Upstream Maximum Mbps(L3/IP): 67.29, Mbps(L2/Eth): 67.45, Mbps(L1/Eth): 67.63, Mbps(L1/Eth+VLAN): 67.67
En sens inverse, avec l'option -d
, où le
Raspberry Pi va envoyer des données :
Downstream Summary Delivered(%): 84.90, Loss/OoO/Dup: 8576/0/0, OWDVar(ms): 0/641/956, RTTVar(ms): 0-39, Mbps(L3/IP): 38.86 Downstream Minimum One-Way Delay(ms): -927 [w/clock difference], Round-Trip Time(ms): 1 Downstream Maximum Mbps(L3/IP): 46.98, Mbps(L2/Eth): 47.68, Mbps(L1/Eth): 48.46, Mbps(L1/Eth+VLAN): 48.62
(Notez le taux de pertes élevé, la pauvre machine n'arrive pas à suivre.)
Date de publication du RFC : Août 2021
Auteur(s) du RFC : F. Gont (SI6 Networks), J. Zorz (6connect), R. Patterson (Sky UK), B. Volz (Cisco)
Réalisé dans le cadre du groupe de travail IETF v6ops
Première rédaction de cet article le 7 septembre 2021
Quand un FAI modifie la configuration de son réseau IPv6, les routeurs chez les clients finaux, les CPE, ne retransmettent pas toujours rapidement ce changement, ce qui mène parfois à des problèmes de connectivité. Ce RFC décrit ce que doivent faire ces CPE pour minimiser les conséquence négatives d'une rénumérotation d'un réseau.
Le problème est documenté dans le RFC 8978 : par exemple, lorsqu'un routeur chez M. Toutlemonde (une « box ») redémarre et « oublie » sa configuration réseau précédente, si elle a changé chez le FAI entretemps, les machines du réseau local de M. Toutlemonde risquent de continuer à utiliser pendant un certain une ancienne configuration, désormais invalide. Comment éviter cela ?
Notre RFC se penche surtout sur le cas où le routeur de M. Toutlemonde a appris son préfixe IPv6 depuis le FAI via la délégation de préfixe DHCP du RFC 8415, et qu'il retransmet l'information sur ce préfixe dans le réseau local avec le SLAAC du RFC 4862 (via les RA, Router Advertisements, RFC 4861) ou bien DHCP. Les machines terminales sur le réseau local peuvent aussi agir (ce sera dans un futur RFC) mais l'actuel RFC ne concerne que les routeurs. Il consiste en une série d'exigences auxquelles doivent se prêter les routeurs, exigences qui s'ajoutent à celles déjà présentes dans le RFC 7084 ou bien qui modifient des exigences de ce RFC 7084.
Beaucoup de ces exigences nécessitent un mécanisme de stockage d'informations sur le routeur, stockage qui doit survivre aux redémarrages, ce qui ne sera pas évident pour tous les routeurs. Ainsi, le RFC demande que, du côté WAN, le routeur utilise toujours le même identificateur (IAID, Identity Association IDentifier, RFC 8415, section 4.2) en DHCP (pour essayer de garder le même préfixe). Certains routeurs choisissent apparemment l'IAID au hasard à chaque démarrage, ce qui leur obtient un nouveau préfixe. Il vaut mieux le garder, mais cela nécessite qu'il puisse être stocké et mémorisé même en cas de redémarrage. Comme tous les routeurs n'ont pas de mécanisme de stockage stable, les exigences du RFC sont exprimées (dans le langage du RFC 2119) par un SHOULD et pas un MUST.
Autre exigence, qui relève du bon sens, le routeur ne doit pas, lorsqu'il utilise un préfixe du côté LAN (le réseau local), utiliser une durée de vie plus longue (options Preferred Lifetime et Valid Lifetime du message d'information sur le préfixe envoyé par le routeur, RFC 4861, section 4.6.2) que celle qu'il a lui-même appris en DHCP côté WAN. On ne doit pas promettre ce qu'on ne peut pas tenir, la durée du bail DHCP s'impose au routeur et aux annonces qu'il fait sur le réseau local.
Enfin, le routeur ne doit pas, au redémarrage, envoyer
systématiquement un abandon du préfixe appris en DHCP (message DHCP
RELEASE
). Certains routeurs font apparemment
cela, ce qui risque de déclencher une renumérotation brutale (RFC 8978).
Lorsque le préfixe change, le routeur devrait aussi signaler cela sur le réseau local. Là encore, cela impose une mémoire, un stockage stable. Il doit se souvenir de ce qu'il a reçu, et annoncé, précédemment, pour pouvoir annoncer sur le réseau local que ces anciens préfixes ne doivent plus être utilisés (cela se fait en annonçant ces préfixes, mais avec une durée de vie nulle). Dans un monde idéal, le routeur sera prévenu des changements de préfixe parce que le FAI réduira la durée de vie de ses baux DHCP, permettant un remplacement ordonné d'un préfixe par un autre. Dans la réalité, on a parfois des renumérotations brutales, sans préavis (RFC 8978). Le routeur doit donc également gérer ces cas.
Date de publication du RFC : Juillet 2021
Auteur(s) du RFC : J. Yao, L. Zhou, H. Li (CNNIC), N. Kong (Consultant), W. Tan (Cloud Registry), J. Xie
Pour information
Première rédaction de cet article le 30 juillet 2021
Le bundling est le rassemblement de plusieurs noms de domaine dans un seul lot (bundle) qui va être traité comme un nom unique pour des opérations comme l'enregistrement du nom ou son transfert à un autre titulaire. Il est surtout pratiqué par les registres qui ont beaucoup de noms en écriture chinoise. Ce RFC décrit une extension au protocole d'avitaillement EPP pour pouvoir traiter ces lots.
Le problème n'existe pas qu'en chinois mais ce sont surtout les
sinophones qui se sont manifestés à ce sujet, en raison de la
possibilité d'écrire le même mot en écriture
traditionnelle ou en écriture
simplifiée (on parle de variantes :
l'ensemble des variantes forme le lot). Pour prendre un exemple
non-chinois, PIR avait décidé qu'un nom dans
.ngo
et dans .ong
devaient
être dans le même lot. Un registre qui décide que ces deux termes
sont équivalents et doivent être gérés ensemble (par exemple,
appartenir au même titulaire) les regroupent dans un lot
(bundle, ou parfois
package). C'est la politique suggérée dans les
RFC 3743 et RFC 4290, et
le RFC 6927 décrit les pratiques
existantes. Par exemple, certains registres peuvent n'autoriser
qu'une variante par lot, et bloquer les autres (empêcher leur
enregistrement), tandis que d'autres enregistreront tous les noms
ensemble. Sans compter bien sûr les registres qui n'ont pas de
système de lot du tout. Notre nouveau RFC 9095 ne prend pas
position sur ce sujet délicat, il décrit juste un moyen technique de
manipuler ces lots avec EPP (RFC 5731).
Les variantes dans un même lot n'ont pas forcément tout en commun. Un registre peut par exemple décider que l'enregistrement des variantes doit être fait par le même titulaire mais qu'un nom du lot peut ensuite être transféré à un autre titulaire. Notre RFC se limite au cas strict où les membres du lot ont presque tous leurs attributs (titulaires, contacts, date d'expiration, peut-être serveurs de noms et, pourquoi pas, clés DNSSEC) en commun.
La
lecture du RFC nécessite un peu de terminologie spécifique, décrite
dans sa section 2. Par exemple, le RDN (Registered Domain
Name) est celui qui a été demandé par le titulaire lors de
l'enregistrement, et le BDN (Bundled Domain Name)
est un nom qui a été inclus dans le lot, en fonction des règles du
registre. Par exemple, si un registre décidait que tout nom avec des
traits d'union était équivalent au même nom
sans traits d'union, et qu'un titulaire enregistre
tarte-poireaux.example
(le RDN), alors
tartepoireaux.example
et
tarte-poi-reaux.example
seraient des BDN,
membres du même lot que le RDN. Dans le modèle de notre RFC, les
métadonnées comme la date d'expiration ou comme l'état du domaine
sont attachées au RDN, les BDN du lot partageant ces
métadonnées.
Notons aussi que le RFC n'envisage que le cas de lots assez
petits (par exemple le nom en écriture chinoise traditionnelle et
celui en écriture chinoise simplifiée). L'exemple que je donnais
avec le trait d'union ne rentre pas tellement dans le cadre de ce
RFC car le nombre de BDN est alors beaucoup plus élevé et serait
difficile à gérer. (Amusez-vous à calculer combien de variantes de
tartepoireaux.example
existeraient si un
décidait que le trait d'union n'est pas significatif.)
Dans l'extension EPP décrite dans ce RFC, le RDN
est représenté (section 5 du RFC) en Unicode
(le « U-label ») ou bien en ASCII (le « A-label »,
la forme « punycodée »). L'élement XML est
<b-dn:rdn>
(où b-dn
est un préfixe possible pour l'espace de noms
XML de notre RFC, urn:ietf:params:xml:ns:epp:b-dn
). Si le RDN est représenté en ASCII, un attribut XML
uLabel
permet d'indiquer la version Unicode du
nom. Cela donnerait, par exemple, <b-dn:rdn
uLabel="实例.example">xn--fsq270a.example</b-dn:rdn>
.
Enfin, la section 6 décrit les commandes et réponses EPP pour
notre extension. La commande <check>
n'est pas modifiée dans sa syntaxe mais le RFC impose que, si un nom
qui fait partie d'un lot est envoyé dans la question, la réponse
doit contenir le RDN et le BDN. Pour un RDN en
sinogrammes, on aurait ainsi la version en
écriture traditionnelle et en écriture simplifiée (ici, le nom est
disponible à l'enregistrement) :
<response> <result code="1000"> <msg>Command completed successfully</msg> </result> <resData> <domain:chkData xmlns:domain="urn:ietf:params:xml:ns:domain-1.0"> <domain:cd> <domain:name avail="1"> xn--fsq270a.example</domain:name> </domain:cd> <domain:cd> <domain:name avail="1"> xn--fsqz41a.example </domain:name> <domain:reason>This associated domain name is a produced name based on bundle name policy. </domain:reason> </domain:cd> </domain:chkData> ...
La commande <info>
n'est pas non plus
modifiée mais sa réponse l'est, par l'ajout d'un élément
<bundle>
qui décrit le lot :
<response> <result code="1000"> <msg>Command completed successfully</msg> </result> <resData> <domain:infData xmlns:domain="urn:ietf:params:xml:ns:domain-1.0"> <domain:name>xn--fsq270a.example</domain:name> <domain:roid>58812678-domain</domain:roid> <domain:status s="ok"/> ... </domain:infData> </resData> <extension> <b-dn:infData xmlns:b-dn="urn:ietf:params:xml:ns:epp:b-dn"> <b-dn:bundle> <b-dn:rdn uLabel="实例.example"> xn--fsq270a.example </b-dn:rdn> <b-dn:bdn uLabel="實例.example"> xn--fsqz41a.example </b-dn:bdn> </b-dn:bundle> </b-dn:infData> </extension>
Quand on crée un domaine qui fait partie d'un lot, la commande
<create>
doit inclure une extension
indiquant que le client EPP connait la gestion de lots, et la
réponse à <create>
lui donnera le
BDN. D'une manière analogue, la commande
<delete>
détruira tout le lot et
l'indiquera dans la réponse. La commande
<update>
fonctionne sur le même
principe : elle modifie le RDN et indique dans sa réponse le BDN
affecté. La syntaxe complète de cette extension EPP figure dans la
section 7 du RFC, sous forme d'un schéma
W3C. Par ailleurs, cette extension est enregistrée dans
le
registre des extensions EPP (celui créé par le RFC 7451).
Un petit mot sur la sécurité, car de nombreux adversaires de l'internationalisation, notamment anglophones, ont critiqué les noms de domaine en Unicode, les accusant de tous les maux : les auteurs du RFC notent que des noms en chinois sont enregistrés depuis plus de quinze ans, et qu'aucun problème particulier n'a été signalé.
Questions mises en œuvre de ce RFC, les registres chinois (comme
.cn
ou
.tw
) suivent les
principes de ce RFC (l'enregistrement d'un lot) depuis
longtemps. CNNIC déploie cette extension
EPP. En dehors de la sinophonie, PIR utilise
les lots pour .ngo
et
.ong
. Et cette extension EPP est mise en œuvre
dans Net::DRI.
Et, comme souvent, il y a un brevet de Verisign sur la technique décrite dans ce RFC. Je ne l'ai pas lu mais il y a des chances qu'il soit sans mérite, comme beaucoup de brevets logiciels.
Date de publication du RFC : Juin 2021
Auteur(s) du RFC : S. Hollenbeck (Verisign Labs), A. Newton (AWS)
Chemin des normes
Réalisé dans le cadre du groupe de travail IETF regext
Première rédaction de cet article le 16 juin 2021
Dans l'ensemble des normes sur le protocole RDAP, ce RFC est destiné à décrire le format de sortie, celui des réponses envoyées par le serveur d'information RDAP. Ce format est basé sur JSON et, pour utiliser RDAP en remplacement de whois, il faudra donc se munir de quelques outils pour traiter le JSON. Ce RFC remplace le RFC 7483 mais il y a très peu de changements.
JSON est normalisé dans le RFC 8259 et représente aujourd'hui le format de choix pour les données structurées c'est-à-dire analysables par un programme. (L'annexe E de notre RFC explique en détail le pourquoi du choix de JSON.) Une des caractéristiques importantes de whois est en effet que son format de sortie n'a aucune structure, c'est du texte libre, ce qui est pénible pour les programmeurs qui essaient de le traiter (mais, d'un autre côté, cela ralentit certains usages déplorables, comme la récolte d'adresses de courrier à des fins de spam, jouant ainsi un rôle de semantic firewall). L'utilisation typique de RDAP est en HTTP (RFC 7480) avec les requêtes exprimées dans la syntaxe des URL du RFC 9082. Voici tout de suite un exemple réel, avec la réponse JSON :
% curl https://rdap.afilias.net/rdap/info/domain/kornog-computing.info ... { "entities": [ ... "objectClassName": "entity", "roles": [ "technical", "billing", "administrative" ], "vcardArray": [ "vcard", [ [ "version", {}, "text", "4.0" ], [ "fn", {}, "text", "KORNOG computing" ], [ "adr", { "cc": "FR" }, "text", {} ] ... "events": [ { "eventAction": "registration", "eventDate": "2007-08-14T17:02:33Z" }, { "eventAction": "last changed", "eventDate": "2014-08-14T01:54:07Z" }, { "eventAction": "expiration", "eventDate": "2015-08-14T17:02:33Z" } ], "handle": "D19378523-LRMS", "lang": "en", "ldhName": "kornog-computing.info", ... "nameservers": [ { "ldhName": "ns1.kornog-computing.net", "objectClassName": "nameserver", "remarks": [ { "description": [ "Summary data only. For complete data, send a specific query for the object." ], "title": "Incomplete Data", "type": "object truncated due to unexplainable reasons" } ] }, { "ldhName": "ns2.kornog-computing.net", "objectClassName": "nameserver", "remarks": [ { "description": [ "Summary data only. For complete data, send a specific query for the object." ], "title": "Incomplete Data", "type": "object truncated due to unexplainable reasons" } ] } ], "notices": [ { "description": [ "Access to AFILIAS WHOIS information is provided to assist persons in determining the contents of a domain name registration record in the Afilias registry database. The data in this record is provided by Afilias Limited for informational purposes only, and Afilias does not guarantee its accuracy. [...]" ], "title": "TERMS OF USE" } ], "objectClassName": "domain", ... "rdapConformance": [ "rdap_level_0" ], "secureDNS": { "zoneSigned": false }, "status": [ "clientDeleteProhibited -- http://www.icann.org/epp#clientDeleteProhibited", "clientTransferProhibited -- http://www.icann.org/epp#clientTransferProhibited" ] }
La section 1.2 présente le modèle de données de RDAP. Dans les réponses, on trouve des types simples (comme des chaînes de caractères), des tableaux JSON, et des objets JSON qui décrivent les trucs sur lesquels on veut de l'information (noms de domaine, adresses IP, etc). Ces objets peuvent comporter des tableaux ou d'autres objets et, lorsqu'une réponse renvoie plusieurs objets (cas des recherches ouvertes, cf. RFC 9082, section 3.2), peuvent eux-même être regroupés en tableaux. Il existe plusieurs classes de ces objets. D'abord, les classes communes aux registres de noms de domaine et aux RIR (registres d'adresses IP) :
in-addr.arpa
et
ip6.arpa
),GR283-FRNIC
.Deux classes sont spécifiques aux RIR :
On verra peut-être apparaître d'autres classes avec le temps, si l'usage de RDAP se répand.
La section 2 de notre RFC décrit comment JSON est utilisé. Le
type MIME renvoyé est
application/rdap+json
. La section 2 commence
par un rappel que le client RDAP doit ignorer
les membres inconnus dans les objets JSON, afin de préserver la
future extensibilité. (Cette règle ne s'applique pas au jCard
- cf. RFC 7095 - embarqué.) Si un serveur veut
ajouter des membres qui lui sont spécifiques, il est recommandé
qu'il préfixe leur nom avec un identificateur court suivi d'un
tiret bas. Ainsi, cet objet
RDAP standard :
{ "handle" : "ABC123", "remarks" : [ { "description" : [ "She sells sea shells down by the sea shore.", "Originally written by Terry Sullivan." ] } ] }
S'il est servi par le « registre de la Lune » (registre imaginaire
qui fournit les exemples de ce RFC), celui-ci, lorsqu'il voudra
ajouter des membres, les précédera de
lunarNIC_
:
{ "handle" : "ABC123", "lunarNic_beforeOneSmallStep" : "TRUE THAT!", "remarks" : [ { "description" : [ "She sells sea shells down by the sea shore.", "Originally written by Terry Sullivan." ] } ], "lunarNic_harshMistressNotes" : [ "In space,", "nobody can hear you scream." ] }
(Je vous laisse décoder les références geeks dans l'exemple.)
La section 3 s'occupe des types de données de base. On utilise du
JSON normal, tel que spécifié dans le RFC 8259, avec ses chaînes de caractères, ses booléens, son
null
... Un handle
(l'identifiant unique - par registre - d'un contact, parfois nommé
NIC handle ou registry ID) est
une chaîne. Les adresses IP se représentent également par une chaîne
de caractères (ne pas oublier le RFC 5952 pour
IPv6). Les pays sont identifiés par le code à
deux lettres de ISO 3166. Les noms de
domaines peuvent être sous une forme ASCII ou bien en
Unicode. Les dates et heures suivent le RFC 3339, et les URI le RFC 3986.
Les informations plus complexes (comme les détails sur un contact) utilisent jCard, normalisé dans le RFC 7095.
Que trouve-t-on dans les objets JSON renvoyés par un serveur RDAP ? Une indication de la version de la norme :
"rdapConformance" : [ "rdap_level_0" ]
(Éventuellement avec d'autres identificateurs pour les extensions locales.) Et des liens vers des ressources situées ailleurs, suivant le cadre du RFC 8288 :
"links": [ { "href": "http://rdg.afilias.info/rdap/entity/ovh53ec16bekre5", "rel": "self", "type": "application/rdap+json", "value": "http://rdg.afilias.info/rdap/entity/ovh53ec16bekre5" }
On peut aussi avoir du texte libre, comme dans l'exemple plus haut
avec le membre remarks
. Ce membre sert aux
textes décrivant la classe, alors que notices
est utilisé pour le service RDAP. Un exemple :
"notices": [ { "description": [ "Access to AFILIAS WHOIS information is provided to assist persons in determining the contents of a domain name registration record in the Afilias registry database. The data in this record is provided by Afilias Limited for informational purposes only, and Afilias does not guarantee its accuracy. [...]" ], "title": "TERMS OF USE" } ],
Contrairement à whois, RDAP permet l'internationalisation sous tous ses aspects. Par exemple, on peut indiquer la langue des textes avec une étiquette de langue (RFC 5646) :
"lang": "en",
Enfin, la classe (le type) de l'objet renvoyé par RDAP est
indiquée par un membre objectClassName
:
"objectClassName": "domain",
Ces classes, justement. La section 5 les décrit. Il y a d'abord
la classe Entity
qui correspond aux requêtes
/entity
du RFC 9082. Elle
sert à décrire les personnes et les organisations. Parmi les membres
importants pour les objets de cette classe,
handle
qui est un identificateur de l'instance
de la classe, et roles
qui indique la relation
de cette instance avec l'objet qui la contient (par exemple
"roles": ["technical"]
indiquera que cette
Entity
est le contact technique de
l'objet). L'information de contact sur une
Entity
se fait avec le format
vCard du RFC 7095, dans
un membre vcardArray
. Voici un exemple avec le
titulaire du domaine uba.ar
:
% curl https://rdap.nic.ar/entity/30546666561 ... "objectClassName": "entity", "vcardArray": [ "vcard", [ [ "version", {}, "text", "4.0" ], [ "fn", {}, "text", "UNIVERSIDAD DE BUENOS AIRES" ] ] ], ...
(jCard permet de mentionner plein d'autres choses qui ne sont a priori pas utiles pour RDAP, comme la date de naissance ou le genre.)
La classe Nameserver
correspond aux requêtes
/nameserver
du RFC 9082. Notez qu'un registre peut gérer les serveurs de noms
de deux façons : ils peuvent être vus comme des objets autonomes,
enregistrés tels quel dans le registre (par exemple via le RFC 5732), ayant des attributs par exemple des
contacts, et interrogeables directement par whois ou RDAP (c'est le
modèle de .com
, on dit
alors que les serveurs de noms sont des « objets de première
classe »). Ou bien ils peuvent être simplement des attributs des
domaines, accessibles via le domaine. Le principal attribut d'un
objet Nameserver
est son adresse IP, pour
pouvoir générer des colles dans le DNS (enregistrement DNS d'une
adresse IP, pour le cas où le serveur de noms est lui-même dans la
zone qu'il sert). Voici un exemple avec un des serveurs de noms de
la zone afilias-nst.info
:
% curl https://rdap.afilias.net/rdap/info/nameserver/b0.dig.afilias-nst.info ... "ipAddresses": { "v4": [ "65.22.7.1" ], "v6": [ "2a01:8840:7::1" ] }, ...
Notez que l'adresse IP est un tableau, un serveur pouvant avoir plusieurs adresses.
La classe Domain
correspond aux requêtes
/domain
du RFC 9082. Un
objet de cette classe a des membres indiquant les serveurs de noms,
si la zone est signée avec DNSSEC ou pas, l'enregistrement DS si elle est signée, le
statut (actif ou non, bloqué ou non), les contacts, etc. Voici un
exemple :
% curl http://rdg.afilias.info/rdap/domain/afilias-nst.info ... "nameservers": [ { "ldhName": "a0.dig.afilias-nst.info", ... "secureDNS": { "delegationSigned": false }, ... "status": [ "client transfer prohibited", "server delete prohibited", "server transfer prohibited", "server update prohibited" ], ...
La classe IP network
rassemble les objets
qu'on trouve dans les réponses aux requêtes /ip
du RFC 9082. Un objet de cette classe ne
désigne en général pas une seule adresse IP mais un préfixe, dont on
indique la première (startAddress
) et la
dernière adresse (endAddress
). Personnellement,
je trouve cela très laid et j'aurai préféré qu'on utilise une
notation préfixe/longueur. Voici un exemple :
% curl https://rdap.db.ripe.net/ip/131.111.150.25 ... { "handle" : "131.111.0.0 - 131.111.255.255", "startAddress" : "131.111.0.0/32", "endAddress" : "131.111.255.255/32", "ipVersion" : "v4", "name" : "CAM-AC-UK", "type" : "LEGACY", "country" : "GB", ...
La dernière classe normalisée à ce stade est
autnum
(les AS), en réponse aux requêtes
/autnum
du RFC 9082. Elle
indique notamment les contacts de l'AS. Pour l'instant, il n'y a pas
de format pour indiquer la politique de routage (RFC 4012). Un exemple d'un objet de cette classe :
% curl https://rdap.db.ripe.net/autnum/20766 { "handle" : "AS20766", "name" : "GITOYEN-MAIN-AS", "type" : "DIRECT ALLOCATION", ... "handle" : "GI1036-RIPE", "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "fn", { }, "text", "NOC Gitoyen" ], [ "kind", { }, "text", "group" ], [ "adr", { "label" : "Gitoyen\n21 ter rue Voltaire\n75011 Paris\nFrance" }, "text", null ], [ "email", { }, "text", "noc@gitoyen.net" ] ] ], ...
Comme, dans la vie, il y a parfois des problèmes, une section de
notre RFC, la section 6, est dédiée aux formats des erreurs que
peuvent indiquer les serveurs RDAP. Le code de retour HTTP fournit
déjà des indications (404 = cet objet n'existe pas ici, 403 = vous
n'avez pas le droit de le savoir, etc) mais on peut aussi ajouter un
objet JSON pour en indiquer davantage, objet ayant un membre
errorCode
(qui reprend le code HTTP), un membre
title
et un membre
description
. Voici un exemple sur le serveur
RDAP de l'ARIN :
% curl -v http://rdap.arin.net/registry/autnum/99999 < HTTP/1.0 404 Not Found < Mon, 10 May 2021 09:07:52 GMT < Server: Apache/2.4.6 (CentOS) OpenSSL/1.0.2k-fips ... { ... "errorCode" : 404, "title" : "AUTNUM NOT FOUND", "description" : [ "The autnum you are seeking as '99999' is/are not here." ]
Plus positive, la possibilité de demander de l'aide à un serveur
RDAP, en se renseignant sur ses capacités, avec la requête
/help
. Son résultat est décrit dans la section
7 mais tous les serveurs RDAP actuels n'utilisent pas
cette possibilité. En voici un où ça marche, à
l'ARIN :
% curl -s https://rdap.arin.net/registry/help { "rdapConformance" : [ "rdap_level_0" ], "notices" : [ { "title" : "Terms of Service", "description" : [ "By using the ARIN RDAP/Whois service, you are agreeing to the RDAP/Whois Terms of Use" ], "links" : [ { "value" : "https://rdap.arin.net/registry/help", "rel" : "about", "type" : "text/html", "href" : "https://www.arin.net/resources/registry/whois/tou/" } ] }, { "title" : "Whois Inaccuracy Reporting", "description" : [ "If you see inaccuracies in the results, please visit: " ], "links" : [ { "value" : "https://rdap.arin.net/registry/help", "rel" : "about", "type" : "text/html", "href" : "https://www.arin.net/resources/registry/whois/inaccuracy_reporting/" } ] }, { "title" : "Copyright Notice", "description" : [ "Copyright 1997-2021, American Registry for Internet Numbers, Ltd." ] } ] }
Et les résultats des recherches ouvertes (section 3.2 du RFC 9082), qui peuvent renvoyer plusieurs objets ?
Ce sont des tableaux JSON, dans des membres dont le nom se termine
par Results
. Par exemple, en cherchant les noms
de domaines commençant par ra
(ce test a été
fait sur un serveur expérimental qui ne marche plus depuis) :
% curl http://rdg.afilias.info/rdap/domains\?name=ra\*|more "domainSearchResults": [ { "ldhName": "RAINSTRAGE.INFO", ... "objectClassName": "domain", "remarks": [ { "description": [ "Summary data only. For complete data, send a specific query for the object." ], "title": "Incomplete Data", "type": "object truncated due to unexplainable reasons" } ... "ldhName": "RADONREMOVAL.INFO", ... "ldhName": "RANCONDI.INFO", ...
Les vrais serveurs RDAP en production ne répondent pas forcément à ces requêtes trop coûteuses et qui peuvent trop facilement être utilisées pour le renseignement économique :
% curl https://rdap.afilias.net/rdap/info/domains\?name=ra\* ... "errorCode": 422, "title": "Error in processing the request", "description": [ "WildCard search is not supported on sub-zone or tld" ]
Vous avez peut-être noté dans le tout premier exemple le membre
events
(section 4.5 du RFC). Ces événements
comme created
ou
last-changed
donnent accès à l'histoire d'un
objet enregistré. Ici, nous apprenons que le domaine
kornog-computing.info
a été enregistré en
2007.
Certaines valeurs qui apparaissent dans les résultats sont des
chaînes de caractères fixes, stockées dans un nouveau registre
IANA. Elles sont conçues pour être utilisées dans les
notices
, remarks
,
status
, roles
et quelques
autres. Parmi les remarques, on trouvera le cas où une réponse a été
tronquée (section 9 du RFC), comme dans l'exemple ci-dessus avec la
mention Incomplete Data. Parmi les statuts, on
trouvera, par exemple validated
(pour un objet
vérifié, par exemple un nom de domaine dont on a vérifié les
coordonnées du titulaire), locked
(pour un
objet verrouillé), obscured
(qui n'est pas un
statut dans le base du données du registre mais simplement la
mention du fait que le serveur RDAP a délibérement modifié certaines
informations qu'il affiche, par exemple pour protéger la vie
privée), etc. Pour les rôles, on trouvera
registrant
(titulaire),
technical
(contact technique), etc.
Pour ceux qu'intéressent les questions d'internationalisation, la section 12 contient d'utiles mentions. L'encodage des données JSON doit être de l'UTF-8. Et, comme indiqué plus haut, les IDN peuvent être sous la forme Punycode ou bien directement en UTF-8.
Et la vie privée, un problème permanent
avec whois, où il faut toujours choisir entre la distribution de
données utiles pour contacter quelqu'un et les risques pour sa vie
privée ? La section 13 revient sur
cette question. Un point important : RDAP est un protocole, pas une
politique. Il ne définit pas quelles règles suivre (c'est de la
responsabilité des divers registres) mais il fournit les outils pour
mettre en œuvre ces règles. Notamment, RDAP permet de marquer des
parties de la réponse comme étant connues du registre, mais n'ayant
délibérement pas été envoyées (avec les codes
private
et removed
) ou
bien comme ayant été volontairement rendues peu ou pas lisibles
(code obscured
).
Vous avez vu dans les exemples précédents que les réponses d'un serveur RDAP sont souvent longues et, a priori, moins lisibles que celles d'un serveur whois. Il faudra souvent les traiter avec un logiciel qui comprend le JSON. Un exemple très simple et que j'apprécie est jq. Il peut servir à présenter le résultat de manière plus jolie :
% curl -s https://rdap.centralnic.com/pw/domain/centralnic.pw | jq . ... { "objectClassName": "domain", "handle": "D956082-CNIC", "ldhName": "centralnic.pw", "nameservers": [ { "objectClassName": "nameserver", "ldhName": "ns0.centralnic-dns.com", ...
(Essayez ce même serveur RDAP sans jq !)
Mais on peut aussi se servir de jq pour extraire un champ particulier, ici le pays :
% curl -s https://rdap.db.ripe.net/ip/131.111.150.25 | jq ".country" "GB" % curl -s https://rdap.db.ripe.net/ip/192.134.1.1 | jq ".country" "FR"
Il y a évidemment d'autres logiciels que jq sur ce créneau, comme
JSONpath,
jpath
ou, pour les programmeurs Python, python -m
json.tool
.
Un dernier mot, sur le choix de JSON pour le format de sortie, alors que le protocole standard d'avitaillement des objets dans les bases Internet, EPP (RFC 5730) est en XML. L'annexe E de notre RFC, qui discute ce choix, donne comme principaux arguments que JSON est désormais plus répandu que XML (cf. l'article « The Stealthy Ascendancy of JSON ») et que c'est surtout vrai chez les utilisateurs (EPP étant utilisé par une population de professionnels bien plus réduite).
Quels changements depuis le RFC 7483 ? La
plupart sont mineurs et sont de l'ordre de la
clarification. D'autres sont des corrections
d'erreurs, par exemple une coquille
qui avait mis registrant là où il aurait fallu
dire registrar (la proximité des mots en anglais
entraine souvent des erreurs, même chez les professionnels). Il y a
une certaine tendance au durcissement des règles, des éléments qui
étaient optionnels dans le RFC 7483 sont
devenus obligatoires comme, par exemple,
rdapConformance
(dont le statut optionnel avait
causé des
problèmes).
Et question logiciels qui mettent en œuvre RDAP ? Beaucoup de logiciels de gestion de registre le font aujourd'hui, notamment ceux sous contrat avec l'ICANN, puisqu'ils n'ont pas le choix. Mais les logiciels ne sont pas forcément publiquement disponibles. Parmi ceux qui le sont, il y a RedDog, Fred, celui de l'APNIC…
Date de publication du RFC : Juin 2021
Auteur(s) du RFC : S. Hollenbeck (Verisign Labs), A. Newton (AWS)
Chemin des normes
Réalisé dans le cadre du groupe de travail IETF regext
Première rédaction de cet article le 16 juin 2021
Le protocole d'information RDAP, qui vise à remplacer whois, est décrit dans un ensemble de RFC. Celui présenté ici normalise la façon de former les requêtes RDAP. Celles-ci ont la forme d'une URL, puisque RDAP repose sur l'architecture REST. Ce RFC remplace l'ancienne norme sur les requêtes RDAP, qui était dans le RFC 7482, mais il n'y a pas de changement significatif.
RDAP peut être utilisé pour beaucoup de sortes d'entités différentes mais ce RFC ne couvre que ce qui correspond aux usages actuels de whois, les préfixes d'adresses IP, les AS, les noms de domaine, etc. Bien sûr, un serveur RDAP donné ne gère pas forcément tous ces types d'entités, et il doit renvoyer le code HTTP 501 (Not implemented) s'il ne sait pas gérer une demande donnée. Ce RFC ne spécifie que l'URL de la requête, le format de la réponse est variable (JSON, XML...) et le seul actuellement normalisé, au-dessus de JSON, est décrit dans le RFC 9083. Quant au protocole de transport, le seul actuellement normalisé pour RDAP (dans le RFC 7480) est HTTP. D'autre part, ces RFC RDAP ne décrivent que le protocole entre le client RDAP et le serveur, pas « l'arrière-cuisine », c'est-à-dire l'avitaillement (création, modification et suppression) des entités enregistrées. RDAP est en lecture seule et ne modifie pas le contenu des bases de données qu'il interroge.
Passons aux choses concrètes. Une requête RDAP est un
URL (RFC 3986). Celui-ci est obtenu en ajoutant un chemin
spécifique à une base. La base (par exemple
https://rdap.example.net/
) va être obtenue par
des mécanismes divers, comme celui du RFC 7484, qui spécifie un registre que vous pouvez
trouver en ligne. On met ensuite un chemin qui dépend du type
d'entité sur laquelle on veut se renseigner, et qui indique
l'identificateur de l'entité. Par exemple, avec la base ci-dessus,
et une recherche du nom de domaine
internautique.fr
, on construirait un URL
complet
https://rdap.example.net/domain/internautique.fr
. Il
y a cinq types d'entités possibles :
ip
: les préfixes IP (notez qu'on peut chercher
un préfixe en donnant juste une des adresses IP couvertes par ce
préfixe),autnum
: les numéros de systèmes autonomes,domain
: un nom de domaine (notez que cela peut être un
domaine dans in-addr.arpa
ou
ipv6.arpa
),nameserver
: un serveur de
noms,entity
: une entité quelconque, comme
un bureau d'enregistrement, ou un contact
identifié par un handle.
La requête est effectuée avec la méthode HTTP
GET
(les méthodes permettant de modifier le
contenu du registre n'ont pas de sens ici, les modifications dans le
registre sont plutôt faites avec EPP). Pour juste savoir si un
objet existe, on peut aussi utiliser la méthode
HEAD
. Si on n'obtient pas de code 404, c'est
que l'objet existe.
Pour ip
, le chemin dans l'URL est
/ip/XXX
où XXX
peut être
une adresse IPv4 ou
IPv6 sous forme texte. Il peut aussi y avoir
une longueur de préfixe à la fin donc
/ip/2001:db8:1:a::/64
est un chemin
valable. Ainsi, sur le service RDAP du RIPE-NCC,
https://rdap.db.ripe.net/ip/2001:4b98:dc0:41::
est un URL possible. Testons-le avec curl (le format de sortie, en JSON, est décrit dans
le RFC 9083, vous aurez peut-être
besoin de passer le résultat à travers jq
pour l'afficher joliment) :
% curl https://rdap.db.ripe.net/ip/2001:4b98:dc0:41:: { "handle" : "2001:4b98:dc0::/48", "startAddress" : "2001:4b98:dc0::/128", "endAddress" : "2001:4b98:dc0:ffff:ffff:ffff:ffff:ffff/128", "ipVersion" : "v6", "name" : "GANDI-HOSTING-DC0", "type" : "ASSIGNED", "country" : "FR", "rdapConformance" : [ "rdap_level_0" ], "entities" : [ { "handle" : "GAD42-RIPE", "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "fn", { }, "text", "Gandi Abuse Department" ], [ "kind", { }, "text", "group" ], [ "adr", { "label" : "63-65 Boulevard Massena\n75013 Paris\nFrance" ...
J'ai utilisé curl mais, notamment pour formater plus joliment la sortie de RDAP, les vrais utilisateurs se serviront plutôt d'un client RDAP dédié comme RDAPBrowser sur Android, ou nicinfo. Voici une vue de RDAPbrowser:
Pour autnum
, on met le numéro de
l'AS après
/autnum/
(au format
« asplain
» du RFC 5396). Toujours dans l'exemple RIPE-NCC,
https://rdap.db.ripe.net/autnum/208069
permet de
chercher de l'information sur l'AS
208069 :
% curl https://rdap.db.ripe.net/autnum/208069 { "handle" : "AS208069", "name" : "ATAXYA", "type" : "DIRECT ALLOCATION", "entities" : [ { "handle" : "mc40833-RIPE", "roles" : [ "administrative", "technical" ], "objectClassName" : "entity" }, { ...
Pour les noms de domaines, on met le nom après
/domain/
. Ainsi, sur le serveur RDAP d'Afilias,
https://rdap.afilias.net/rdap/info/domain/rmll.info
nous donnera de l'information sur le domaine
rmll.info
. On peut mettre un nom en
Unicode donc
https://rdap.example.net/domain/potamochère.fr
est valable, mais il devra être encodé comme l'explique la section
6.1, plus loin (en gros, UTF-8 en
NFC). Si on ne veut pas lire cette
information sur l'encodage, on peut aussi utiliser la forme
Punycode, donc chercher avec
https://rdap.example.net/domain/xn--potamochre-66a.fr
. Un
exemple réel, en Russie :
% curl https://api.rdap.nic.рус/domain/валфекс.рус ... { "eventAction": "registration", "eventDate": "2018-12-26T07:53:41.776927Z" }, ... "adr", { "type": "Registrar Contact" }, "text", [ "", "", "125476, g. Moskva, ul. Vasilya Petushkova, dom 3, str. 1", "", "", "", "RU" ] ] ...
(Attention, le certificat ne sera accepté par curl que si curl a été compilé avec l'option « IDN ».)
On peut aussi se servir de RDAP pour les noms de domaines qui servent à traduire une adresse IP en nom :
% curl https://rdap.db.ripe.net/domain/1.8.a.4.1.0.0.0.0.d.1.4.1.0.0.2.ip6.arpa { "handle" : "0.d.1.4.1.0.0.2.ip6.arpa", "ldhName" : "0.d.1.4.1.0.0.2.ip6.arpa", "nameServers" : [ { "ldhName" : "dns15.ovh.net" }, { "ldhName" : "ns15.ovh.net" } ], "rdapConformance" : [ "rdap_level_0" ], "entities" : [ { "handle" : "OK217-RIPE", "roles" : [ "administrative" ] }, { "handle" : "OTC2-RIPE", "roles" : [ "zone", "technical" ] }, { "handle" : "OVH-MNT", "roles" : [ "registrant" ] } ], "remarks" : [ { "description" : [ "OVH IPv6 reverse delegation" ] } ], ...
Pour un serveur de noms, on met son nom après
/nameserver
donc, chez Afilias :
% curl https://rdap.afilias.net/rdap/info/nameserver/rmll1.rmll.info { ... "ipAddresses": { "v4": [ "80.67.169.65" ] }, "lang": "en", "ldhName": "rmll1.rmll.info", ...
Pour entity
, on indique juste un
identificateur. Voici un exemple :
% curl http://rdg.afilias.info/rdap/entity/81 { "handle": "81", "lang": "en", ... "roles": [ "registrar" ], "vcardArray": [ "vcard", [ [ "version", {}, "text", "4.0" ], [ "fn", {}, "text", "Gandi SAS" ], [ "adr", {}, "text", [ "", "", "63-65 boulevard Massena", "Paris", "", "F-75013", "FR" ] ...
Certains registres, qui stockent d'autres types d'objets,
pourront ajouter leurs propres requêtes, en prenant soin
d'enregistrer les préfixes de ces requêtes dans le
registre IANA. Par exemple, le logiciel de gestion de
registres FRED permet
d'interroger le registre sur les clés DNSSEC avec les requêtes
/fred_keyset
(la syntaxe des requêtes locales est identificateur du préfixe +
tiret bas + type cherché).
Dernière possibilité, un chemin spécial indique qu'on veut
récupérer de l'aide sur ce serveur RDAP particulier. En envoyant
help
(par exemple
https://rdap.example.net/help
), on obtient un
document décrivant les capacités de ce serveur, ses conditions
d'utilisation, sa politique vis-à-vis de la vie
privée, ses possibilités d'authentification (via les
mécanismes de HTTP), l'adresse
où contacter les responsables, etc. C'est l'équivalent de la
fonction d'aide qu'offrent certains serveurs whois, ici celui de
l'AFNIC :
% whois -h whois.nic.fr -- -h ... %% Option Function %% ------- ------------------------------------- %% -r turn off recursive lookups %% -n AFNIC output format %% -o old fashioned output format (Default) %% -7 force 7bits ASCII output format %% -v verbose mode for templates and help options %% (may be use for reverse query) %% -T type return only objects of specified type %% -P don't return individual objects in case of contact search %% -h informations about server features %% -l lang choice of a language for informations (you can specify US|EN|UK for %% english or FR for french) %% ...
Pour RDAP, voyez par exemple
(qui renvoie de
l'HTML), ou,
plus austères et se limitant à un renvoi à une page Web,
https://rdap.nic.bzh/help
ou
http://rdap.apnic.net/help
.https://rdap.nic.cz/help
Toutes les recherches jusque-là ont été des recherches exactes
(pas complètement pour les adresses IP, où on pouvait chercher un
réseau par une seule des adresses contenues dans le réseau). Mais on
peut aussi faire des recherches plus ouvertes, sur une partie de
l'identificateur. Cela se fait en ajoutant une requête (la partie
après le point
d'interrogation) dans l'URL et en ajoutant un
astérisque (cf. section 4.1). Ainsi,
https://rdap.example.net/domains?name=foo*
cherchera tous les domaines dont le nom commence par la chaîne de
caractères foo
. (Vous avez noté que c'est
/domains
, au pluriel, et non plus
/domain
?) Voici un exemple d'utilisation :
% curl https://rdap.afilias.net/rdap/info/domains\?name=rm\* ... "errorCode": 422, "title": "Error in processing the request", "description": [ "WildCard search is not supported on sub-zone or tld" ] ...
Eh oui, les requêtes ouvertes comme celle-ci posent à la fois des problèmes techniques (la charge du serveur) et politico-juridiques (la capacité à extraire de grandes quantités de la base de données). Elles sont donc typiquement utilisables seulement après une authentification.
On peut aussi chercher un domaine d'après ses serveurs de noms,
par exemple
https://rdap.example.net/domains?nsLdhName=ns1.example.com
chercherait tous les domaines délégués au serveur DNS
ns1.example.com
. Une telle fonction peut être
jugée très indiscrète et le serveur RDAP est toujours libre de
répondre ou pas mais, ici, cela marche, on trouve bien le domaine
qui a ce serveur de noms :
% curl https://rdap.afilias.net/rdap/info/domains\?nsLdhName=ns0.abul.org ... "domainSearchResults": [ { "objectClassName": "domain", "handle": "D10775367-LRMS", "ldhName": "rmll.info", ...
Deux autres types permettent ces recherches ouvertes,
/nameservers
(comme dans
https://rdap.example.net/nameservers?ip=2001:db8:42::1:53
,
mais notez qu'on peut aussi chercher un serveur par son nom) et
/entities
(comme dans
https://rdap.example.net/entities?fn=Jean%20Dupon*
) :
% curl http://rdg.afilias.info/rdap/entities\?fn=go\* { "entitySearchResults": [ { "fn": "Go China Domains, Inc.", ... "fn": "Gotnames.ca Inc.", ...
Notez que ce type de recherche peut représenter un sérieux danger pour la vie privée (comme noté dans le RFC, par exemple en section 4.2) puisqu'elle permet, par exemple de trouver tous les titulaires prénommés Jean. Elle est donc parfois uniquement accessible à des clients authentifiés, et de confiance.
La section 4 détaille le traitement des requêtes. N'oubliez pas qu'on travaille ici sur HTTP et que, par défaut, les codes de retour RDAP suivent la sémantique HTTP (404 pour un objet non trouvé, par exemple). Il y a aussi quelques cas où le code à retourner est moins évident. Ainsi, si un serveur ne veut pas faire une recherche ouverte, il va répondre 422 (Unprocessable Entity).
Vous avez noté plus haut, mais la section 6 le rappelle aux
distraits, que le nom de domaine peut être exprimé en
Unicode ou en ASCII. Donc,
https://rdap.example.net/domain/potamochère.fr
et
https://rdap.example.net/domain/xn--potamochre-66a.fr
sont deux requêtes acceptables.
Enfin, la section 8 rappelle quelques règles de sécurité comme :
Les changements depuis le RFC 7482 sont peu nombreux et sont surtout de clarification.
Date de publication du RFC : Août 2021
Auteur(s) du RFC : M. Boutier, J. Chroboczek (IRIF, University of Paris)
Chemin des normes
Réalisé dans le cadre du groupe de travail IETF babel
Première rédaction de cet article le 29 août 2021
Traditionnellement, le routage dans l'Internet se fait sur la base de l'adresse IP de destination. Mais on peut aussi envisager un routage où l'adresse IP source est prise en compte. Ce nouveau RFC étend le protocole de routage Babel en lui ajoutant cette possibilité.
Babel, normalisé dans le RFC 8966 est un protocole de routage à vecteur de
distance. Il est prévu par défaut pour du routage
IP traditionnel, où
le protocole de routage construit une table de transmission qui
associe aux préfixes IP de destination le routeur suivant
(next hop). Un paquet
arrive dans un routeur ? Le routeur regarde
dans sa table le préfixe le plus spécifique qui correspond à
l'adresse IP de destination (utilisant pour cela une structure de
données comme, par exemple, le Patricia trie)
et, lorsqu'il le trouve, la table contient les coordonnées du
routeur suivant. Par exemple, si une entrée de la table dit que
2001:db8:1:fada::/64
a pour routeur suivant
fe80::f816:3eff:fef2:47db%eth0
, un paquet
destiné à 2001:db8:1:fada::bad:1234
sera
transmis à ce fe80::f816:3eff:fef2:47db
, sur
l'interface réseau eth0
. Cette transmission
classique a de nombreux avantages : elle est simple et rapide, et
marche dans la plupart des cas.
Mais ce mécanisme a des limites. On peut vouloir prendre en compte d'autres critères que l'adresse de destination pour la transmission d'un paquet. Un exemple où un tel critère serait utile est le multihoming. Soit un réseau IP qui est connecté à plusieurs opérateurs, afin d'assurer son indépendance et pour faire face à un éventuel problème (technique ou commercial) chez l'un des opérateurs. La méthode officielle pour cela est d'acquérir des adresses indépendantes du fournisseur et de faire router ces adresses par les opérateurs auxquels on est connectés (par exemple en leur parlant en BGP). Mais avoir de ces adresses PI (Provider-Independent) est souvent difficile. Quand on est passionnée, on y arrive mais ce n'est pas vraiment accessible à tout le monde. On se retrouve donc en général avec des adresses dépendant du fournisseur. C'est par exemple le cas de loin le plus courant pour le particulier, la petite association ou la PME. Dans ce cas, le multihoming est compliqué. Mettons que les deux fournisseurs d'accès se nomment A et B et que chacun nous fournisse un préfixe d'adresses IP. Si une machine interne a deux adresses IP, chacune tirée d'un des préfixes, et qu'elle envoie vers l'extérieur un paquet ayant comme adresse source une adresse de B, que se passe-t-il ? Si le paquet est envoyé via le fournisseur B, tout va bien. Mais s'il est envoyé via le fournisseur A, il sera probablement jeté par le premier routeur de A, puisque son adresse IP source ne sera pas une adresse du fournisseur (RFC 3704 et RFC 8704). Pour éviter cela, il faudrait pouvoir router selon l'adresse IP source, envoyant les paquets d'adresse source A vers le fournisseur A et ceux ayant une adresse source B vers B. En gros, soit l'information de routage influe sur le choix de l'adresse source, soit c'est le choix de l'adresse source qui influe sur le routage. On ne peut pas faire les deux, ou alors on risque une boucle de rétroaction. Ce RFC choisit la deuxième solution.
La première solution serait que la machine émettrice choisisse l'adresse IP source selon que les adresses de destination soient routées vers A ou vers B. Le RFC 3484 explique comment le faire en séquentiel et le RFC 8305 le fait en parallèle (algorithme dit des « globes oculaires heureux »). Mais les systèmes actuels ne savent typiquement pas le faire et, de toute façon, cela ne réglerait pas le cas où le routage change pendant la vie d'une connexion TCP. TCP, contrairement à QUIC ou SCTP, ne supporte pas qu'une adresse IP change en cours de route, sauf à utiliser les extensions du RFC 8684. Une dernière solution serait de laisser les applications gérer cela (cf. RFC 8445) mais on ne peut pas compter que toutes les applications le fassent. Bref, le routage tenant compte de la source reste souvent la meilleure solution.
À part le cas du multihoming, d'autres usages peuvent tirer profit d'un routage selon la source. Ce routage peut simplifier la configuration des tunnels. Il peut aussi être utile dans le cas de l'anycast. Lorsqu'on utilise du routage classique, fondé uniquement sur la destination, il est difficile de prédire quels clients du groupe anycast seront servis par quelle instance du groupe. Cela rend la répartition de charge compliquée (il faut jouer avec les paramètres des annonces BGP, par exemple) et cela pose des problèmes aux protocoles avec état, comme TCP, où un même client doit rester sur la même instance du groupe anycast. (Des utilisations de l'anycast pour des services sans état, comme le DNS sur UDP, n'ont pas ce problème.) Au contraire, avec du routage fait selon la source, chaque instance anycast peut annoncer sa route avec le ou les préfixes IP qu'on veut que cette instance serve.
D'ailleurs, si vous voulez en savoir plus sur le routage selon la source (ou SADR Source-Address Dependent Routing, également appelé SSR pour Source-Specific Routing), vous pouvez lire l'article détaillé des auteurs du RFC, « Source-Specific Routing ». On peut aussi noter que ce routage dépendant de la source n'est pas la même chose qu'un routage décidé par la source (source routing), qui indiquerait une liste de routeurs par où passer. Ici, chaque routeur reste maitre du saut suivant (sur le routage décidé par la source, voir RFC 8354, et un exemple dans le RFC 6554).
Bon, le routage tenant compte de la source est intéressant,
OK. Mais, avant de s'y lancer, il faut faire attention à un petit
problème, la spécificité des routes. Lorsqu'on route uniquement
selon la destination, et que plusieurs routes sont possibles, la
règle classique d'IP est celle de la spécificité. La route avec le
préfixe le plus spécifique (le plus long) gagne. Mais que se passe
t-il si on ajoute l'adresse source comme critère ? Imaginons qu'on
ait deux routes, une pour la destination
2001:db8:0:1::/64
et s'appliquant à toutes les
sources et une autre route pour toutes les destinations (route par
défaut), ne s'appliquant qu'à la source
2001:db8:0:2::/64
. On va noter ces deux routes
sous forme d'un tuple (destination, source) donc, ici,
(2001:db8:0:1::/64
, ::/0
)
et (::/0
,
2001:db8:0:2::/64
). Pour un paquet venant de
2001:db8:0:2::1
et allant en
2001:db8:0:1::1
, quelle est la route la plus
spécifique ? Si on considère la spécificité de la destination
d'abord, la route choisie sera la
(2001:db8:0:1::/64
,
::/0
). Mais si on regarde la spécificité de la
source en premier, ce sera l'autre route, la (::/0
,
2001:db8:0:2::/64
) qui sera adoptée. Doit-on
laisser chaque machine libre de faire du destination d'abord ou du
source d'abord ? Non, car tous les routeurs du domaine doivent
suivre la même règle, autrement des boucles pourraient se former, un
paquet faisant du ping-pong éternel entre un routeur « destination
d'abord » et un routeur « source d'abord ». Babel impose donc une
règle : pour déterminer la spécificité d'une route, on regarde la
destination d'abord. Le choix n'est pas arbitraire, il correspond à
des topologies réseau typique, avec des réseaux locaux pour
lesquelles on a une route spécifique, et une route par défaut pour
tout le reste.
Un autre point important est celui du système de transmission des paquets. Il faut en effet rappeler que le terme « routage » est souvent utilisé pour désigner deux choses très différentes. La première est le routage à proprement parler (routing en anglais), c'est-à-dire la construction des tables de transmission, soit statiquement, soit avec des protocoles de routage comme Babel. La seconde est la transmission effective des paquets qui passent par le routeur (forwarding en anglais). Si on prend un routeur qui tourne sur Unix avec le logiciel babeld, ce dernier assure bien le routage au sens strict (construire les tables de routage grâce aux messages échangés avec les autres routeurs) mais c'est le noyau Unix qui s'occupe de la transmission. Donc, si on veut ajouter des fonctions de routage tenant compte de la source, il ne suffit pas de le faire dans le programme qui fait du Babel, il faut aussi s'assurer que le système de transmission en est capable, et avec la bonne sémantique (notamment l'utilisation de la destination d'abord pour trouver la route la plus spécifique). Si Babel, avec les extensions de ce RFC, tourne sur un système qui ne permet pas cela, il faut se résigner à ignorer les annonces spécifiques à une source. (Pour des systèmes comme Linux, qui permet le routage selon la source mais avec une mauvaise sémantique, puisqu'il utilise la source d'abord pour trouver la route la plus spécifique, la section V-B de l'article des auteurs cité plus haut fournit un algorithme permettant de créer des tables de transmission correctes.)
Le passage du routage classique au routage tenant compte de la
source nécessite de modifier les structures de données du protocole
de routage, ici Babel (section 3 du RFC). Les différentes structures
de données utilisées par une mise en œuvre de Babel doivent être
modifiées pour ajouter le préfixe de la source, et plusieurs des
TLV dans les messages que s'échangent les
routeurs Babel doivent être étendus avec un sous-TLV (RFC 8966, section 4.4) qui contient un préfixe
source. La section 5 du RFC détaille les modifications nécessaires
du protocole Babel. Les trois types de TLV
qui indiquent un préfixe de destination, Updates,
Route Requests et Seqno,
doivent désormais transporter en plus un sous-TLV indiquant le
préfixe source, marqué comme obligatoire (RFC 8966, section 4.4), de façon à ce qu'une version de Babel
ne connaissant pas le routage selon la source n'accepte pas des
annonces incomplètes. Si une annonce ne dépend pas de l'adresse IP
source, il ne faut pas envoyer un sous-TLV avec le préfixe
::/0
mais au contraire ne pas mettre de
sous-TLV (les autres versions de Babel accepteront alors
l'annonce).
Ainsi, des versions de Babel gérant les extensions de notre RFC 9079 et d'autres qui ne les gèrent pas (fidèles au RFC 8966) pourront coexister sur le même réseau, sans que des boucles de routage ne se forment. En revanche, comme la notion de sous-TLV obligatoire n'est apparue qu'avec le RFC 8966, les vieilles versions de Babel, qui suivent l'ancien RFC 6126, ne doivent pas être sur le même réseau que les routeurs à routage dépendant de la source.
Le nouveau sous-TLV Source Prefix a le type 128 et sa valeur comporte un préfixe d'adresses IP, encodé en {longueur, préfixe}.
Notez que si les routeurs qui font du routage selon la source n'annoncent que des routes ayant le sous-TLV indiquant un préfixe source, les routeurs qui ne gèrent pas ce routage selon la source, et qui ignorent donc cette annonce, souffriront de famine : il n'y aura pas de routes vers certaines destinations, conduisant le routeur à jeter les paquets. Il peut donc être prudent, par exemple, d'avoir une route par défaut sans condition liée à la source, pour ces routeurs qui ignorent cette extension.
Question sécurité, il faut signaler (section 9 du RFC), que cette extension au routage apporte davantage de souplesse, et que cela peut nécessiter une révision des règles de sécurité, si celles-ci supposaient que tous les paquets vers une destination donnée suivaient le même chemin. Ainsi, le filtrage des routes (RFC 8966, annexe C) pourra être inutile si des routes spécifiques à une source sont présentes. Autre supposition qui peut être désormais fausse : une route spécifique à une machine de destination (host route, c'est-à-dire un préfixe /128) n'est plus forcément obligatoire, une autre route pour certaines sources peut prendre le dessus.
Au moins deux mises en œuvre de cette extension existent, dans babeld depuis la version 1.6 (en IPv6 seulement), et dans BIRD depuis la 2.0.2.
Merci à Juliusz Chroboczek pour sa relecture, et pour ses bonnes remarques sur le choix entre le routage selon la source et les autres solutions.
Date de publication du RFC : Août 2021
Auteur(s) du RFC : D. Crocker (Brandenburg InternetWorking), R. Signes (Fastmail), N. Freed (Oracle)
Expérimental
Première rédaction de cet article le 5 août 2021
Vous trouvez que le courrier électronique, c'est vieux et ringard, et que les réseaux sociaux avec leurs possibilités de réaction (« J'aime ! », « Je partage ! », « Je rigole ! »), c'est mieux ? Et bien, vous n'êtes pas le seul ou la seule. Ce nouveau RFC normalise un mécanisme pour mettre des réactions courtes et impulsives dans le courrier électronique.
L'idée de base est que, si ce RFC plait et est mis en œuvre par les auteurs de MUA, on verra près du message qu'on lit un menu avec des émojis, et on cliquera dessus, et l'émetteur du message verra sur son propre MUA le message qu'il a envoyé accompagné de ces réactions. (Le RFC ne normalise pas une apparence particulière à ces réactions, cf. section 5.2 pour des idées qui ne sont que des suggestions.)
Bien sûr, on peut déjà répondre à un message avec des émojis dans le corps de la réponse. Mais l'idée est de structurer cette réaction pour permettre une utilisation plus proche de celle des réseaux sociaux, au lieu que la réaction soit affichée comme un message comme les autres. Liée au message originel, cette structuration permettra, par exemple, au MUA de l'émetteur originel de voir le nombre de Likes de son message… (Quant à savoir si c'est utile, c'est une autre histoire…)
Techniquement, cela fonctionnera avec la combinaison des en-têtes
Content-Disposition:
et
In-Reply-To:
. Voici un exemple montrant le
format d'une réaction (négative…) à un message (le
12345@example.com
) :
To: author@example.com From: recipient@example.org Date: Today, 29 February 2021 00:00:10 -800 Message-id: 56789@example.org In-Reply-To: 12345@example.com Subject: Re: Meeting Mime-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Disposition: Reaction 👎
(Si vous n'avez pas les bons émojis sur votre système, le caractère
en question était le pouce vers le
bas.) Recevant cette réponse, le MUA de l'auteur
(author@example.com
) peut rechercher le message
12345@example.com
et afficher que
recipient@example.org
n'est pas enthousiaste.
Le paramètre Reaction
pour l'en-tête
Content-Disposition:
(RFC 2183) a été ajouté
au registre IANA.
Le texte dans la partie MIME qui a l'en-tête
Content-Disposition: Reaction
est une ligne
d'émojis. On
peut utiliser les séquences
d'émojis. Tous les émojis sont utilisables mais le RFC en
liste cinq qui sont particulièrement importants car considérés comme
la base, le minimum que devrait reconnaitre tout MUA :
Notez que le concept de séquence d'émojis n'est pas simple. Ce concept permet d'éviter de normaliser des quantités astronomiques d'émojis, en autorisation la combinaison d'émojis. Il est utilisé entre autres pour les drapeaux nationaux, ainsi le drapeau libanais sera 🇱🇧 (U+1F1F1 qui indique le L du code pays ISO 3166 et U+1F1E7 qui indique le B). Le RFC rappelle qu'écrire du code dans son application pour gérer ces séquences n'est pas raisonnable et qu'il vaut mieux utiliser une bibliothèque Unicode existante.
Les sections 4 et 5 du RFC donnent quelques idées aux auteurs de MUA sur la gestion de ces réactions. Normalement, l'IETF normalise des protocoles, pas des interfaces utilisateur. Mais cela n'interdit pas de parler un peu d'UX dans le RFC, comme indiqué plus haut. En outre, l'interface utilisateur vers le courrier est typiquement assez différente de celle des réseaux sociaux où ce concept de réaction existe. Ainsi, le RFC ne tranche pas sur la question de savoir s'il faut envoyer la réaction uniquement à l'auteur original du message, ou bien à tous les destinataires. Ou bien s'il faut envoyer un message contenant juste la partie Réaction ou si on peut la combiner avec un autre contenu. Et que faire si, depuis la même adresse, on reçoit plusieurs réactions, éventuellement contradictoires ? Ne garder que la dernière (attention, le courrier électronique ne conserve pas l'ordre d'envoi) ? Les additionner ?
Toujours en UX, le RFC note que la réception d'une image dépend beaucoup du récepteur. (Et, contrairement à ce que laisse entendre le RFC, ce n'est pas juste une question de « culture », des personnes de la même « culture » peuvent comprendre différemment la même image.) Il faut donc faire attention aux réactions, qui peuvent être mal comprises. (Ceci dit, c'est exactement pareil avec le texte seul.) Et, comme toujours sur l'Internet, cette possibilité pourra ouvrir de nouveaux problèmes de sécurité (utilisation pour l'hameçonnage ?).
Date de publication du RFC : Juillet 2021
Auteur(s) du RFC : P. van Dijk (PowerDNS)
Chemin des normes
Réalisé dans le cadre du groupe de travail IETF dnsop
Première rédaction de cet article le 25 juillet 2021
Ce nouveau RFC corrige une légère bavure. Lorsqu'on utilise la mémorisation énergique du RFC 8198 pour synthétiser des réponses DNS en utilisant les informations DNSSEC, les normes existantes permettaient une mémorisation pendant une durée bien trop longue. Cette erreur (peu grave en pratique) est désormais corrigée.
Rappelons que le principe du RFC 8198 est
d'autoriser un résolveur DNS à synthétiser
des informations qu'il n'a normalement pas dans sa mémoire,
notamment à partir des enregistrements NSEC
de
DNSSEC (RFC 4034,
section 4). Si un résolveur interroge la racine du DNS :
% dig @a.root-servers.net A foobar ... ;; ->>HEADER<<- opcode: QUERY, status: NXDOMAIN, id: 37332 ... foo. 86400 IN NSEC food. NS DS RRSIG NSEC
La réponse est négative (NXDOMAIN
)
et un enregistrement NSEC annonce au client DNS
qu'il n'y a pas de nom dans la racine entre
.foo
et .food
. Si le
résolveur mémorise cette information et qu'on lui demande par la
suite un nom en .foocat
, il n'aura pas besoin
de contacter la racine, il sait, en raison de l'enregistrement NSEC
que ce nom ne peut pas exister.
Bon, mais combien de temps le résolveur peut-il mémoriser cette
non-existence ? Le RFC 2308 disait qu'une
réponse négative (NXDOMAIN
) pouvait être
mémorisée pendant une durée indiquée par le
minimum du champ Minimum
de l'enregistrement SOA et du TTL de ce même enregistrement
SOA. Mais le RFC 4034, normalisant DNSSEC,
disait que l'enregistrement NSEC devait avoir un TTL égal au champ
Minimum
du SOA. Dans l'exemple de la racine du
DNS, à l'heure actuelle, cela ne change rien, ces durées sont toutes
égales. Mais elles pourraient être différentes. Si un enregistrement
SOA a un Minimum
à une journée mais un TTL
d'une heure, le RFC 2308 impose une heure de
mémorisation au maximum, le RFC 4034
permettait une journée… Ça pourrait même être exploité pour une
attaque en faisant des requêtes qui retournent des enregistrements
NSEC, afin de nier l'existence d'un nom pendant plus longtemps que
prévu. [Bon, dans le monde réel, je trouve que
c'est un problème assez marginal mais ce n'est pas une raison pour ne
pas le corriger.]
Le RFC 8198 avait déjà tenté de corriger le
problème mais sans y réussir. Notre nouveau RFC impose désormais
clairement que, contrairement à ce que dit le RFC 4034 (et deux ou trois autres RFC sur DNSSEC), la durée
maximale de mémorisation est bien le minimum du
champ Minimum
de l'enregistrement
SOA et du TTL
de ce même enregistrement SOA.
Si les logiciels que vous utilisez pour signer les zones ne
peuvent pas être corrigées immédiatement, le RFC demande que vous
changiez le contenu de la zone pour mettre la même valeur au champ
Minimum
de l'enregistrement
SOA et au TTL
de ce même enregistrement SOA.
En pratique, les signeurs suivants ont déjà été corrigés :
Date de publication du RFC : Juillet 2021
Auteur(s) du RFC : T. Wicinski
Pour information
Réalisé dans le cadre du groupe de travail IETF dprive
Première rédaction de cet article le 24 juillet 2021
La surveillance généralisée sur l'Internet est un gros problème pour la vie privée. L'IETF s'active donc à améliorer la protection de la vie privée contre cette surveillance (RFC 7258). Un protocole qui avait souvent été négligé dans ce travail est le DNS. Ce RFC décrit les problèmes de vie privée liés au DNS. Il remplace le RFC 7626, avec deux changements important, l'intégration des techniques de protection développées et déployées depuis l'ancien RFC et, malheureusement, beaucoup de propagande anti-chiffrement imposée par les acteurs traditionnels des résolveurs DNS qui ne veulent pas se priver de leurs possibilités de contrôle et de surveillance.
Ce RFC est en fait à la croisée de deux activités. L'une d'elles consiste à documenter les problèmes de vie privée, souvent ignorés jusqu'à présent dans les RFC. Cette activité est symbolisée par le RFC 6973, dont la section 8 contient une excellente analyse de ces problèmes, pour un service particulier (la présence en ligne). L'idée est que, même si on ne peut pas résoudre complètement le problème, on peut au moins le documenter, pour que les utilisateurs soient conscients des risques. Et la seconde activité qui a donné naissance à ce RFC est le projet d'améliorer effectivement la protection de la vie privée des utilisateurs du DNS, en marchant sur deux jambes : minimiser les données envoyées et les rendre plus résistantes à l'écoute, via le chiffrement. L'objectif de diminution des données a débouché sur la QNAME minimization spécifiée dans le RFC 9156 et le chiffrement a donné DoT (RFC 7858) et DoH (RFC 8484).
Donc, pourquoi un RFC sur les questions de vie privée dans le
DNS ? Ce dernier est un très ancien protocole, dont l'une des
particularités est d'être mal spécifié : aux deux RFC originaux, les
RFC 1034 et RFC 1035, il
faut ajouter dix ou vingt autres RFC dont la lecture est nécessaire
pour tout comprendre du DNS. Et aucun travail de consolidation n'a
jamais été fait, contrairement à ce qui a eu lieu pour XMPP,
HTTP ou
SMTP. Or, le DNS est crucial, car quasiment
toutes les transactions Internet mettent en jeu au moins une requête
DNS (ne me dites pas des bêtises du genre « moi, je télécharge avec
BitTorrent, je
n'utilise pas le DNS » : comment allez-vous sur
thepiratebay.am
?) Mais, alors que les
questions de vie privée liées à HTTP ont fait l'objet d'innombrables articles
et études, celles liées au DNS ont été largement ignorées pendant
longtemps (voir la bibliographie du RFC pour un état de
l'art). Pourtant, on peut découvrir bien des choses sur votre
activité Internet uniquement en regardant le trafic DNS.
Une des raisons du manque d'intérêt pour le thème « DNS et vie privée » est le peu de compétences concernant le DNS : le protocole est souvent ignoré ou mal compris. C'est pourquoi le RFC doit commencer par un rappel (section 1) du fonctionnement du DNS.
Je ne vais pas reprendre tout le RFC ici. Juste quelques rappels
des points essentiels du DNS : il existe deux sortes de serveurs
DNS, qui n'ont pratiquement aucun rapport. Il y a les
serveurs
faisant autorité et les résolveurs. Les premiers
sont ceux qui connaissent de première main l'information pour une
zone DNS donnée (comme fr
ou
wikipedia.org
). Ils sont gérés par le titulaire
de la zone ou bien sous-traités à un hébergeur DNS. Les seconds, les
résolveurs, ne connaissent rien (à part l'adresse IP des serveurs de la racine). Ils
interrogent donc les serveurs faisant autorité, en partant de la
racine. Les résolveurs sont gérés par le FAI ou le service
informatique qui s'occupe du réseau local de l'organisation. Ils
peuvent aussi être individuels, ou bien au
contraire être de gros serveurs publics comme Google Public DNS, gros fournisseur de la
NSA. Pour
prendre un exemple concret (et en simplifiant un peu), si M. Michu
veut visiter le site Web
http://thepiratebay.am/
, son navigateur va
utiliser les services du système d'exploitation sous-jacent pour
demander l'adresse IP de
thepiratebay.am
. Le système d'exploitation va
envoyer une requête DNS au résolveur (sur
Unix, les adresses IP des résolveurs sont
dans /etc/resolv.conf
). Celui-ci va demander
aux serveurs de la racine s'ils connaissent
thepiratebay.am
, il se fera rediriger vers les
serveurs faisant autorité pour am
, puis vers ceux faisant
autorité pour thepiratebay.am
. Le résolveur
aura alors une réponse qu'il pourra transmettre au navigateur de
M. Michu.
Principal point où j'ai simplifié : le DNS s'appuie beaucoup sur
la mise en cache des données, c'est-à-dire sur
leur mémorisation pour gagner du temps la fois suivante. Ainsi, si
le même M. Michu, cinq minutes après, veut aller en
http://armenpress.am/
, son résolveur ne
demandera rien aux serveurs de la racine : il sait déjà quels sont
les serveurs faisant autorité pour am
.
Le trafic DNS est un trafic IP ordinaire, typiquement porté par UDP. Mais il peut aussi fonctionner sur TCP et bientôt sur QUIC. Il peut être écouté, et comme il n'est pas toujours chiffré, un indiscret peut tout suivre. Voici un exemple pris avec tcpdump sur un serveur racine (pas la racine officielle, mais, techniquement, cela ne change rien) :
15:29:24.409283 IP6 2001:67c:1348:8002::7:107.10127 > \ 2001:4b98:dc2:45:216:3eff:fe4b:8c5b.53: 32715+ [1au] \ AAAA? www.armenpress.am. (46)
On y voit que le client
2001:67c:1348:8002::7:107
a demandé l'adresse
IPv6 de
www.armenpress.am
.
Pour compléter le tableau, on peut aussi noter que les logiciels
génèrent un grand nombre de requêtes DNS, bien supérieur à ce que
voit l'utilisateur. Ainsi, lors de la visite d'une page Web, le
résolveur va envoyer la requête primaire (le
nom du site visité, comme thepiratebay.am
), des
requêtes secondaires dues aux objets contenus
dans la page Web (JavaScript, CSS, divers
traqueurs et autres outils de cyberflicage ou de cyberpub) et même
des requêtes tertiaires, lorsque le
fonctionnement du DNS lui-même nécessitera des requêtes. Par
exemple, si abc.xyz
est hébergé sur des
serveurs dans google.com
, une visite de
http://abc.xyz/
nécessitera de résoudre les
noms comme ns1.google.com
, donc de faire des
requêtes DNS vers les serveurs de
google.com
.
Bien sûr, pour un espion qui veut analyser tout cela, le trafic DNS représente beaucoup de données, souvent incomplètes en raison de la mise en cache, et dont l'interprétation peut être difficile (comme dans l'exemple ci-dessus). Mais les organisations qui pratiquent l'espionnage massif, comme la NSA, s'y connaissent en matière de big data et savent trouver les aiguilles dans les bottes de foin.
Les sections 3 à 7 du RFC détaille les risques pour la vie privée
dans les différents composants du DNS. Notez que la confidentialité
du contenu du DNS n'est pas prise en compte (elle l'est dans les
RFC 5936 et RFC 5155). Il est important de noter qu'il y a une énorme
différence entre la confidentialité du contenu
et la confidentialité des requêtes. L'adresse
IP de www.charliehebdo.fr
n'est pas un secret : les données DNS sont publiques, dès qu'on
connait le nom de domaine, et tout le monde peut faire une requête
DNS pour la connaitre. Mais le fait que vous fassiez une requête
pour ce nom ne devrait pas être public. Vous n'avez pas forcément
envie que tout le monde le sache. (On peut quand même nuancer un peu
le côté « public » des données DNS : on peut avoir des noms de
domaine purement internes à une organisation. Mais ces noms
« fuitent » souvent, par la marche sur la zone décrite dans le RFC 4470, ou via des systèmes de « passive DNS).
Pour comprendre les risques, il faut aussi revenir un peu au protocole DNS. Les deux informations les plus sensibles dans une requête DNS sont l'adresse IP source et le nom de domaine demandé (qname, pour Query Name, cf. RFC 1034, section 3.7.1). L'adresse IP source est celle de votre machine, lorsque vous parlez au résolveur, et celle du résolveur lorsqu'il parle aux serveurs faisant autorité. Elle peut indiquer d'où vient la demande. Lorsque on utilise un gros résolveur, celui-ci vous masque vis-à-vis des serveurs faisant autorité (par contre, ce gros résolveur va avoir davantage d'informations). Ceci dit, l'utilisation d'ECS (RFC 7871) peut trahir votre adresse IP, ou au moins votre préfixe. (Cf. cette analyse d'ECS. D'autre part, certains opérateurs se permettent d'insérer des informations comme l'adresse MAC dans des options EDNS de la requête.)
Quant au qname, il peut être très révélateur :
il indique les sites Web que vous visitez, voire, dans certains cas,
les logiciels utilisés. Au moins un client
BitTorrent fait des requêtes DNS pour
_bittorrent-tracker._tcp.domain.example
,
indiquant ainsi à beaucoup de monde que vous utilisez un protocole
qui ne plait pas aux ayant-droits. Et si vous utilisez le RFC 4255, pas mal de serveurs verront à quelles
machines vous vous connectez en SSH...
Donc où un méchant qui veut écouter votre trafic DNS peut-il se placer ? D'abord, évidemment, il suffit qu'il écoute le trafic réseau. On l'a dit, le trafic DNS aujourd'hui est souvent en clair donc tout sniffer peut le décoder. Même si vous utilisez HTTPS pour vous connecter à un site Web, le trafic DNS, lui, ne sera pas chiffré. (Les experts pointus de TLS noteront qu'il existe d'autres faiblesses de confidentialité, comme le SNI du RFC 6066, qui n'est pas encore protégé, cf. RFC 8744.) À noter une particularité du DNS : le trafic DNS peut passer par un autre endroit que le trafic applicatif. Alice peut naïvement croire que, lorsqu'elle se connecte au serveur de Bob, seul un attaquant situé physiquement entre sa machine et celle de Bob représente une menace. Alors que son trafic DNS peut être propagé très loin, et accessible à d'autres acteurs. Si vous utilisez, par exemple, le résolveur DNS public de FDN, toute la portion de l'Internet entre vous et FDN peut facilement lire votre trafic DNS.
Donc, l'éventuel espion peut être près du câble, à écouter. Mais il peut être aussi dans les serveurs. Bercé par la musique du cloud, on oublie souvent cet aspect de la sécurité : les serveurs DNS voient passer le trafic et peuvent le copier. Pour reprendre les termes du RFC 6973, ces serveurs sont des assistants : ils ne sont pas directement entre Alice et Bob mais ils peuvent néanmoins apprendre des choses à propos de leur conversation. Et l'observation est très instructive. Elle est utilisée à de justes fins dans des systèmes comme DNSDB (section 6 du RFC) mais il n'est pas difficile d'imaginer des usages moins sympathiques comme dans le cas du programme NSA MORECOWBELL.
Les résolveurs voient tout le trafic puisqu'il y a peu de mise en cache en amont de ces serveurs. Il faut donc réfléchir à deux fois avant de choisir d'utiliser tel ou tel résolveur ! Il est déplorable qu'à chaque problème DNS (ou supposé tel), des ignorants bondissent sur les réseaux sociaux pour dire « zyva, mets 8.8.8.8 [Google Public DNS] comme serveur DNS et ça ira plus vite » sans penser à toutes les données qu'ils envoient à la NSA ainsi.
Les serveurs faisant autorité voient passer moins de trafic (à cause des caches des résolveurs) mais, contrairement aux résolveurs, ils n'ont pas été choisis délibérement par l'utilisateur. Celui-ci peut ne pas être conscient que ses requêtes DNS seront envoyées à plusieurs acteurs du monde du DNS, à commencer par la racine. Le problème est d'autant plus sérieux que, comme le montre une étude, la concentration dans l'hébergement DNS est élevée : dix gros hébergeurs hébergent le tiers des domaines des 100 000 sites Web les plus fréquentés (listés par Alexa).
Au passage, le lecteur attentif aura noté qu'un résolveur personnel (sur sa machine ou dans son réseau local) a l'avantage de ne pas envoyer vos requêtes à un résolveur peut-être indiscret mais l'inconvénient de vous démasquer vis-à-vis des serveurs faisant autorité, puisque ceux-ci voient alors votre adresse IP. Une bonne solution (qui serait également la plus économe des ressources de l'Internet) serait d'avoir son résolveur local et de faire suivre les requêtes non résolues au résolveur du FAI. Du point de vue de la vie privée, ce serait sans doute la meilleure solution mais cela ne résout hélas pas un autre problème, celui des DNS menteurs, contre lesquels la seule protection est d'utiliser uniquement un résolveur de confiance. On peut résoudre ce problème en ayant son propre résolveur mais qui fait suivre à un résolveur public de confiance. Notez que la taille compte : plus un résolveur est petit, moins il protège puisque ses requêtes sortantes ne seront dues qu'à un petit nombre d'utilisateurs.
Enfin, il y a aussi les serveurs DNS « pirates » (installés, par
exemple, via un serveur DHCP lui-même pirate) qui détournent le trafic
DNS, par exemple à des fins de surveillance. Voir par exemple
l'exposé de Karrenberg à JCSA
2012 disponible
en ligne (transparents 27 et 28, Unknown
et Other qui montrent l'existence de clones
pirates du serveur racine
K.root-servers.net
).
Pour mes lecteurs en France férus de droit, une question intéressante : le trafic DNS est-il une « donnée personnelle » au sens de la loi Informatique & Libertés ? Je vous laisse plancher sur la question, qui a été peu étudiée.
Les changements depuis le RFC 7626 sont résumés dans l'annexe A. Outre des retours d'expérience basés sur le déploiement de solutions minimisant et/ou chiffrant les données, ils consistent essentiellement en remarques négatives sur le chiffrement, défendant le mécanisme traditionnel de résolution DNS. Il n'y avait d'ailleurs pas forcément d'urgence à sortir un nouveau RFC. Comme le demandait Alissa Cooper, l'auteure du RFC 6973, « Why not wait to see how QUIC, DOH, ADD, ODNS, etc. shake out in the next few years and take this up then? ». C'est en raison de ces changements négatifs que je ne suis pas cité comme auteur du RFC (contraitement à son prédécesseur). Sara Dickinson, qui avait géré l'essentiel du travail pour les débuts de ce nouveau RFC, s'est également retirée, en raison de la dureté des polémiques qui ont déchiré l'IETF sur ce document.
Depuis la sortie du RFC 7626, il y a eu un certain nombre de déploiement de techniques améliorant la protection de la vie privée dans le cas du DNS, notamment la minimisation des données (RFC 9156) et le chiffrement (DoT, RFC 7858, DoH, RFC 8484, et le futur DoQ). Cela permet des retours d'expérience. Par exemple, sur l'ampleur du déploiement de la minimisation, ou sur les performances du chiffrement (voir « An End-to-End, Large-Scale Measurement of DNS-over-Encryption »). Il y a eu également des discussions politiques à propos de ces techniques et de leur déploiement, souvent malhonnêtes ou confuses (et ce RFC en contient plusieurs) comme l'accusation de « centralisation », qui sert surtout à protéger l'état actuel, où l'opérateur de votre réseau d'accès à l'Internet peut à la fois vous surveiller et contrôler ce que vous voyez, via sa gestion de la résolution DNS. Mais il est vrai que rien n'est simple en matière de sécurité et que DoH (mais pas DoT) est trop bavard, puisqu'il hérite des défauts de HTTP, en envoyant trop d'informations au serveur.
Le RFC essaie de décourager les utilisateurs d'utiliser le chiffrement (dans l'esprit du RFC 8404) en notant que ce n'est pas une technologie parfaite (par exemple, l'observation des métadonnées peut donner des indications ou, autre exemple, une mauvaise authentification du résolveur peut vous faire envoyer vos requêtes à un méchant). C'est vrai mais c'est le cas de toutes les solutions de sécurité et on ne trouve pas de tels avertissements dans d'autres RFC qui parlent de chiffrement. L'insistance des opérateurs pour placer ces messages anti-chiffrement dans ce RFC montre bien, justement, l'importance de se protéger contre la curiosité des opérateurs.
Le RFC remarque également qu'il n'existe pas actuellement de moyen de découvrir automatiquement le résolveur DoT ou DoH. Mais c'est normal : un résolveur DoT ou DoH annoncé par le réseau d'accès, par exemple via DHCP, n'aurait guère d'intérêt, il aurait les mêmes défauts (et les mêmes qualités) que le résolveur traditionnel. DoT et DoH n'ont de sens qu'avec un résolveur configuré statiquement. Le RFC cite des déploiements de résolveurs DNS avec chiffrement faits par plusieurs FAI (aucun en France, notons-le). Cela montre une grosse incompréhension du problème : on ne chiffre pas pour le plaisir de chiffrer, mais parce que cela permet d'aller de manière sécurisée vers un autre résolveur. Chiffrer la communication avec le résolveur habituel ne fait pas de mal mais on ne gagne pas grand'chose puisque ce résolveur a les mêmes capacités de surveillance et de modification des réponses qu'avant. Notons que le RFC, inspiré par la propagande des telcos, raconte que le risque de surveillance est minimisé par le chiffrement du lien radio dans les réseaux 4G et après. Cela oublie le fait que la surveillance peut justement venir de l'opérateur.
Plus compliqué est le problème de l'endroit où se fait cette configuration. Traditionnellement, la configuration du résolveur à utiliser était faite globalement par le système d'exploitation. Ce n'est pas du tout une règle du protocole DNS (les RFC normalisant le DNS ne contrôlent que ce qui circule sur le câble, pas les choix locaux de chaque machine) et il serait donc absurde d'invoquer un soi-disant principe comme quoi le DNS serait forcément géré au niveau du système. Certains déploiements de DoH (celui de Firefox, par exemple), mettent le réglage dans l'application (en l'occurrence le navigateur Web). La question est discutable et discutée mais, de toute façon, elle ne relève pas de l'IETF (qui normalise ce qui passe sur le réseau) et il est anormal que le RFC en parle.
Le temps passé depuis le RFC 7626 a également permis la mise en service d'un plus grand nombre de résolveurs publics, comme Quad9 ou comme celui de Cloudflare. Mais il n'y a pas que les grosses boîtes états-uniennes qui gèrent des résolveurs DNS publics. Ainsi, je gère moi-même un modeste résolveur public. Le très intéressant RFC 8932 explique le rôle des politiques de vie privée dans ces résolveurs publics et comment en écrire une bonne. Bien sûr, comme toujours en sécurité, la lutte de l'épée contre la cuirasse est éternelle et des nouvelles actions contre la vie privée et la liberté de choix apparaissent, par exemple des réseaux qui bloquent l'accès à DoT (en bloquant son port 853) ou aux résolveurs DoH connus (DoH a été développé en partie pour être justement plus difficile à bloquer).
Date de publication du RFC : Juillet 2021
Auteur(s) du RFC : J. Arkko (Ericsson), S. Farrell (Trinity College Dublin), M. Kühlewind (Ericsson), C. Perkins (University of Glasgow)
Pour information
Première rédaction de cet article le 23 juillet 2021
La pandémie de Covid-19 en 2020 a affecté de nombreux aspects de notre vie. Concernant plus spécifiquement l'Internet, elle a accru l'utilisation d'outils de communication informatiques. Quels ont été les effets de ces changements sur l'Internet ? Un atelier de l'IAB fait le point sur ces effets et en tire des leçons. Je vous le dis tout de suite : contrairement à certains discours sensationnalistes, l'Internet n'a pas subi de conséquences sérieuses.
De janvier à mars 2020, de nombreux pays ont imposé un confinement plus ou moins strict. Des activités comme le travail ou l'éducation devaient se faire à distance, en utilisant les outils numériques. Certains politiciens ont tenu des discours dramatisants, prétendant que, si on ne renonçait pas à regarder ses vidéos favorites, l'Internet allait s'écrouler. Même si aucun professionnel des réseaux n'a repris ce discours, cela ne veut pas dire qu'il ne s'est rien passé. L'IAB a donc organisé en novembre 2020 un atelier (évidemment tenu en ligne car il n'y avait pas le choix) pour étudier les effets de ces confinements sur l'Internet, comprendre ce qui est arrivé et peut-être formuler des recommandations.
Donc, pendant le confinement et l'augmentation du télétravail qui en a résulté, le trafic Internet a changé. Il n'a pas toujours augmenté (les travailleurs utilisaient également Internet quand ils étaient au bureau, et les gens regardaient déjà Netflix avant la pandémie) mais il s'est déplacé : trafic de type différent, à des heures différentes, etc. Et, dans la plupart des pays, c'est arrivé assez soudainement, laissant peu de temps pour l'adaptation. La sécurité a été également très affectée, les mesures de sécurité conçues sur la base d'un lieu physique n'ayant plus de sens. Tout à coup, il a fallu autoriser beaucoup d'accès distants, avec tous les risques que cela impliquait. L'atelier lui-même a été différent des précédents ateliers de l'IAB, qui étaient fondés sur une participation physique pendant deux jours continus. Cette fois, l'atelier s'est fait en trois sessions à distance, avec respiration et réflexion entre les sessions.
Qu'ont observé les acteurs de l'Internet ? À l'atelier, certains FAI ou gérants de points d'échange Internet ont signalé des accroissements de 20 % du trafic. C'est à la fois beaucoup et peu. C'est beaucoup car c'est survenu soudainement, sans être étalé sur une période permettant le déploiement de nouvelles ressources, et c'est peu, car la croissance de l'utilisation des réseaux est un phénomène permanent : il faut toujours augmenter la capacité (20 % représente une augmentation annuelle typique, mais qui fut concentrée en quelques semaines). On voit ici le trafic sur le point d'échange Internet de Francfort (la source est ici). S'il y a bien une brusque montée début 2020 avec le démarrage du confinement, il faut noter qu'elle s'inscrit dans une augmentation du trafic Internet sur le long terme :
Cette montée du trafic lors du confinement est également relativisée sur les statistiques de trafic au point d'échange parisien (la source est ici) :
De même, l'article « Measurement of congestion on ISP interconnection links » mesure des moments de congestion limités aux États-Unis en mars. Cette croissance était très inégalement répartie selon les services. Vous ne serez pas surpris d'apprendre que certains opérateurs de services de vidéo-conférence ont vu leur activité tripler, voire décupler. Une intéressante conclusion est que, contrairement à ce que certains discours sensationnalistes comme ceux de Thierry Breton prétendaient, l'Internet n'a pas connu de problème généralisé. Comme toujours dans ce réseau mondial, les problèmes sont restés localisés, ralentissements à certains endroits, baisse automatique de la qualité des vidéos à d'autres, mais pas de problème systémique. Ce bon résultat n'a pas été obtenu uniquement par la capacité du réseau existant à encaisser la montée en charge. Il y a eu également de nombreuses actions prises par les différents acteurs du réseau, qui ne sont pas restés les bras croisés face au risque. Bref, vu du point de vue scientifique, c'était une expérience intéressante, qui montre que l'Internet peut résister à des crises, ce qui permet de penser que les problèmes futurs ne seront pas forcément fatals.
[L'atelier portait bien sur l'Internet, sur l'infrastructure, pas sur les services hébergés. Il est important de faire la distinction car certains services (comme ceux de l'Éducation Nationale en France) ont été incapables de résister à la charge et se sont vite écroulés. Mais ce n'était pas une défaillance de l'Internet, et renoncer à regarder des vidéos n'aurait pas protégé le CNED contre ces problèmes. Notons qu'il n'y avait pas de fatalité à ces problèmes des services : Wikipédia et Pornhub, deux services très différents dans leur utilisation et leur gestion, ont continué à fonctionner correctement.]
Voyons maintenant les détails, dans la section 3 du RFC. On commence avec la sous-section 3.1, les mesures. Que s'est-il passé ? Comme on pouvait s'y attendre, le trafic résidentiel a augmenté (en raison du travail à la maison), tandis que celui des réseaux mobiles chutait (on se déplaçait moins). Le trafic vers les opérateurs de services de vidéo-conférence (comme Zoom) a augmenté, ainsi que celui des services de distraction (VoD) pendant la journée. Mais il y a eu surtout un gros déplacement des pics d'activité. En semaine, l'activité Internet était très liée au rythme de la journée, avec des trafics très différents dans la journée et le soir. Pendant les confinements, on a vu au contraire une activité plus étalée dans le temps chez les FAI résidentiels, et une activité de la semaine qui ressemble à celle des week-ends. Là encore, pas de conséquences graves, bien que certains FAI aient signalé des ralentissements notamment en mars 2020. Bref, l'Internet sait bien résister aux sautes de trafic qui, il est vrai, font partie de son quotidien depuis sa création.
Parmi les articles soumis pour l'atelier, je vous recommande, sur
la question des mesures, le très détaillé « A
view of Internet Traffic Shifts at ISP and IXPs during the COVID-19
Pandemic ». Les auteurs ont observé le trafic chez
plusieurs opérateurs et points d'échange (le
seul nommé est le réseau académique de
Madrid). Par exemple, en utilisant diverses
heuristiques (le port seul ne suffit plus,
tout le monde étant sur 443),
les auteurs ont des chiffres concernant diverses applications
(vidéo-conférence, vidéo à la demande, jeux en ligne, etc). Moins
détaillé, il y a le « IAB
COVID-19 Workshop: Interconnection Changes in the United
States » (à l'origine publié dans un
Internet-Draft,
draft-feamster-livingood-iab-covid19-workshop
).
Un exemple de changement du trafic est donné par le Politecnico de Turin qui a vu son trafic sortant multiplié par 2,5, en raison de la diffusion de ses 600 cours en ligne par jour, alors que le trafic entrant était divisé par 10. Dans les universités, le trafic entrant est typiquement bien plus gros que le sortant (les étudiants et enseignants sont sur le campus et accèdent à des ressources distantes) mais cela a changé pendant le confinement, les ressources externes étant accédées depuis la maison. (Une entreprise aurait pu voir des effets différents, si les employés accèdent à ces ressources externes via le VPN de l'entreprise.) Le REN REDIMadrid a vu également de gros changements dans son trafic. Effet imprévu, les communications avec les AS d'Amérique latine a augmenté, probablement parce que les étudiants hispanophones américains profitaient des possibilités de cours à distance pour suivre les activités des universités de l'ex-métropole.
Comme dit plus haut, les réseaux mobiles, 4G et autres, ont vu leur activité baisser. L'article « A Characterization of the COVID-19 Pandemic Impact on a Mobile Network Operator Traffic » note une mobilité divisée par deux en Grande-Bretagne et un trafic diminué d'un quart. (Certaines personnes ne sont plus mobiles mais utilisent la 4G à la maison, et il y a bien d'autres phénomènes qui rendent compliquée l'analyse.) L'observation des signaux envoyés par les téléphones (le réseau mobile sait où vous êtes…) a également permis de mesurer l'ampleur de la fuite hors des grandes villes (10 % des Londoniens).
Et dans la connexion des FAI avec les services sur le cloud ? Les liens d'interconnexion entre FAI et fournisseurs de services ont-ils tenu ? Pas de congestion persistante mais des moments de tension, par exemple aux États-Unis vers les petits FAI, qui n'avaient pas toujours une interconnexion suffisante pour encaisser tout le trafic accru. Comme toujours sur l'Internet, malgré le caractère mondial de la pandémie, il y a peu d'observations valables partout et tout le temps, vu la variété des capacités des liaisons. Malgré l'observation générale « globalement, ça a tenu », il y a toujours des endroits où ça rame à certains moments.
En effet, le bon fonctionnement de l'Internet mondial ne signifiait pas que tous les MM. Toutlemonde de la Terre avaient une bonne qualité de connexion. L'article « The Impact of COVID-19 on Last-mile Latency » (plus de détails sur le blog de l'auteur) rend compte de mesures faites avec les sondes RIPE Atlas, qui trouvent une congestion plus fréquente sur le « premier kilomètre » (le lien entre la maison de M. Toutlemonde et le premier POP de son FAI) pendant le confinement. Cela dépend évidemment beaucoup du FAI et du pays, le Japon ayant été particulièrement touché. La situation s'est toutefois améliorée au fur et à mesure, notamment en raison des déploiements de capacité supplémentaire par les opérateurs (et, au Japon, des investissements qui étaient prévus pour les Jeux Olympiques). L'Internet se retrouve donc plus robuste qu'avant. Le RFC cite même Nietzsche « Ce qui ne me tue pas me rend plus fort ».
On l'a dit, le trafic n'a pas seulement changé quantitativement mais aussi qualitativement. La vidéo-conférence a, fort logiquement, crû. Le trafic très asymétrique de certains FAI grand public (beaucoup plus de trafic entrant vers les consommateurs que de trafic sortant) s'est un peu égalisé, en raison des flux vidéos sortants. NCTA et Comcast signalent plus de 30 % de hausse de ce trafic sortant, Vodafone 100 %. Un rapport d'Ericsson sur les utilisateurs signale :
Ces changements sont-ils permanents ? Resteront-ils dans le « monde d'après » ? Le RFC estime que le télétravail s'est désormais installé et restera ; on peut donc prévoir que l'utilisation intensive d'outils de réunion à distance persistera (cf. le rapport « Work-At-Home After Covid-19—Our Forecast »).
Après ces observations, la section 3 du RFC continue avec des considérations sur les problèmes opérationnels constatés. D'abord, un point de fracture numérique. Aux États-Unis, et probablement dans bien d'autres pays, le débit entrant chez les utilisateurs est corrélé au niveau de vie. Mais on a constaté pendant la pandémie une réduction de l'écart entre riches et pauvres (l'étude ne portait pas sur des foyers individuels mais sur des zones géographiques identifiées par leur code postal, un bon indicateur de niveau de vie, au moins aux USA). Cette réduction de l'écart n'était pas forcément liée à un changement de comportement des utilisateurs mais l'était peut-être au fait que certains FAI comme Comcast ont étendu la capacité liée à des abonnements bon marché, par souci de RSE pendant la crise. L'écart entre riches et pauvres était donc peut-être dû à une différence dans les abonnements souscrits, pas à une différence d'utilisation de l'Internet.
Les applications vedettes des confinements ont évidemment été les outils de réunion en ligne, gros services privateurs et capteurs de données personnelles comme Microsoft Teams ou Zoom, ou bien services reposant sur des logiciels libres comme BigBlueButton (dont le RFC, qui reflète un point de vue surtout étatsunien, ne parle pas). D'autres outils de distribution de vidéo, comme YouTube ont vu également leur trafic augmenter soudainement. Certains acteurs, comme justement YouTube, ont délibérement réduit la qualité des vidéos pour diminuer la charge sur le réseau, mais il n'est pas évident que cela ait eu un effet majeur. Autre catégorie d'applications qui a vu son utilisation augmenter, les jeux en ligne. Souvent très consommateurs de ressources, ils ont la particularité de demander à la fois une forte capacité (en raisons des contenus multimédias riches à télécharger) et une faible latence (quand on tire sur le zombie, il doit tomber tout de suite). La mise à jour d'un jeu très populaire a un effet très net sur le réseau des FAI ! Mais il faut noter que ce n'est pas un phénomène spécifique au confinement. Les opérateurs ont déjà dû faire face à des évènements soudains, comme une nouvelle version d'un logiciel très utilisé ou comme une nouvelle mode, par exemple une application qui connait un succès rapide, ce qui est assez fréquent sur l'Internet. Outre ces « effets Slashdot », il y a aussi les attaques par déni de service, qui nécessitent de suravitailler (mettre davantage de capacité que strictement nécessaire). Les opérateurs ont donc déjà de l'expérience dans ce domaine mais, note le RFC, cette expérience n'est pas toujours partagée.
Une discussion lors de l'atelier a porté sur la possibilité de gérer ce genre de problèmes par des mesures discriminatoires, de type qualité de service (un terme propagandiste, il faut le noter : si on discrimine, certains auront une meilleure qualité et d'autres une moins bonne). Marquer le trafic « pas essentiel » (qui décidera de ce qui n'est pas essentiel ?) avec DSCP pour le faire passer par les chemins les plus lents aurait-il aidé ? Compte-tenu du caractère très brûlant de ce débat, il n'est pas étonnant qu'aucun consensus n'est émergé de l'atelier. Le RFC se réjouit qu'au moins les engueulades ont été moins graves que d'habitude.
Une bonne partie de cet atelier était consacré à l'étude de faits : qu'ont vu les opérateurs ? Or, ils n'ont pas vu la même chose. Cela reflète les différences de situation mais aussi les différences dans les outils d'observation. La métrologie n'est pas une chose facile ! Par exemple, les applications de vidéo-conférence ou de distribution de vidéo à la demande ont des mécanismes de correction d'erreur et de gestion de la pénurie très élaborés. L'application s'adapte en permanence aux caractéristiques du réseau, par exemple en ajustant son taux de compression. C'est très bien pour l'utilisateur, cela permet de lui dissimuler une grande partie des problèmes, mais cela complique l'observation. Et quand il y a un problème, il est difficile à analyser. L'autre jour, sans que cela ait de rapport avec un confinement, je regardais une vidéo sur Netflix et la qualité de l'image était vraiment médiocre, gâchant le plaisir. Mais où était le problème ? Mon PC était trop lent ? Le Wifi était pourri ? Le réseau de Free surchargé ? L'interconnexion entre Free et Netflix était-elle encombrée ? Les serveurs de Netflix ramaient-ils ? C'est très difficile à dire, et cela dépend de beaucoup de choses (par exemple, deux utilisateurs de Netflix ne tombent pas forcément sur le même serveur chez Netflix et peuvent donc avoir des vécus différents). Et puis, globalement, on manque de capacités d'observation sur l'Internet. Le client ne voit pas ce qui se passe sur le serveur, le serveur ne sait pas grand'chose sur le client, et peut-être qu'aucun des deux n'a pas visibilité sur l'interconnexion. Chacun connait bien son réseau, mais personne ne connait l'Internet dans son ensemble. Le RFC note que, paradoxalement, la Covid-19 a amélioré les choses, en augmentant le niveau de coopération entre les acteurs de l'Internet.
Et la sécurité ? Elle a aussi été discutée à l'atelier car le passage brusque de tant de gens au télétravail a changé le paysage de la sécurité. On ne pouvait plus compter sur le pare-feu corporate et sur les machines du bureau soigneusement verrouillées par la DSI. Au lieu de cela, tout le monde utilisait des VPN pas toujours bien maitrisés (cf. l'article « IAB COVID-19 Network Impacts »). Et la pandémie a été l'occasion de nombreuses escroqueries (décrites dans le même article). À propos de sécurité, le RFC en profite pour vanter les résolveurs DNS menteurs et critiquer DoH (qui n'est pour rien dans ces escroqueries).
En conclusion, le RFC note que le bon fonctionnement de l'Internet pendant la pandémie n'était pas dû uniquement à ses qualités intrinsèques, mais aussi à l'action de nombreux acteurs. Comme d'autres professions, les techniciens et techniciennes de l'Internet étaient une des lignes de défense face à l'épidémie et cette ligne était très motivée, et a tenu. Ces techniciennes et techniciens méritent donc de chaudes félicitations. Mais on peut quand même améliorer les choses :
Et le RFC se conclut par un bilan de cet atelier qui, contrairement aux ateliers précédents de l'IAB, a été fait entièrement en ligne. Les participants ont été plutôt contents, notamment du fait que le travail à distance a permis de changer le format : au lieu de deux jours complets de discussions, l'atelier a pu se tenir en alternant des moments de discussion et du travail chez soi, pour approfondir et critiquer les discussions. Toutefois, le RFC note que cela a bien marché car la quasi-totalité des présents se connaissaient bien, étant des participants de longue date à l'IETF. Il n'est pas du tout évident que cela aurait aussi bien marché avec des gens nouveaux, le présentiel étant crucial pour créer des liens informels.
L'ensemble des articles écrits par les participants à l'atelier (pour participer, il fallait avoir écrit un texte) est disponible en ligne (en bas de la page).
Date de publication du RFC : Juillet 2021
Auteur(s) du RFC : G. Fairhurst (University of Aberdeen), C. Perkins (University of Glasgow)
Pour information
Réalisé dans le cadre du groupe de travail IETF tsvwg
Première rédaction de cet article le 20 juillet 2021
La couche Transport n'est pas celle qui suscite le plus de passions dans l'Internet. Mais la récente normalisation du protocole QUIC a mis cette couche en avant et l'usage du chiffrement par QUIC a relancé le débat : quelles sont les conséquences d'un chiffrement de plus en plus poussé de la couche Transport ?
Traditionnellement, la couche Transport ne faisait pas de chiffrement (cf. RFC 8095 et RFC 8922). On chiffrait en-dessous (IPsec) ou au-dessus (TLS, SSH). IPsec ayant été peu déployé, l'essentiel du chiffrement aujourd'hui sur l'Internet est fait par TLS. Toute la mécanique TCP est donc visible aux routeurs sur le réseau. Ils peuvent ainsi mesurer le RTT, découvrir début et fin d'une connexion, et interférer avec celle-ci, par exemple en envoyant des paquets RST (ReSeT) pour mettre fin à la session. Cela permet de violer la vie privée (RFC 6973), par exemple en identifiant une personne à partir de son activité en ligne. Et cette visibilité de la couche Transport pousse à l'ossification : de nombreux intermédiaires examinent TCP et, si des options inhabituelles sont utilisées, bloquent les paquets. Pour éviter cela, QUIC chiffre une grande partie de la couche 4, pour éviter les interférences par les intermédiaires et pour défendre le principe de bout en bout et la neutralité du réseau. Comme souvent en sécurité, cette bonne mesure de protection a aussi des inconvénients, que ce RFC examine. Notons tout de suite que ce qui est un inconvénient pour les uns ne l'est pas forcément pour les autres : pour un FAI, ne pas pouvoir couper les connexions TCP de BitTorrent avec RST est un inconvénient mais, pour l'utilisateur, c'est un avantage, cela le protège contre certaines attaques par déni de service.
On ne peut pas sérieusement aujourd'hui utiliser des communications non-chiffrées (RFC 7258). Personne n'ose dire publiquement le contraire. Par contre, on entend souvent un discours « je suis pour le chiffrement, mais » et, comme toujours avec ce genre de phrase, c'est ce qui est après le « mais » qui compte. Ce RFC essaie de documenter les avantages et les inconvénients du chiffrement de la couche Transport, mais, en pratique, est plus détaillé sur les inconvénients, ce qui était déjà le cas du RFC 8404.
La section 2 du RFC explique quel usage peut être fait des informations de la couche Transport par les équipements intermédiaires. En théorie, dans un modèle en couches idéal, il n'y en aurait aucun : la couche Transport est de bout en bout, les routeurs et autres équipements intermédiaires ne regardent rien au-dessus de la couche Réseau. Mais en pratique, ce n'est pas le cas, comme l'explique cette section. (Question pour mes lecteurices au passage : vous semble-t-il légitime de parler de DPI quand un routeur regarde le contenu de la couche Transport, dont il n'a en théorie pas besoin, ou bien doit-on réserver ce terme aux cas où il regarde dans la couche Application ?)
Première utilisation de la couche Transport par des intermédiaires : identifier des flots de données (une suite d'octets qui « vont ensemble »). Pourquoi en a-t-on besoin ? Il y a plusieurs raisons possibles, par exemple pour la répartition de charge, où on veut envoyer tous les paquets d'un flot donné au même serveur. Cela se fait souvent en prenant un tuple d'informations dans le paquet (tuple qui peut inclure une partie de la couche Transport, comme les ports source et destination) et en le condensant pour avoir un identificateur du flot. Si la couche Transport est partiellement ou totalement chiffrée, on ne pourra pas distinguer deux flots différents entre deux machines. En IPv6, l'étiquette de flot (RFC 6437) est une solution possible (RFC 6438, RFC 7098), mais je n'ai pas l'impression qu'elle soit très utilisée.
Maintenant, passons à la question de l'identification d'un flot. Était-ce un transfert de fichiers, de la vidéo, une session interactive ? Il faut déduire cette identification à partir des informations de la couche Transport (voir le RFC 8558). Mais pourquoi identifier ces flots alors que l'opérateur doit tous les traiter pareil, en application du principe de neutralité ? Cela peut être dans l'intérêt de l'utilisateur (mais le RFC ne donne pas d'exemple…) ou bien contre lui, par exemple à des fins de surveillance, ou bien pour discriminer certains usages (comme le réclament régulièrement certains politiciens et certains opérateurs), voire pour les bloquer complètement. Autrefois, on pouvait souvent identifier un service uniquement avec le numéro de port (43 pour whois, 25 pour le courrier, etc, cf. RFC 7605) mais cela n'a jamais marché parfaitement, plusieurs services pouvant utiliser le même port et un même service pouvant utiliser divers ports. De toute façon, cette identification par le numéro de port est maintenant finie, en partie justement en raison de cette discrimination selon les usages, qui pousse tout le monde à tout faire passer sur le port 443. Certains services ont un moyen simple d'être identifié, par exemple par un nombre magique, volontairement placé dans les données pour permettre l'identification, ou bien simple conséquence d'une donnée fixe à un endroit connu (RFC 3261, RFC 8837, RFC 7983…). Lors de la normalisation de QUIC, un débat avait eu lieu sur la pertinence d'un nombre magique permettant d'identifier du QUIC, idée finalement abandonnée.
Si les équipements intermédiaires indiscrets n'arrivent pas à déterminer le service utilisé, le flot va être considéré comme inconnu et le RFC reconnait que certains opérateurs, en violation de la neutralité de l'Internet, ralentissent ces flots inconnus.
L'étape suivante pour ceux qui veulent identifier à quoi servent les données qu'ils voient passer est d'utiliser des heuristiques. Ainsi, une visio-conférence à deux fera sans doute passer à peu près autant d'octets dans chaque sens, alors que regarder de la vidéo à la demande créera un trafic très asymétrique. Des petits paquets UDP régulièrement espacés permettent de soupçonner du trafic audio, même si on n'a pas pu lire l'information SDP (RFC 4566). Des heuristiques plus subtiles peuvent permettre d'en savoir plus. Donc, il faut se rappeler que le chiffrement ne dissimule pas tout, il reste une vue qui peut être plus ou moins précise (le RFC 8546 décrit en détail cette notion de vue depuis le réseau).
Autre motivation pour analyser la couche Transport, l'amélioration des performances. Inutile de dire que le FAI typique ne va pas se pencher sur les problèmes de performance d'un abonné individuel (si ça rame avec Netflix, appeler le support de son FAI ne déclenche pas de recherches sérieuses). Mais cela peut être fait pour des analyse globales. Là encore, les conséquences peuvent être dans l'intérêt de l'utilisateur, ou bien contre lui. Le RFC note que les mesures de performance peuvent amener à une discrimination de certains services (« QoS », qualité de service, c'est-à-dire dégradation de certains services). Que peut-on mesurer ainsi, qui a un impact sur les performances ? Il y a la perte de paquets, qu'on peut déduire, en TCP, des retransmissions. Dans l'Internet, il y a de nombreuses causes de pertes de paquets, du parasite sur un lien radio à l'abandon délibéré par un routeur surchargé (RFC 7567) en passant par des choix politiques de défavoriser certains paquets (RFC 2475). L'étude de ces pertes peut permettre dans certains cas de remonter aux causes.
On peut aussi mesurer le débit. Bon, c'est facile, sans la couche Transport, uniquement en regardant le nombre d'octets qui passent par les interfaces réseaux. Mais l'accès aux données de la couche Transport permet de séparer le débit total du débit utile (goodput, en anglais, pour le différencier du débit brut, le throughput, cf. section 2.5 du RFC 7928, et le RFC 5166). Pour connaitre ce débit utile, il faut pouvoir reconnaitre les retransmissions (si un paquet est émis trois fois avant enfin d'atteindre le destinataire, il ne contribue qu'une fois au débit utile). Une retransmission peut se voir en observant les numéros de séquence en TCP (ou dans d'autres protocoles comme RTP).
La couche Transport peut aussi nous dire quelle est la latence. Cette information est cruciale pour évaluer la qualité des sessions interactives, par exemple. Et elle influe beaucoup sur les calculs du protocole de couche 4. (Voir l'article « Internet Latency: A Survey of Techniques and their Merits ».) Comment mesure-t-on la latence ? Le plus simple est de regarder les accusés de réception TCP et d'en déduire le RTT. Cela impose d'avoir accès aux numéros de séquence. Dans TCP, ils sont en clair, mais QUIC les chiffre (d'où l'ajout du spin bit).
D'autres métriques sont accessibles à un observateur qui regarde la couche Transport. C'est le cas de la gigue, qui se déduit des observations de la latence, ou du réordonnancement des paquets (un paquet qui part après un autre, mais arrive avant). L'interprétation de toutes ces mesures dépend évidemment du type de lien. Un lien radio (RFC 8462) a un comportement différent d'un lien filaire (par exemple, une perte de paquets n'est pas forcément due à la congestion, elle peut venir de parasites).
Le RFC note que la couche Réseau, que les équipements intermédiaires ont tout à fait le droit de lire, c'est son rôle, porte parfois des informations qui peuvent être utiles. En IPv4, ce sont les options dans l'en-tête (malheureusement souvent jetées par des pare-feux trop fascistes, cf. RFC 7126), en IPv6, les options sont après l'en-tête de réseau, et une option, Hop-by-hop option est explicitement prévue pour être examinée par tous les routeurs intermédiaires.
Outre les statistiques, l'analyse des données de la couche Transport peut aussi servir pour les opérations (voir aussi le RFC 8517), pour localiser un problème, pour planifier l'avitaillement de nouvelles ressources réseau, pour vérifier qu'il n'y a pas de tricheurs qui essaient de grapiller une part plus importante de la capacité, au risque d'aggraver la congestion (RFC 2914). En effet, le bon fonctionnement de l'Internet dépend de chaque machine terminale. En cas de perte de paquets, signal probable de congestion, les machines terminales sont censées réémettre les paquets avec prudence, puisque les ressources réseau sont partagées. Mais une machine égoïste pourrait avoir plus que sa part de la capacité. Il peut donc être utile de surveiller ce qui se passe, afin d'attraper d'éventuels tricheurs, par exemple une mise en œuvre de TCP qui ne suivrait pas les règles habituelles. (Si on utilise UDP, l'application doit faire cela elle-même, cf. RFC 8085. Ainsi, pour RTP, comme pour TCP, un observateur extérieur peut savoir si les machines se comportent normalement ou bien essaient de tricher.)
Autre utilisation de l'observation de la couche Transport pour l'opérationnel, la sécurité, par exemple la lutte contre les attaques par déni de service, l'IDS et autres fonctions. Le RFC note que cela peut se faire en coopération avec les machines terminales, si c'est fait dans l'intérêt de l'utilisateur. Puisqu'on parle de machines terminales, puisque le chiffrement d'une partie de la couche Transport est susceptible d'affecter toutes les activités citées plus haut, le RFC rappelle la solution évidente : demander la coopération des machines terminales. Il y a en effet deux cas : soit les activités d'observation de la couche Transport sont dans l'intérêt des utilisateurs, et faites avec leur consentement, et dans ce cas la machine de l'utilisateur peut certainement coopérer, soit ces activités se font contre l'utilisateur (discrimination contre une application qu'il utilise, par exemple), et dans ce cas le chiffrement est une réponse logique à cette attaque. Bien sûr, c'est la théorie ; en pratique, certaines applications ne fournissent guère d'informations et de moyens de débogage. Les protocoles de transport qui chiffrent une bonne partie de leur fonctionnement peuvent aussi aider, en exposant délibérement des informations. C'est par exemple ce que fait QUIC avec son spin bit déjà cité, ou avec ses invariants documentés dans le RFC 8999.
Autre cas où le chiffrement de la couche Transport peut interférer avec certains usages, les réseaux d'objets contraints, disposant de peu de ressources (faible processeur, batterie qu'il ne faut pas vider trop vite, etc). Il arrive dans ce cas d'utiliser des relais qui interceptent la communication, bricolent dans la couche Transport puis retransmettent les données. Un exemple d'un tel bricolage est la compression des en-têtes, courante sur les liens à très faible capacité (cf. RFC 2507, RFC 2508, le ROHC du RFC 5795, RFC 6846, le SCHC du RFC 8724, etc). Le chiffrement rend évidemment cela difficile, les relais n'ayant plus accès à l'information. C'est par exemple pour cela que le RTP sécurisé du RFC 3711 authentifie l'en-tête mais ne le chiffre pas. (Je suis un peu sceptique sur cet argument : d'une part, les objets contraints ne vont pas forcément utiliser des protocoles de transport chiffrés, qui peuvent être coûteux, d'autre part un sous-produit du chiffrement est souvent la compression, ce qui rend inutile le travail des relais.)
Un dernier cas cité par le RFC où l'observation du fonctionnement de la couche Transport par les machines intermédiaires est utile est celui de la vérification de SLA. Si un contrat ou un texte légal prévoit certaines caractéristiques pour le réseau, l'observation de la couche 4 (retransmission, RTT…) est un moyen d'observer sans avoir besoin d'impliquer les machines terminales. (Personnellement, je pense justement que ces vérifications devraient plutôt se faire depuis les machines terminales, par exemple avec les sondes RIPE Atlas, les SamKnows, etc.)
La section 3 du RFC décrit un autre secteur qui est intéressé par l'accès aux données de transport, la recherche. Par exemple, concevoir de nouveaux protocoles doit s'appuyer sur des mesures faites sur les protocoles existants, pour comprendre leurs forces et leurs faiblesses. C'est possible avec un protocole comme TCP, où l'observation passive permet, via notamment les numéros de séquence, de découvrir le RTT et le taux de perte de paquets. (Passive : sans injecter de paquets dans le réseau. Voir le RFC 7799.) Mais ces mêmes informations peuvent aussi servir contre l'utilisateur. Même s'il n'y a pas d'intention néfaste (par exemple de discrimination contre certains usages), toute information qui est exposée peut conduire à l'ossification, l'impossibilité de changer le protocole dans le futur. Une des motivations des protocoles chiffrés comme QUIC est en effet d'éviter l'ossification : une middlebox ne pourra pas prendre de décisions sur la base d'informations qu'elle n'a pas. QUIC affiche des données au réseau seulement s'il le veut (c'est le cas du spin bit). D'où également le choix délibéré de graisser, c'est-à-dire de faire varier certaines informations pour éviter que des programmeurs de middleboxes incompétents et/ou paresseux n'en déduisent que cette information ne change jamais (le graissage est décrit dans le RFC 8701).
La bonne solution pour récolter des données sans sacrifier la vie
privée est, comme dit plus haut, de faire participer les extrémités,
les machines terminales, ce qu'on nomme en anglais le
endpoint-based logging. Actuellement,
malheureusement, les mécanismes de débogage ou de récolte
d'information sur ces machines terminales sont trop réduits, mais
des efforts sont en cours. Par exemple, pour QUIC, c'est la
normalisation du format « qlog » d'enregistrement des informations
vues par la couche Transport
(Internet-Draft
draft-ietf-quic-qlog-main-schem
)
ou bien le format Quic-Trace. Mais
le RFC note que la participation des machines terminales ne suffit
pas toujours, notamment si on veut déterminer
où, dans le réseau, se produit un problème.
Après qu'on ait vu les utilisations qui sont faites de l'analyse de la couche Trnsport par les équipements intermédiaires, la section 4 du RFC revient ensuite sur les motivations du chiffrement de cette couche. Pourquoi ne pas se contenter de ce que font TLS et SSH, qui chiffrent uniquement la couche Application ? L'une des premières raisons est d'empêcher l'ossification, ce phénomène qui fait qu'on ne peut plus faire évoluer la couche Transport car de stupides équipements intermédiaires, programmés avec les pieds par des ignorants qui ne lisent pas les RFC, rejettent les paquets légaux mais qui ne correspondent pas à ce que ces équipements attendaient. Ainsi, si un protocole de transport permet l'utilisation d'un octet dans l'en-tête, mais que cet octet est à zéro la plupart du temps, on risque de voir des middleboxes qui jettent les paquets où certains bits de ce champ sont à un car « ce n'est pas normal ». Tout ce qui est observable risque de devenir ossifié, ne pouvant plus être modifié par la suite. Chiffrer permet de garantir que les équipements intermédiaires ne vont pas regarder ce qui ne les regarde pas. Le RFC donne plusieurs exemples édifiants des incroyables comportements de ces logiciels écrits par des gens qui ne comprenaient qu'une partie d'un protocole :
Il n'est donc pas étonnant que les concepteurs de protocole cherchent désormais à chiffrer au maximum, pour éviter ces interférences. Le RFC 8546 rappelle ainsi que c'est la vue depuis le réseau (wire image), c'est-à-dire ce que les équipements intermédiaires peuvent observer, pas la spécification écrite du protocole, qui détermine, dans le monde réel, ce qu'un intermédiaire peut observer et modifier. Il faut donc réduire cette vue au strict minimum ; tout ce qui n'est pas chiffré risque fortement d'être ossifié, figé. Et le RFC 8558 affirme lui qu'on ne doit montrer au réseau que ce qui doit être utilisé par le réseau, le reste, qui ne le regarde pas, doit être dissimulé.
Une autre motivation du chiffrement de la couche Transport est évidemment de mieux protéger la vie privée (RFC 6973). L'ampleur de la surveillance massive (RFC 7624) est telle qu'il est crucial de gêner cette surveillance le plus possible. Le RFC note qu'il n'y a pas que la surveillance passive, il y a aussi l'ajout de données dans le trafic, pour faciliter la surveillance. Du fait de cet « enrichissement », il peut être utile, quand un champ doit être observable (l'adresse IP de destination est un bon exemple), d'utiliser quand même la cryptographie pour empêcher ses modifications, via un mécanisme d'authentification. C'est ce que fait TCP-AO (RFC 5925, mais qui semble peu déployé), et bien sûr le service AH d'IPsec (RFC 4302).
Comme on le voit, il y a une tension, voire une lutte, entre les opérateurs réseau et les utilisateurs. On pourrait se dire que c'est dommage, qu'il vaudrait mieux que tout le monde travaille ensemble. Cela a été discuté à l'IETF, avec des expressions comme « un traité de paix entre machines terminales et boitiers intermédiaires ». Pour l'instant, cela n'a pas débouché sur des résultats concrets, en partie parce qu'il n'existe pas d'organisations représentatives qui pourraient négocier, signer et faire respecter un tel traité de paix. On en reste donc aux mesures unilatérales. Les machines terminales doivent chiffrer de plus en plus pour maintenir le principe de bout en bout. Comme dans tout conflit, il y a des dégâts collatéraux (le RFC 8922 en décrit certains). Le problème n'étant pas technique mais politique, il est probable qu'il va encore durer. La tendance va donc rester à chiffrer de plus en plus de choses.
À noter qu'une autre méthode que le chiffrement existe pour taper sur les doigts des boitiers intermédiaires pénibles, qui se mêlent de ce qui ne les regarde pas, et s'en mêlent mal : c'est le graissage. Son principe est d'utiliser délibérément toutes les options possibles du protocole, pour habituer les middleboxes à voir ces variations. Le RFC 8701 en donne un exemple, pour le cas de TLS.
Déterminer ce qu'il faut chiffrer, ce qu'il faut authentifier, et ce qu'il vaut mieux laisser sans protection, autorisant l'observation et les modifications, n'est pas une tâche facile. Autrefois, tout était exposé parce qu'on avait moins de problèmes avec les boitiers intermédiaires et que les solutions, comme le chiffrement, semblaient trop lourdes. Aujourd'hui qu'on a des solutions réalistes, on doit donc choisir ce qu'on montre ou pas. Le choix est donc désormais explicite (cf. RFC 8558).
Au passage, une façon possible d'exposer des informations qui peuvent être utiles aux engins intermédiaires est via un en-tête d'extension. Par exemple en IPv6, l'en-tête Hop-by-hop (RFC 8200, section 4.3) est justement fait pour cela (voir un exemple dans le RFC 8250, quoiqu'avec un autre type d'en-tête). Toutefois, cet en-tête Hop-by-hop est clairement un échec : beaucoup de routeurs jettent les paquets qui le portent (RFC 7872), ou bien les traitent plus lentement que les paquets sans cette information. C'est encore pire si cet en-tête porte des nouvelles options, inconnues de certaines middleboxes, et c'est pour cela que le RFC 8200 déconseille (dans sa section 4.8) la création de nouvelles options Hop-by-hop.
Mais, bon, le plus important est de décider quoi montrer, pas juste comment. Le RFC rappelle qu'il serait sympa d'exposer explicitement des informations comme le RTT ou le taux de pertes vu par les machines terminales, plutôt que de laisser les machines intermédiaires le calculer (ce qu'elles ne peuvent de toute façon plus faire en cas de chiffrement). Cela permettrait de découpler l'information de haut niveau des détails du format d'un protocole de transport. Pourquoi une machine terminale ferait-elle cela, au risque d'exposer des informations qu'on peut considérer comme privées ? Le RFC cite la possibilité d'obtenir un meilleur service, sans trop préciser s'il s'agit de laisser les opérateurs offrir un traitement préférentiel aux paquets portant cette information, ou bien si c'est dans l'espoir que l'information exposée serve à l'opérateur pour améliorer son réseau. (Comme le note le RFC 8558, il y a aussi le risque que la machine terminale mente au réseau. Au moins, avec le chiffrement, les choses sont claires : « je refuse de donner cette information » est honnête.)
Dernière note, cet ajout d'informations utiles pour l'OAM peut être faite par la machine terminale mais aussi (section 6 du RFC) par certains équipements intermédiaires.
En conclusion ? La section 7 du RFC reprend et résume les points importants :
Date de publication du RFC : Juillet 2021
Auteur(s) du RFC : R. Moskowitz (HTT Consulting), M. Komu (Ericsson)
Pour information
Réalisé dans le cadre du groupe de travail IETF hip
Première rédaction de cet article le 15 juillet 2021
Ce RFC propose d'aborder l'architecture de l'Internet en utilisant un nouveau type d'identificateur, le Host Identifier (HI), pour beaucoup d'usages qui sont actuellement ceux des adresses IP. Il remplace le RFC 4423, qui était la description originale du protocole HIP, mais il n'y a pas de changements fondamentaux. HIP était un projet très ambitieux mais, malgré ses qualités, la disponibilité de plusieurs mises en œuvre, et des années d'expérimentation, il n'a pas percé.
Une adresse IP sert actuellement à deux choses : désigner une machine (l'adresse IP sert par exemple à distinguer plusieurs connexions en cours) et indiquer comment la joindre (routabilité). Dans le premier rôle, il est souhaitable que l'adresse soit relativement permanente, y compris en cas de changement de FAI ou de mobilité (actuellement, si une machine se déplace et change d'adresse IP, les connexions TCP en cours sont cassées). Dans le second cas, on souhaite au contraire une adresse qui soit le plus « physique » possible, le plus dépendante de la topologie. Ces deux demandes sont contradictoires.
HIP résout le problème en séparant les deux fonctions. Avec HIP, l'adresse IP ne serait plus qu'un identifiant « technique », ne servant qu'à joindre la machine, largement invisible à l'utilisateur et aux applications (un peu comme une adresse MAC aujourd'hui). Chaque machine aurait un HI (Host Identifier) unique. Contrairement aux adresses IP, il n'y a qu'un HI par machine multi-homée mais on peut avoir plusieurs HI pour une machine si cela correspond à des usages différents, par exemple une identité publique, et une « anonyme ».
Pour pouvoir être vérifié, le nouvel identificateur, le HI sera (dans la plupart des cas) une clé publique cryptographique, qui sera peut-être allouée hiérarchiquement par PKI ou plutôt de manière répartie par tirage au sort (comme le sont les clés SSH ou PGP aujourd'hui, ce qui serait préférable, question vie privée). Ces identificateurs fondés sur la cryptographie permettent l'authentification réciproque des machines (contrairement à IP, où il est trivial de mentir sur son adresse), et d'utiliser ensuite IPsec (RFC 7402) pour chiffrer la communication (HIP n'impose pas IPsec, plusieurs encapsulations des données sont possibles, et négociées dynamiquement, mais, en pratique, la plupart des usages prévus reposent sur IPsec).
L'authentification permet d'être sûr du HI de la machine avec qui on parle et, si le HI était connu préalablement à partir d'une source de confiance, d'être sûr qu'on parle bien à l'interlocuteur souhaité. (Si on ne connait pas le HI à l'avance, on dit que HIP est en mode « opportuniste ».)
Cette séparation de l'identificateur et du localisateur est un sujet de recherche commun et d'autres propositions que HIP existent, comme LISP (RFC 9300) ou ILNP (RFC 6740). Dans tous les cas, les couches supérieures (comme TCP) ne verront que l'identificateur, permettant au localisateur de changer sans casser les sessions de transport en cours. (Un mécanisme ressemblant est le Back to My Mac du RFC 6281.) L'annexe A.1 de notre RFC rappelle les avantages de cette approche. Et l'annexe A.2, lecture très recommandée, note également ses défauts, l'indirection supplémentaire ajoutée n'est pas gratuite, et entraine des nouveaux problèmes. Notamment, il faut créer un système de correspondance - mapping - entre les deux, système qui complexifie le projet. Il y a aussi la latence supplémentaire due au protocole d'échange initial, qui est plus riche. Comparez cette honnêteté avec les propositions plus ou moins pipeau de « refaire l'Internet en partant de zéro », qui ne listent jamais les limites et les problèmes de leurs solutions miracle.
Ce HI (Host Identifier) pourra être stocké dans des annuaires publics, comme le DNS (RFC 8005), ou une DHT (RFC 6537), pour permettre le rendez-vous (RFC 8004) entre les machines.
Notez que ce n'est pas directement le Host Identifier, qui peut être très long, qui sera utilisé dans les paquets IP, mais un condensat, le HIT (Host Identity Tag).
HIP intègre les leçons de l'expérience avec IP, notamment de l'importance d'authentifier la machine avec qui on parle. C'est ce qui est fait dans l'échange initial qui permet à un initiateur et un répondeur de se mettre à communiquer. Notamment, il y a obligation de résoudre un puzzle cryptographique, pour rendre plus difficile certaines attaques par déni de service. Voir à ce sujet « DOS-Resistant Authentication with Client Puzzles » de Tuomas Aura, Pekka Nikander et Jussipekka Leiwo, « Deamplification of DoS Attacks via Puzzles » de Jacob Beal et Tim Shepard ou encore « Examining the DOS Resistance of HIP » de Tritilanunt, Suratose, Boyd, Colin A., Foo, Ernest, et Nieto, Juan Gonzalez.
La sécurité est un aspect important de HIP. Les points à garder en tête sont :
Au sujet du TOFU, le RFC cite « Leap-of-faith security is enough for IP mobility » de Miika Kari Tapio Komu et Janne Lindqvist, « Security Analysis of Leap-of-Faith Protocols » de Viet Pham et Tuomas Aura et « Enterprise Network Packet Filtering for Mobile Cryptographic Identities » de Janne Lindqvist, Essi Vehmersalo, Miika Komu et Jukka Manner.
Notre RFC ne décrit qu'une architecture générale, il est complété par les RFC 7401, qui décrit le protocole, RFC 7402, RFC 8003, RFC 8004, RFC 8005, RFC 8046 et RFC 5207. Si des implémentations expérimentales existent déjà et que des serveurs publics utilisent HIP, aucun déploiement significatif n'a eu lieu (cf. l'article « Adoption barriers of network layer protocols: The case of host identity protocol de T. Leva, M. Komu, A. Keranen et S. Luukkainen). Comme le disait un des relecteurs du RFC, « There's a lot of valuable protocol design and deployment experience packed into this architecture and the associated protocol RFCs. At the same time, actual adoption and deployment of HIP so far appears to have been scarce. I don't find this surprising. The existing Internet network/transport/application protocol stack has already become sufficiently complicated that considerable expertise is required to manage it in all but the simplest of cases. Teams of skilled engineers routinely spend hours or days troubleshooting operational problems that crop up within or between the existing layers, and the collection of extensions, workarounds, identifiers, knobs, and failure cases continues to grow. Adding a major new layer--and a fairly complicated one at that--right in the middle of the existing stack seems likely to explode the already heavily-strained operational complexity budget of production deployments. ». L'annexe A.3 décrit les questions pratiques liées au déploiement. Elle rappelle le compte-rendu d'expérience chez Boeing de Richard Paine dans son livre « Beyond HIP: The End to Hacking As We Know It ». Elle tord le cou à certaines légendes répandues (que HIP ne fonctionne pas à travers les routeurs NAT, ou bien qu'il faut le mettre en œuvre uniquement dans le noyau.)
Ah, question implémentations (RFC 6538), on a au moins HIP for Linux et OpenHIP qui ont été adaptés aux dernières versions de HIP, et des protocoles associés.
Les changements depuis le RFC 4423 sont résumés en section 14. Il n'y en a pas beaucoup, à part l'intégration de l'expérience, acquise dans les treize dernières années (et résumée dans le RFC 6538) et des améliorations du texte. La nouvelle annexe A rassemble plein d'informations concrètes, notamment que les questions pratiques de déploiement de HIP, et sa lecture est très recommandée à tous ceux et toutes celles qui s'intéressent à la conception de protocoles. La question de l'agilité cryptographique (RFC 7696) a également été détaillée.
Date de publication du RFC : Juin 2021
Auteur(s) du RFC : D. Crocker (Brandenburg InternetWorking)
Expérimental
Première rédaction de cet article le 23 juin 2021
Qui est l'auteur d'un message reçu par courrier
électronique ? La réponse semblait autrefois simple,
c'est forcément la personne ou l'organisation indiquée dans le champ
From:
. Sauf que des changements récents (et pas
forcément bien inspirés) dans le traitement des messages ont amené à
ce que le From:
soit parfois modifié, par
exemple par certaines listes de diffusion. Il
ne reflète donc plus le vrai auteur. D'où ce RFC qui crée un nouvel
en-tête, Author:
, qui identifie le vrai
émetteur.
La norme pertinente est le RFC 5322, qui
définit des en-têtes comme From:
(auteur d'un
message) ou Sender:
(personne ou entité qui n'a
pas écrit le message mais réalise l'envoi, par exemple parce que
l'auteur n'avait pas le temps, ou bien parce que le message a été
relayé par un intermédiaire). Les amateurs d'archéologie noteront
que le courrier électronique est très ancien,
plus ancien que l'Internet, cette ancienneté étant la preuve de son
efficacité et de sa robustesse. Le premier RFC avec la notion formelle d'auteur d'un
message était le RFC 733.
Une particularité du courrier électronique est que le message ne
va pas toujours directement de la machine de l'auteure à celle du
lecteur (cf. le RFC 5598, sur l'architecture
du courrier). Il y a souvent des intermédiaires
(mediators) comme les gestionnaires de
listes de diffusion, qui prennent un message
et le redistribuent aux abonnées. En raison du problème du
spam, plusieurs mécanismes de protection ont
été développés, qui peuvent poser des problèmes pour ces
intermédiaires. SPF (RFC 7208),
contrairement à une légende tenace, n'affecte pas les listes de
diffusion. (C'est en raison de la distinction entre enveloppe et
en-tête d'un message. Si vous entendez quelqu'un pontifier sur SPF
et la messagerie, et qu'il ne connait pas la différence entre
l'émetteur indiqué dans l'enveloppe et celui marqué dans l'en-tête,
vous pouvez être sûre que cette personne est ignorante.) En
revanche, DKIM (RFC 6376), surtout
combiné avec DMARC (RFC 7489), peut poser des problèmes dans certains cas (le
RFC 6377 les détaille). Pour gérer ces
problèmes, certaines listes de diffusion choisissent de carrément
changer le From:
du message et d'y mettre, par
exemple (ici tiré de l'utile liste
outages, informant des pannes Internet) :
From: Erik Sundberg via Outages <outages@outages.org>
Alors que le message avait en fait été écrit par « Erik Sundberg
<ESundberg@nitelusa.com>
». C'est une
mauvaise idée, mais elle est commune. Dans ce cas, la liste s'est
« approprié » le message et on a perdu le vrai auteur, privant ainsi
la lectrice d'une information utile (dans le cas de la liste
Outages, il a toutefois été préservé dans le
champ Reply-To:
). [Personnellement, il me semble
que l'idéal serait de ne pas modifier le message, préservant ainsi
les signatures DKIM, et donc les vérifications DMARC. Dans les cas
où le message est modifié, DMARC s'obstine à vérifier le
From:
et pas le Sender:
,
ce qui serait pourtant bien plus logique.]
Maintenant, la solution (section 3 du RFC) : un nouvel en-tête,
Author:
, dont la syntaxe est la même que celle
de From:
. Par exemple :
Author: Dave Crocker <dcrocker@bbiw.net>
Cet en-tête peut être mis au départ, par le MUA, ou bien ajouté par un intermédiaire
qui massacre le champ From:
mais veut garder
une trace de sa valeur originale en la recopiant dans le champ
Author:
. Cela n'est possible que s'il n'y a pas
déjà un champ Author:
. S'il existe, il est
strictement interdit de le modifier ou de le supprimer.
À la lecture, par le MUA de réception, le RFC conseille
d'utiliser le champ Author:
, s'il est présent,
plutôt que le From:
, pour afficher ou trier sur
le nom de l'expéditrice.
Comme dans le cas du champ From:
, un
malhonnête peut évidemment mettre n'importe quoi dans le champ
Author:
et il faut donc faire attention à ne
pas lui accorder une confiance aveugle.
Author:
a été ajouté au registre
des en-têtes.
On notera qu'une tentative précédente de préservation de
l'en-tête From:
original avait été faite dans
le RFC 5703, qui créait un
Original-From:
mais uniquement pour les
intermédiaires, pas pour les MUA.
Date de publication du RFC : Août 2021
Auteur(s) du RFC : A. Melnikov (Isode), B. Leiba (Futurewei Technologies)
Chemin des normes
Réalisé dans le cadre du groupe de travail IETF extra
Première rédaction de cet article le 3 septembre 2021
IMAP, protocole d'accès distant à des boites aux lettres n'est pas juste un protocole, c'est tout un écosystème, décrit dans de nombreux RFC. Celui-ci est le socle de la version actuelle d'IMAP, la « 4rev2 ». Elle remplace la précédente version, « 4rev1 » (normalisée dans le RFC 3501). Le principal changement est sans doute une internationalisation plus poussée pour les noms de boites aux lettres et les en-têtes des messages.
Les protocoles traditionnels du courrier électronique, notamment SMTP ne permettaient que d'acheminer le courrier jusqu'à destination. Pour le lire, la solution la plus courante aujourd'hui est en mode client/serveur, avec IMAP, qui fait l'objet de notre RFC. IMAP ne permet pas d'envoyer du courrier, pour cela, il faut utiliser le RFC 6409. IMAP fonctionne sur un modèle où les messages ont plutôt vocation à rester sur le serveur, et où les clients peuvent, non seulement les récupérer mais aussi les classer, les détruire, les marquer comme lus, etc. IMAP permet de tout faire à distance.
Pour apprendre IMAP, il vaut mieux ne pas commencer par le RFC,
qui est très riche. (La lecture du RFC 2683 est recommandée
aux implémenteurs.) Pourtant, le principe de base est simple. IMAP
est client/serveur. Le
client se connecte en TCP au serveur, sur le
port 143, et envoie des commandes sous forme
de texte, comme avec SMTP. Il s'authentifie avec la commande
LOGIN
, choisit une boite aux lettres avec la
commande SELECT
, récupère un message avec la
commande FETCH
. IMAP peut aussi utiliser du
TLS
implicite, sur le port 993. Voici un exemple de session. Notons tout
de suite, c'est un point très important du protocole IMAP, que
chaque commande est précédée d'une étiquette
(tag, voir les sections 2.2.1 et 5.5 du RFC)
arbitraire qui change à chaque commande et qui permet d'associer une
réponse à une commande, IMAP permettant d'envoyer plusieurs
commandes sans attendre de réponse. Les réponses portent
l'étiquette correspondante, ou bien un
astérisque s'il s'agit d'un message du
serveur non sollicité. Ici, le serveur utilise le logiciel Archiveopteryx :
% telnet imap.example.org imap Trying 192.0.2.23... Connected to imap.example.org. Escape character is '^]'. * OK [CAPABILITY IMAP4rev1 AUTH=CRAM-MD5 AUTH=DIGEST-MD5 AUTH=PLAIN COMPRESS=DEFLATE ID LITERAL+ STARTTLS] imap.example.org Archiveopteryx IMAP Server com1 LOGIN stephane vraimentsecret com1 OK [CAPABILITY IMAP4rev1 ACL ANNOTATE BINARY CATENATE CHILDREN COMPRESS=DEFLATE CONDSTORE ESEARCH ID IDLE LITERAL+ NAMESPACE RIGHTS=ekntx STARTTLS UIDPLUS UNSELECT URLAUTH] done com2 SELECT INBOX * 189 EXISTS * 12 RECENT * OK [UIDNEXT 1594] next uid * OK [UIDVALIDITY 1] uid validity * FLAGS (\Deleted \Answered \Flagged \Draft \Seen) * OK [PERMANENTFLAGS (\Deleted \Answered \Flagged \Draft \Seen \*)] permanent flags com2 OK [READ-WRITE] done
Le serveur préfixe ses réponses par OK si tout va bien, NO s'il ne pas réussi à faire ce qu'on lui demande et BAD si la demande était erronnée (commande inconnue, par exemple). Ici, avec l'étiquette A2 et un Dovecot :
A2 CAVAPAS A2 BAD Error in IMAP command CAVAPAS: Unknown command (0.001 + 0.000 secs).
Si le serveur n'est accessible qu'en TLS implicite (TLS démarrant
automatiquement au début, sans STARTTLS
), on
peut utiliser un client TLS en ligne de commande, comme celui de
GnuTLS :
% gnutls-cli -p 993 imap.maison.example - Certificate[0] info: - subject `CN=imap.maison.example', issuer `CN=CAcert Class 3 Root,OU=http://www.CAcert.org,O=CAcert Inc.', serial 0x02e9bf, RSA key 2048 bits, signed using RSA-SHA512, activated `2020-12-14 14:47:42 UTC', expires `2022-12-14 14:47:42 UTC', pin-sha256="X21ApSVQ/Qcb5Q8kgL/YqlH2XuEco/Rs2X2EgkvDEdI=" ... - Status: The certificate is trusted. - Description: (TLS1.3)-(ECDHE-SECP256R1)-(RSA-PSS-RSAE-SHA256)-(AES-256-GCM) [À partir d'ici, on fait de l'IMAP] * OK [CAPABILITY IMAP4rev1 SASL-IR LOGIN-REFERRALS ID ENABLE IDLE LITERAL+ AUTH=PLAIN] Dovecot (Debian) ready. A1 LOGIN stephane vraimenttropsecret A1 OK [CAPABILITY IMAP4rev1 SASL-IR LOGIN-REFERRALS ID ENABLE IDLE SORT SORT=DISPLAY THREAD=REFERENCES THREAD=REFS THREAD=ORDEREDSUBJECT MULTIAPPEND URL-PARTIAL CATENATE UNSELECT CHILDREN NAMESPACE UIDPLUS LIST-EXTENDED I18NLEVEL=1 CONDSTORE QRESYNC ESEARCH ESORT SEARCHRES WITHIN CONTEXT=SEARCH LIST-STATUS BINARY MOVE SNIPPET=FUZZY PREVIEW=FUZZY STATUS=SIZE SAVEDATE LITERAL+ NOTIFY SPECIAL-USE] Logged in A2 SELECT INBOX ... * 0 EXISTS * 0 RECENT A2 OK [READ-WRITE] Select completed (0.002 + 0.000 + 0.001 secs). ... A14 FETCH 1 (BODY.PEEK[HEADER.FIELDS (SUBJECT)]) * 1 FETCH (BODY[HEADER.FIELDS (SUBJECT)] {17} Subject: Test ) A14 OK Fetch completed (0.007 + 0.000 + 0.006 secs).
Les commandes IMAP, comme FETCH
dans l'exemple
ci-dessus, sont décrites dans la section 6 du RFC.
Pour tester son serveur IMAP, on peut aussi utiliser fetchmail et
lui demander d'afficher toute la session avec -v
-v
:
% fetchmail -v -v -u MYLOGIN imap.1and1.fr Enter password for MYLOGIN@imap.1and1.fr: fetchmail: 6.3.6 querying imap.1and1.fr (protocol auto) at Tue Apr 15 12:00:27 2008: poll started fetchmail: 6.3.6 querying imap.1and1.fr (protocol IMAP) at Tue Apr 15 12:00:27 2008: poll started Trying to connect to 212.227.15.141/143...connected. fetchmail: IMAP< * OK IMAP server ready H mimap3 65564 fetchmail: IMAP> A0001 CAPABILITY fetchmail: IMAP< * CAPABILITY IMAP4rev1 LITERAL+ ID STARTTLS CHILDREN QUOTA IDLE NAMESPACE UIDPLUS UNSELECT SORT AUTH=LOGIN AUTH=PLAIN fetchmail: IMAP< A0001 OK CAPABILITY finished. fetchmail: Protocol identified as IMAP4 rev 1 fetchmail: IMAP> A0002 STARTTLS fetchmail: IMAP< A0002 OK Begin TLS negotiation. fetchmail: Issuer Organization: Thawte Consulting cc fetchmail: Issuer CommonName: Thawte Premium Server CA fetchmail: Server CommonName: imap.1and1.fr fetchmail: imap.1and1.fr key fingerprint: 93:13:99:6A:3F:23:73:C3:00:37:4A:39:EE:22:93:AB fetchmail: IMAP> A0003 CAPABILITY fetchmail: IMAP< * CAPABILITY IMAP4rev1 LITERAL+ ID CHILDREN QUOTA IDLE NAMESPACE UIDPLUS UNSELECT SORT AUTH=LOGIN AUTH=PLAIN fetchmail: IMAP< A0003 OK CAPABILITY finished. fetchmail: Protocol identified as IMAP4 rev 1 fetchmail: imap.1and1.fr: upgrade to TLS succeeded. fetchmail: IMAP> A0004 LOGIN "m39041005-1" * fetchmail: IMAP< A0004 OK LOGIN finished. fetchmail: selecting or re-polling default folder fetchmail: IMAP> A0005 SELECT "INBOX"
En IMAP, un message peut être identifié par un UID
(Unique Identifier) ou par un numéro. Le numéro
n'est pas global, il n'a de sens que pour une boite donnée, et il
peut changer, par exemple si on détruit des messages. L'UID, lui,
est stable, au moins pour une session (et de préférence pour
toutes). Les messages ont également des attributs
(flags). Ceux qui commencent par une
barre oblique inverse comme
\Seen
(indique que le message a été lu),
\Answered
(on y a répondu), etc, sont
obligatoires, et il existe également des attributs optionnels
commençant par un dollar comme
$Junk
(spam). Les
fonctions de recherche d'IMAP pourront utiliser ces attributs comme
critères de recherche. Les attributs optionnels
(keywords) sont listés dans un
registre IANA, et on peut en ajouter (le RFC 5788
explique comment).
IMAP a des fonctions plus riches, notamment la possibilité de chercher dans les messages (section 6.4.4 du RFC), ici, on extrait les messages de mai 2007, puis on récupère le sujet du premier message, par son numéro :
com10 SEARCH since 1-May-2007 before 31-May-2007 * SEARCH 12 com10 OK done com11 FETCH 1 (BODY.PEEK[HEADER.FIELDS (SUBJECT)]) * 1 FETCH (BODY[HEADER.FIELDS (Subject)] {17} Subject: Rapport sur les fonctions de vue dans Archiveopteryx ) com11 OK done
IMAP 4rev2 est complètement internationalisé et, par exemple, peut gérer des en-têtes en UTF-8, comme décrits dans le RFC 6532, ce qui est une des améliorations par rapport à 4rev1.
Vous noterez dans les réponses CAPABILITY
montrées plus haut, que le serveur indique la ou les versions d'IMAP
qu'il sait gérer. Il peut donc annoncer 4rev1 et 4rev2, s'il est
prêt à gérer les deux. Le client devra utiliser la commande
ENABLE
pour choisir. S'il choisit 4rev2, le
serveur pourra utiliser les nouveautés de 4rev2, notamment dans le
domaine de l'internationalisation (noms de boites en UTF-8, alors
que 4rev1 savait tout juste utiliser un bricolage basé sur
UTF-7).
IMAP 4rev2 est compatible avec 4rev1 (un logiciel de la précédente norme peut interagir avec un logiciel de la nouvelle). Il peut même interagir avec l'IMAP 2 du RFC 1776 (IMAP 3 n'a jamais été publié), cf. RFC 2061.
L'annexe E résume les principaux changements depuis le RFC 3501, qui normalisait IMAP 4rev1 :
BINARY
du RFC 3516 ou le LIST-EXTENDED
du RFC 5258.UNSEEN
de la commande
SELECT
.La réalisation d'un client ou d'un serveur IMAP soulève plein de problèmes pratiques, que la section 5 de notre RFC traite. Par exemple, les noms des boites aux lettres peuvent être en Unicode, plus précisément le sous-ensemble d'Unicode du RFC 5198 (cela n'était pas possible en 4rev1). Un logiciel doit donc s'attendre à rencontrer de tels noms.
IMAP est mis en œuvre dans de nombreux serveurs comme Dovecot, Courier, ou Archiveopteryx, déjà cité (mais qui semble abandonné). Mais, comme vous l'avez vu dans les exemples de session IMAP cités plus haut, la version de notre RFC, « 4rev2 » n'a pas encore forcément atteint tous les logiciels.
Côté client, on trouve du IMAP dans beaucoup de logiciels, des webmails, des MUA classiques comme mutt, des MUA en ligne de commande comme fetchmail, très pratique pour récupérer son courrier. (Si vous écrivez un logiciel IMAP à partir de zéro, ce RFC recommande la lecture préalable du RFC 2683.)
Il existe également des bibliothèques toutes faites pour programmer son client IMAP à son goût comme imaplib pour Python. Voici un exemple d'un court programme Python qui se connecte à un serveur IMAP, sélectionne tous les messages et les récupère. On note que la bibliothèque a choisi de rester très proche du vocabulaire du RFC :
import imaplib # Unlike what the documentation says, "host" has no proper default. connection = imaplib.IMAP4_SSL(host='localhost') connection.login("stephane", "thisissecret") connection.select() # Select the default mailbox typee, data = connection.search(None, "ALL") for num in data[0].split(): # Fetches the whole message # "RFC822" est le terme traditionnel mais le format des messages # est désormais dans le RFC 5322. typ, data = connection.fetch(num, '(RFC822)') print('Message %s\n%s\n' % (num, data[0][1])) connection.close() connection.logout()
Date de publication du RFC : Août 2021
Auteur(s) du RFC : M. Niedermayer, D. Rice, J. Martinez
Pour information
Réalisé dans le cadre du groupe de travail IETF cellar
Première rédaction de cet article le 24 août 2021
Ce RFC décrit FFV1, un encodage de flux vidéo sans pertes, et sans piège connu, par exemple de brevet.
Certains encodages vidéo font perdre de l'information, c'est-à-dire que, après décompression, on ne retrouve pas tout le flux original. Ces encodages avec pertes tirent profit des limites de l'œil humain (on n'encode pas ce qui ne se voit pas) mais peut poser des problèmes dans certains cas. Par exemple, si on veut stocker une vidéo sur le long terme, il est certainement préférable qu'elle soit aussi « authentique » que possible, et donc encodée sans pertes. Les organisations qui font de l'archivage sur le long terme (comme l'INA) ont tout intérêt à privilégier un format sans pertes et libre, qui sera encore utilisable dans 50 ans, plutôt que de choisir les derniers gadgets à la mode. Si on souhaite faire analyser cette vidéo par autre chose qu'un œil humain, par exemple un programme qui souhaite avoir la totalité des informations, là encore, le « sans perte » est nécessaire. FFV1 est sans perte, pour le plus grand bénéfice de l'archivage et de la science. C'est le premier codec sans perte documenté par l'IETF.
FFV1 ne date pas d'aujourd'hui, et le RFC traite d'un coup trois versions de FFV1 (section 4.2.1) :
Le nom FFV1 veut dire « FF Video 1 » et FF est une référence à FFmpeg, le programme de référence (qui a largement précédé la spécification officielle) dont le nom venait de Fast Forward (avance rapide).
Je ne vais pas vous décrire les algorithmes de FFV1, ça dépasse largement mes compétences. Comme le note le RFC, il faut d'abord connaitre le codage par intervalle, et le modèle YCbCr. Je vous renvoie donc à l'article en français d'un des auteurs et bien sûr au RFC lui-même. FFV1 utilise souvent des techniques un peu anciennes, pour éviter les problèmes de brevet.
Le RFC utilise du pseudo-code pour décrire l'encodage FFV1. Ce pseudo-code particulier est assez proche de C. Vous verrez donc du pseudo-code du genre :
for (i = 0; i < quant_table_set_count; i++) { states_coded if (states_coded) { for (j = 0; j < context_count[ i ]; j++) { for (k = 0; k < CONTEXT_SIZE; k++) { initial_state_delta[ i ][ j ][ k ] } } } }
Le type de média video/FFV1
a été enregistré
à l'IANA pour les flux encodés en FFV1.
FFV1 décrit un encodage d'un flux vidéo, pas un format de conteneur. Le flux en question doit donc être inclus dans un conteneur, par exemple aux formats AVI, NUT ou Matroska (section 4.3.3 pour les détails de l'inclusion dans chaque format).
Comme pour tout codec utilisé sur le grand méchant Internet, un programme qui lit du FFV1 doit être paranoïaque et ne doit pas supposer que le flux vidéo est forcément correct. Même si le contenu du fichier est délibérement malveillant, le décodeur ne doit pas allouer de la mémoire à l'infini ou boucler sans fin (ou, pire encore, exécuter du code arbitraire par exemple parce qu'il y aura eu un débordement de pile ; FFV1 lui-même ne contient pas de code exécutable). Le RFC (section 6) donne l'exemple d'un calcul de taille d'une image où on multiplie la largeur par la hauteur, sans plus de précautions. Si cela provoque un dépassement d'entier, des tas de choses vilaines peuvent arriver. Un exemple de précaution : FFmpeg a été soumis à des flux FFV1 corrects et à des données aléatoires, tout en étant examiné par Valgrind et le vérificateur de Clang et aucun accès mémoire anormal n'a été détecté.
Question mises en œuvre, FFV1 est suffisamment ancien pour que de nombreux programmes sachent le décoder. L'implémentation de référence est FFmpeg (qui sait aussi encoder en FFV1). Il y a également un décodeur en Go, développé en même temps que la spécification et il y a aussi MediaConch dont le développement a permis de détecter des incohérences entre le projet de spécification et certains programmes. La spécification elle-même a été développée sur GitHub si vous voulez suivre son histoire, et son futur (une version 4 est prévue).
Comme exemple d'une vidéo encodée en FFV1, j'ai pris les 20
premières secondes de mon
exposé au FOSDEM 2021. Le fichier (pour 20 secondes de
vidéo !) fait 60 mégaoctets : la
compression sans perte est évidemment moins efficace que si on
accepte les pertes. Le fichier a été produit par FFmpeg
(ffmpeg -i retro_gemini.webm -vcodec ffv1 -level 3 -to
00:00:20 retro_gemini.mkv
, notez que, si FFV1 lui-même
est sans perte, si vous encodez en FFV1 à partir d'un fichier déjà
comprimé avec perte, vous ne recupérez évidemment pas ce qui a été
perdu). mediainfo vous montre son contenu :
% mediainfo retro_gemini.mkv General ... Format : Matroska Movie name : Gemini, a modern protocol that looks retro ... DATE : 2021-02-07 EVENT : FOSDEM 2021 SPEAKERS : Stéphane Bortzmeyer Video ID : 1 Format : FFV1 Format version : Version 3.4 Codec ID : V_MS/VFW/FOURCC / FFV1 Duration : 20 s 0 ms Bit rate mode : Variable Bit rate : 25.7 Mb/s Width : 1 280 pixels Height : 720 pixels Frame rate mode : Constant Frame rate : 25.000 FPS Color space : YUV Compression mode : Lossless ...
Ou bien avec ffprobe :
% fprobe -show_format -show_streams retro_gemini.mkv ... Input #0, matroska,webm, from 'retro_gemini.mkv': Metadata: title : Gemini, a modern protocol that looks retro DATE : 2021-02-07 ... [STREAM] index=0 codec_name=ffv1 codec_long_name=FFmpeg video codec #1 codec_type=video codec_time_base=1/25 codec_tag_string=FFV1 width=1280 height=720 ...
Merci à Jérôme Martinez pour sa relecture attentive.
Date de publication du RFC : Juin 2021
Auteur(s) du RFC : J. Arkko (Ericsson), C. Jennings (Cisco), Z. Shelby (ARM)
Chemin des normes
Réalisé dans le cadre du groupe de travail IETF core
Première rédaction de cet article le 1 juillet 2021
Voici un nouvel espace de noms pour des URN, dev
, afin de
stocker des identificateurs pour des équipements matériels, par
exemple pour les inventaires.
Les URN, un
sous-ensemble des URI, et des cousins des URL, sont normalisés
dans le RFC 8141. On peut placer des URN
partout où on peut mettre des URI, par exemple comme noms dans
SenML (RFC 8428). Pour
des objets contraints, les URN risquent d'être un peu longs par
rapport à, par exemple, une adresse IPv4,
mais ils sont plus souples. Un URN commence par le plan
urn:
, suivie d'un espace de noms. Ces espaces
sont stockés
dans un registre IANA. Ce nouveau RFC crée un nouvel espace de noms,
dev
. On pourra donc désormais avec des URN
comme urn:dev:os:32473-123456
(qui identifie la
machine 123456 de l'organisation 32473). Ces identificateurs de
machines pourront être utilisés toutes les fois où on a besoin de
désigner une machine, dans les données du RFC 8428, dans
des inventaires, etc.
Passons au concret maintenant, avec la section 3 du RFC, qui
donne la définition formelle de l'espace de noms
dev
. Ces URN sont évidemment conformes à la
norme des URN, le RFC 8141. Comme tous les
URN, ceux sous urn:dev:
ne sont pas prévus pour
être résolus automatiquement. Contrairement aux URL, ils ne
fournissent pas de moyen d'accès à une ressource. Bien sûr, si on
les met dans une base de données, d'inventaire de ses machines, par
exemple, on pourra retrouver l'information, mais ce RFC ne spécifie
aucun mécanisme de résolution standard (section 3.6 du RFC).
Après le urn:dev:
, ces URN prennent
un composant supplémentaire, qui identifie le type d'identificateur
dont dérive l'URN. Cela peut être :
mac
, où l'identificateur est une
adresse MAC, enregistrée
selon les procédures IEEE, au format
EUI-64. C'est par exemple
urn:dev:mac:acde48234567019f
(pour l'adresse MAC
ac:de:48:23:45:67:01:9f
). Si vous faites varier
l'adresse MAC, par exemple pour protéger votre vie privée, l'URN
n'est plus un identifiant stable.ow
, qui s'appuie sur
1-Wire, un système privateur.org
, avec des identificateurs
spécifiques à une organisation, identifiée par son PEN
(les PEN sont normalisés dans le RFC 2578). Les PEN sont enregistrés
à l'IANA. Prenons comme exemple celui d'une entreprise dont
j'étais un co-fondateur, 9319. Si cette entreprise utilise des
noms pour ses machines, la machine marx
aurait comme URN urn:dev:org:9319-marx
.os
, comme les org
mais plutôt prévus pour des numéros de série. Si cette entreprise
numérote toutes ses machines en partant de 1, un URN serait
urn:dev:os:9319-1
. (Notez que des versions
précédentes des URN dev
utilisaient
os
pour les identificateurs LwM2M.) Ainsi, le
RIPE-NCC qui a le PEN 15854 pourrait nommer
ses sondes Atlas
d'après leur numéro unique et donc la sonde 6593
pourrait être désignée par
urn:dev:os:15854-6593
. Notez qu'on pourrait
avoir ici une intéressante discussion sur l'intérêt respectif des
URN (urn:dev:os:15854-6593
) et des URL
(https://atlas.ripe.net/probes/6593/
).ops
, qui ressemble à
l'os
, mais avec un identificateur de produit
entre le PEN et le numéro de série, par exemple
urn:dev:ops:9319-coffeemachine-2
pour la
deuxième machine à café de
l'organisation. (Comme os
, il avait été
utilisé pour l'Open Mobile
Alliance.)example
pour des exemples, comme
urn:dev:example:1234
.
Enfin, une machine identifiée par un de ces URN peut avoir une
partie particulière de la machine désignée par une chaine de
caractères après un tiret bas. Ainsi,
urn:dev:os:9319-1_alimentation
serait
l'alimentation de la machine
urn:dev:os:9319-1
.
Notez que l'équivalence de deux URN est sensible à la casse donc attention, par exemple, à la façon dont vous écrivez les adresses MAC. Le RFC recommande de tout mettre en minuscules.
Idéalement, on veut bien sûr qu'un URN dev
identifie une machine et une seule. Mais, en pratique, cela peut
dépendre du type d'identificateurs utilisé. Ainsi, les adresses MAC
ne sont pas forcément uniques, entre autres parce que certains
fabricants ont déjà réutilisé des adresses.
Petit avertissement sur la vie privée : les identificateurs décrits dans ce RFC sont prévus pour être très stables sur le long terme (évidemment, puisque leur but est de garder trace d'une machine) et leur utilisation imprudente (par exemple si on envoie un de ces URN avec les données d'un utilisateur anonyme) peut permettre une surveillance accrue (sections 3.4 et 6.1 du RFC). Le RFC 7721 détaille les risques de ces identificateurs à longue durée de vie.
Le RFC note (section 1) qu'il existe d'autres catégories
d'identificateurs qui, selon le cas, pourraient concurrencer nos URN
de l'espace de noms dev
. C'est le cas par
exemple des condensats du RFC 6920, des
IMEI du RFC 7254, des
MEID du RFC 8464 et
bien sûr des UUID du RFC 9562. Tous peuvent se
représenter sous forme d'URI, et parfois d'URN. Ils ont leurs
avantages et leurs inconvénients, le choix est vaste.
Pour les gens qui utiisent le SGBD
PostgreSQL, notez qu'il n'a pas de type de
données « URI » donc, si on veut stocker les URN de
notre RFC dans PostgreSQL, il faut utiliser le type
TEXT
, ou bien installer une extension comme
pguri. Selon ce
qu'on veut faire de ces URN, on peut aussi prendre une solution plus
simple qui ne nécessite pas d'installer d'extension, ici pour une
organisation qui met toutes ces machines en
urn:dev:os:9319-…
:
CREATE DOMAIN urndev AS text CHECK (VALUE ~ '^urn:dev:os:9319-[0-9]+(_[a-z0-9]+)?$'); COMMENT ON DOMAIN urndev IS 'URN DEV (RFC 9039) for our devices'; CREATE TABLE Devices (id SERIAL, urn urndev UNIQUE NOT NULL, comments TEXT); INSERT INTO Devices (urn, comments) VALUES ('urn:dev:os:9319-2', 'No comment'); INSERT INTO Devices (urn) VALUES ('urn:dev:os:9319-1_alimentation'); INSERT INTO Devices (urn, comments) VALUES ('urn:dev:os:9319-666', 'Beast');
Date de publication du RFC : Mai 2021
Auteur(s) du RFC : J. Gould (VeriSign), M. Casanova (SWITCH)
Chemin des normes
Réalisé dans le cadre du groupe de travail IETF regext
Première rédaction de cet article le 30 mai 2021
Le protocole EPP, qui sert notamment lors de la communication entre un registre (par exemple de noms de domaine) et son client, est extensible : on peut rajouter de nouveaux types d'objets et de nouvelles informations. Normalement, le serveur EPP n'envoie au client ces nouveautés que si le client a annoncé, lors de la connexion, qu'il savait les gérer. Et s'il ne l'a pas fait, que faire de ces données, ces unhandled namespaces ? Ce nouveau RFC propose de les envoyer quand même, mais dans un élément XML prévu pour des informations supplémentaires, et qui ne devrait donc rien casser chez le client.
Le but est de rester compatible avec l'EPP standard,
tel que normalisé dans le RFC 5730. Prenons
l'exemple de l'extension pour DNSSEC du RFC 5910. Comme toutes les extensions EPP, elle utilise les
espaces de noms XML. Cette extension
particulière est identifiée par l'espace de noms
urn:ietf:params:xml:ns:secDNS-1.1
. À
l'établissement de la connexion, le serveur annonce les extensions
connues :
<greeting <svcMenu> ... <svcExtension> <extURI>urn:ietf:params:xml:ns:secDNS-1.1</ns0:extURI>
Et le client annonce ce qu'il sait gérer :
<epp xmlns="urn:ietf:params:xml:ns:epp-1.0"> <command> <login> ... <svcs> <svcExtension><extURI>urn:ietf:params:xml:ns:secDNS-1.1</extURI></svcExtension> ...
(<svcExtension>
est décrit dans le RFC 5730, sections 2.4 et 2.9.1.1). Ici, le serveur
a annoncé l'extension DNSSEC et le client l'a acceptée. Tout le
monde va donc pouvoir envoyer et recevoir des messages spécifiques à
cette extension, comme l'ajout d'une clé :
<extension> <update xmlns="urn:ietf:params:xml:ns:secDNS-1.1"> <add xmlns="urn:ietf:params:xml:ns:secDNS-1.1"> <dsData xmlns="urn:ietf:params:xml:ns:secDNS-1.1"> ... <digest xmlns="urn:ietf:params:xml:ns:secDNS-1.1">076CF6DA3692EFE72434EA1322177A7F07023400E4D1A1F617B1885CF328C8AA</digest> ...
Mais, si le client ne gère pas une extension (et ne l'a donc pas
indiquée dans son <login>
), que peut
faire le serveur s'il a quand même besoin d'envoyer des messages
spécifiques à cette extension inconnue du client, ce
unhandled namespace ? C'est particulièrement
important pour la messagerie EPP (commande
<poll>
) puisque le serveur peut, par
exemple, mettre un message dans la boite sans connaitre les
capacités du client, mais cela peut affecter également d'autres
activités.
La solution de notre RFC est d'utiliser un élément EPP déjà
normalisé (RFC 5730, secton 2.6),
<extValue>
, qui permet d'ajouter des
informations que le client ne pourra pas analyser automatiquement,
comme par exemple un message d'erreur lorsque le serveur n'a pas pu
exécuter l'opération demandée. Notre RFC étend cet
<extValue>
au cas des espaces de noms non
gérés. Le sous-élément <value>
contiendra
l'élément XML appartenant à l'espace de noms que le client ne sait
pas gérer, et le sous-élément <reason>
aura comme contenu un message d'information dont la forme
recommandée est NAMESPACE-URI not in login
services
où NAMESPACE-URI
est le
unhandled namespace. Par exemple, le RFC cite un
cas où le registre ne gère pas l'extension DNSSEC du RFC 5910 et répond :
<response> ... <extValue> <value> <secDNS:infData xmlns:secDNS="urn:ietf:params:xml:ns:secDNS-1.1"> <secDNS:dsData> ... <secDNS:digest>49FD46E6C4B45C55D4AC</secDNS:digest> </secDNS:dsData> </secDNS:infData> </value> <reason> urn:ietf:params:xml:ns:secDNS-1.1 not in login services </reason> </extValue>
(Le RFC a aussi un autre exemple, avec l'extension de rédemption du RFC 3915.)
Ce RFC ne change pas le protocole EPP : il ne fait que décrire
une pratique, compatible avec les clients actuels, pour leur donner
le plus d'informations possible. Toutefois, pour informer tout le
monde, cette pratique fait l'objet elle-même d'une extension,
urn:ietf:params:xml:ns:epp:unhandled-namespaces-1.0
,
que le serveur va inclure dans son
<greeting>
et que le client mettra dans
sa liste d'extensions acceptées. (Cet espace de nom a été mis
dans le registre IANA créé par le RFC 3688. L'extension a été ajoutée au registre
des extensions EPP introduit par le RFC 7451.) De toute façon, le client a tout intérêt à inclure
dans sa liste toutes les extensions qu'il gère
et à regarder s'il y a des
<extValue>
dans les réponses qu'il reçoit
(même si la commande EPP a été un succès) ; cela peut donner des
idées aux développeurs sur des extensions supplémentaires qu'il
serait bon de gérer. Quant au serveur, il est bon que son
administrateur regarde s'il y a eu des réponses pour des
unhandled namespaces et prenne ensuite contact
avec l'administrateur du client pour lui signaler ce manque.
Notez que ce RFC s'applique aux extensions portant sur les objets manipulés en EPP (par exemple les noms de domaine) et à celles portant sur les séquences de commandes et de réponses EPP, mais pas aux extensions portant sur le protocole lui-même (cf. RFC 3735).
Question mises en œuvre de ce RFC, le SDK de Verisign
inclut
le code nécessaire (dans le fichier
gen/java/com/verisign/epp/codec/gen/EPPFullExtValuePollMessageFilter.java
). D'autre
part, le registre du .ch
utilise ce concept d'espaces de noms inconnus pour indiquer au
BE les
changements d'un domaine provoqués par l'utilisation du CDS du RFC 7344, puisque ces changements ne sont pas
passés par EPP.
Date de publication du RFC : Mai 2021
Auteur(s) du RFC : G. Lozano (ICANN), J. Gould, C. Thippeswamy (VeriSign)
Chemin des normes
Réalisé dans le cadre du groupe de travail IETF regext
Première rédaction de cet article le 30 mai 2021
Le RFC 8909 normalisait un format générique pour les séquestres d'objets enregistrés dans un registre. Ce nouveau RFC 9022 précise ce format pour le cas spécifique des registres de noms de domaine (dont les objets enregistrés sont les noms de domaine, les contacts, les serveurs de noms, etc).
Rappelons que le but d'un séquestre est de permettre à un tiers de reprendre les opérations d'un registre (par exemple un registre de noms de domaine) en cas de défaillance complète de celui-ci. Pas juste une panne matérielle qui fait perdre des données, non, une défaillance grave, par exemple une faillite, qui fait que plus rien ne marche. Le registre doit donc envoyer régulièrement des données à l'opérateur de séquestre qui, au cas où la catastrophe survient, enverra ces données au nouveau registre, qui sera en mesure (en théorie…) de charger les données dans une nouvelle base et de reprendre les opérations. Sous quel format sont envoyées ces données ? Car il faut évidemment un format ouvert et documenté, pour que le nouveau registre ait pu développer un programme d'importation des données. C'est le but du RFC 8909 et de notre nouveau RFC 9022. Ils spécifient un format à base de XML (avec possibilité de CSV).
Par rapport au format générique du RFC 8909, notre RFC ajoute les objets spécifiques de l'industrie des noms de domaine (il est recommandé de réviser la terminologie du RFC 8499) :
Le format concret du séquestre se décline en deux « modèles », un en XML et un en CSV (RFC 4180), mais j'ai l'impression que le XML est nettement plus utilisé. Dans tous les cas :
Pour CSV, chaque fichier CSV représente une table (les noms de
domaine, les contacts, les serveurs de noms…) et chaque ligne du
fichier un objet. Le modèle de données est décrit plus précisément
en section 4.6. Voici un exemple d'une ligne extraite du fichier des
noms de domaine, décrivant le domaine
domain1.example
, créé le 3 avril 2009, et dont
le titulaire a pour identificateur (handle)
registrantid
:
domain1.example,Ddomain2-TEST,,,registrantid,registrarX,registrarX,clientY,2009-04-03T22:00:00.0Z,registrarX,clientY,2009-12-03T09:05:00.0Z,2025-04-03T22:00:00.0Z
Les contacts seraient mis dans un autre fichier CSV, avec un fichier de jointure pour faire le lien entre domaines et contacts.
Et pour XML ? Il s'inspire beaucoup des éléments XML échangés
avec EPP, par exemple pour la liste des états
possibles pour un domaine. Voici un exemple (RDE = Registry
Data Escrow, et l'espace de noms
correspondant rdeDom
est
urn:ietf:params:xml:ns:rdeDomain-1.0
) :
<rdeDom:domain> <rdeDom:name>jdoe.example</rdeDom:name> <rdeDom:roid>DOM03-EXAMPLE</rdeDom:roid> <rdeDom:status s="ok"/> <rdeDom:registrant>IZT01</rdeDom:registrant> <rdeDom:contact type="tech">IZT01</rdeDom:contact> <rdeDom:contact type="billing">IZT01</rdeDom:contact> <rdeDom:contact type="admin">IZT01</rdeDom:contact> <rdeDom:ns> <domain:hostObj>ns.jdoe.example</domain:hostObj> </rdeDom:ns> <rdeDom:clID>RAR03</rdeDom:clID> <rdeDom:crRr>RAR03</rdeDom:crRr> <rdeDom:crDate>2019-12-26T14:18:40.65647Z</rdeDom:crDate> <rdeDom:exDate>2020-12-26T14:18:40.509742Z</rdeDom:exDate> </rdeDom:domain>
On voit que cela ressemble en effet beaucoup à ce qui avait été envoyé en EPP pour créer le domaine (cf. RFC 5731). Si vous voulez un exemple complet et réaliste, regardez les sections 14 et 15 du RFC.
Et voici un exemple de contact (RFC 5733) :
<rdeContact:contact> <rdeContact:id>IZT01</rdeContact:id> <rdeContact:status s="ok"/> <rdeContact:postalInfo type="loc"> <contact:name>John Doe</contact:name> <contact:addr> <contact:street>12 Rue de la Paix</contact:street> <contact:city>Paris</contact:city> <contact:pc>75002</contact:pc> <contact:cc>FR</contact:cc> </contact:addr> </rdeContact:postalInfo> <rdeContact:voice>+33.0353011234</rdeContact:voice> <rdeContact:email>john.doe@foobar.example</rdeContact:email> <rdeContact:clID>RAR03</rdeContact:clID> <rdeContact:crRr>RAR03</rdeContact:crRr> <rdeContact:crDate>2019-12-26T13:47:05.580392Z</rdeContact:crDate> <rdeContact:disclose flag="0"> <contact:name type="loc"/> <contact:addr type="loc"/> <contact:voice/> <contact:fax/> <contact:email/> </rdeContact:disclose> </rdeContact:contact>
On notera l'élement <disclose>
qui
indique qu'on ne doit pas diffuser le nom, l'adresse ou d'autres
éléments sur le contact (normal, il s'agit d'une personne physique,
et la loi Informatique & Libertés
s'applique, cf. section 14 du RFC). La jointure avec les domaines
dont il est contact (comme le jdoe.example
plus
haut), se fait sur l'identificateur (élément
<id>
, dit aussi
handle). L'information sur l'adresse a le type
loc
, ce qui veut dire qu'elle peut utiliser
tout le jeu de caractères Unicode. Avec le
type int
, elle serait restreinte à l'ASCII (une très ancienne erreur fait que
EPP appelle loc
- local, ce
qui est internationalisé et int
-
international ce qui est restreint aux lettres
utilisées en anglais).
Et enfin, un objet représentant un serveur de noms (RFC 5732) :
<rdeHost:host> <rdeHost:name>ns1.foobar.example</rdeHost:name> <rdeHost:status s="ok"/> <rdeHost:addr ip="v6">2001:db8:cafe:fada::53</rdeHost:addr> <rdeHost:clID>RAR02</rdeHost:clID> <rdeHost:crRr>RAR02</rdeHost:crRr> <rdeHost:crDate>2020-05-13T12:37:41.788684Z</rdeHost:crDate> </rdeHost:host>
Ce format de séquestre permet aussi de représenter des objets qui n'ont pas d'équivalent en EPP, comme les bureaux d'enregistrement, qui ne peuvent pas être créés en EPP puisque la session EPP est liée au client du registre, donc au bureau d'enregistrement. Un exemple de BE (Bureau d'Enregistrement) :
<rdeRegistrar:registrar> <rdeRegistrar:id>RAR21</rdeRegistrar:id> <rdeRegistrar:name>Name Business</rdeRegistrar:name> <rdeRegistrar:status>ok</rdeRegistrar:status> <rdeRegistrar:postalInfo type="loc"> <rdeRegistrar:addr> <rdeRegistrar:street>1 rue du Test</rdeRegistrar:street> <rdeRegistrar:city>Champignac</rdeRegistrar:city> <rdeRegistrar:cc>FR</rdeRegistrar:cc> </rdeRegistrar:addr> </rdeRegistrar:postalInfo> <rdeRegistrar:voice>+33.0639981234</rdeRegistrar:voice> <rdeRegistrar:fax>+33.0199001234</rdeRegistrar:fax> <rdeRegistrar:email>master-of-domains@namebusiness.example</rdeRegistrar:email> </rdeRegistrar:registrar>
On peut aussi mettre dans le séquestre des références vers ses tables IDN (que l'ICANN exige mais qui n'ont aucun intérêt). Plus intéressant, la possibilité de stocker dans le séquestre les listes de termes traités spécialement, par exemple interdits ou bien soumis à un examen manuel. Cela se nomme NNDN pour « NNDN's not domain name », oui, c'est récursif. Voici un exemple :
<rdeNNDN:NNDN> <rdeNNDN:uName>gros-mot.example</rdeNNDN:uName> <rdeNNDN:nameState>blocked</rdeNNDN:nameState> <rdeNNDN:crDate>2005-04-23T11:49:00.0Z</rdeNNDN:crDate> </rdeNNDN:NNDN>
Tous les registres n'ont pas les mêmes règles et le RFC décrit également les mécanismes qui permettent de spécifier dans le séquestre les contraintes d'intégrité spécifiques d'un registre. L'opérateur de séquestre, qui reçoit le fichier XML ou les fichiers CSV, est censé vérifier tout cela (autrement, il ne joue pas son rôle, s'il se contente de stocker aveuglément un fichier). La section 8 de notre RFC décrit plus en profondeur les vérifications recommandées, comme de vérifier que les contacts indiqués pour chaque domaine sont bien présents. Pour vérifier un séquestre, il faut importer beaucoup de schémas. Voici, la liste, sous forme d'une commande shell :
for schema in contact-1.0.xsd host-1.0.xsd rdeDomain-1.0.xsd rdeIDN-1.0.xsd rgp-1.0.xsd domain-1.0.xsd rde-1.0.xsd rdeEppParams-1.0.xsd rdeNNDN-1.0.xsd secDNS-1.1.xsd epp-1.0.xsd rdeContact-1.0.xsd rdeHeader-1.0.xsd rdePolicy-1.0.xsd eppcom-1.0.xsd rdeDnrdCommon-1.0.xsd rdeHost-1.0.xsd rdeRegistrar-1.0.xsd); do wget https://www.iana.org/assignments/xml-registry/schema/${schema} done
Ensuite, on importe (fichier escrow-wrapper.xsd
) et
on utilise xmllint sur l'exemple de séquestre de la section 14 du
RFC (fichier escrow-example.xml
) :
% xmllint --noout --schema wrapper.xsd escrow-example.xml escrow-example.xml validates
Ouf, tout va bien, le registre nous a envoyé un séquestre correct.
Enfin, la syntaxe formelle de ce format figure dans la section 9 du RFC, dans le langage XML Schema.
Ce format est mis en œuvre par tous les TLD qui sont liés par un contrat avec l'ICANN. 1 200 TLD envoient ainsi un séquestre une fois par semaine à l'ICANN.
Le concept de séquestre pose de sérieux problèmes de sécurité car le ou les fichiers transmis sont typiquement à la fois confidentiels, et cruciaux pour assurer la continuité de service. Lors du transfert du fichier, le registre et l'opérateur de séquestre doivent donc vérifier tous les deux l'authenticité du partenaire, et la confidentialité de la transmission. D'autant plus qu'une bonne partie du fichier est composée de données personnelles.
Date de publication du RFC : Avril 2021
Auteur(s) du RFC : B. Moran, H. Tschofenig (Arm Limited), D. Brown (Linaro), M. Meriac (Consultant)
Pour information
Réalisé dans le cadre du groupe de travail IETF suit
Première rédaction de cet article le 1 mai 2021
On le sait, le « S » dans « IoT » veut dire « sécurité ». Cette plaisanterie traditionnelle rappelle une vérité importante sur les brosses à dents connectées, les télés connectées, les voitures connectées et les sextoys connectés : leur sécurité est catastrophique. Les vendeurs de ces objets ont vingt ou trente ans de retard en matière de sécurité et c'est tous les jours qu'un nouveau piratage spectaculaire d'un objet connecté est annoncé. Une partie des vulnérabilités pourrait être bouchée en mettant à jour plus fréquemment les objets connectés. Mais cela ne va pas sans mal. Ce RFC fait partie d'un projet qui vise à construire certaines briques qui seront utiles pour permettre cette mise à jour fréquente. Il définit l'architecture générale.
Une bonne analyse du problème est décrite dans le RFC 8240, qui faisait le compte-rendu d'un atelier de réflexion sur la question. L'atelier avait bien montré l'ampleur et la complexité du problème ! Notre nouveau RFC est issu du groupe de travail SUIT de l'IETF, créé suite à cet atelier. La mise à jour du logiciel des objets connectés compte beaucoup d'aspects, et l'IETF se focalise sur un format de manifeste (en CBOR) qui permettra de décrire les mises à jour. (Le modèle d'information a été publié dans le RFC 9124, le format effectif sera dans un futur RFC.)
Le problème est compliqué : idéalement, on voudrait que les mises à jour logicielles de ces objets connectés se passent automatiquement, et sans casse. Ces objets sont nombreux, et beaucoup d'objets connectés sont installés dans des endroits peu accessibles ou, en tout cas, ne sont pas forcément bien suivis. Et ils n'ont souvent pas d'interface utilisateur du tout. Une solution réaliste doit donc être automatique (on sait que les injonctions aux humains « mettez à jour vos brosses à dents connectées » ne seront pas suivies). D'un autre côté, cette exigence d'automaticité va mal avec celle de consentement de l'utilisateur, d'autant plus que des mises à jour peuvent éliminer certaines fonctions de l'objet, ou en ajouter des peu souhaitables (cf. RFC 8240). En moins grave, elles peuvent aussi annuler des réglages ou modifications faits par les utilisateurs.
Le format de manifeste sur lequel travaille le groupe SUIT vise à authentifier l'image (le code), par exemple en la signant. Un autre point du cahier des charges, mais qui n'est qu'optionnel [et que je trouve peu réaliste dans la plupart des environnements] est d'assurer la confidentialité de l'image, afin d'empêcher la rétro-ingénierie du code. Ce format de manifeste vise avant tout les objets de classe 1 (selon la terminologie du RFC 7228). Il est prévu pour du logiciel mais peut être utilisé pour décrire d'autres ressources, comme des clés cryptographiques.
On l'a dit, le travail du groupe SUIT se concentre sur le format du manifeste. Mais une solution complète de mise à jour nécessite évidemment bien plus que cela, le protocole de transfert de fichiers pour récupérer l'image, un protocole pour découvrir qu'il existe des mises à jour (cf. le status tracker en section 2.3), un pour trouver le serveur pertinent (par exemple avec DNS-SD, RFC 6763). Le protocole LwM2M (Lightweight M2M) peut par exemple être utilisé.
Et puis ce n'est pas tout de trouver la mise à jour, la récupérer, la vérifier cryptographiquement. Une solution complète doit aussi fournir :
Bon, maintenant, après cette liste au Père Noël, au travail. Un peu de vocabulaire pour commencer :
Le monde de ces objets connectés se traduit par une grande dispersion des parties prenantes. On y trouve par exemple :
[Résultat, on a des chaînes d'approvisionnement longues, compliquées, opaques et vulnérables. Une grande partie des problèmes de sécurité des objets connectés vient de là, et pas de la technique. Par exemple, il est fréquent que les auteurs des logiciels utilisés se moquent de la sécurité et ne fournissent pas de mise à jour pour combler les failles dans les programmes.]
La section 3 du RFC présente l'architecture générale de la solution. Elle doit évidemment reposer sur IP (ce RFC étant écrit par l'IETF, un choix contraire aurait été étonnant), et, comme les images sont souvent de taille significative (des dizaines, des centaines de kilo-octets, au minimum), il faut utiliser en prime un protocole qui assurera la fiabilité du transfert et qui ne déclenchera pas de congestion, par exemple TCP, soit directement, soit par l'intermédiaire d'un protocole applicatif comme MQTT ou CoAP. En dessous d'IP, on pourra utiliser tout ce qu'on veut, USB, BLE, etc. Outre IP et TCP (pas évident pour un objet contraint, cf. RFC 9006), l'objet devra avoir le moyen de consulter le status tracker pour savoir s'il y a du nouveau, la capacité de stocker la nouvelle image avant de l'installer (si la mise à jour échoue, il faut pouvoir revenir en arrière, donc stocker l'ancienne image et la nouvelle), la capacité de déballer, voire de déchiffrer l'image et bien sûr, puisque c'est le cœur du projet SUIT, la capacité de lire et de comprendre le manifeste. Comme le manifeste comprend de la cryptographie (typiquement une signature), il faudra que l'objet ait du logiciel pour des opérations cryptographiques typiques, et un magasin de clés cryptographiques. Notez que la sécurité étant assurée via le manifeste, et non pas via le mécanisme de transport (sécurité des données et pas du canal), le moyen de récupération de l'image utilisé n'a pas de conséquences pour la sécurité. Ce sera notamment pratique pour le multicast.
Le manifeste peut être inclus dans l'image ou bien séparé. Dans le second cas, l'objet peut décider de charger l'image ou pas, en fonction du manifeste.
La mise à jour du code d'un objet contraint pose des défis particuliers. Ainsi, ces objets ne peuvent souvent pas utiliser de code indépendant de sa position. La nouvelle image ne peut donc pas être mise n'importe où. Cela veut dire que, lorsque le code est exécuté directement à partir du moyen de stockage (et donc pas chargé en mémoire), il faut échanger l'ancienne et la nouvelle image, ce qui complique un éventuel retour en arrière, en cas de problème.
Avec tout ça, je n'ai pas encore tellement parlé du manifeste lui-même. C'est parce que notre RFC ne décrit que l'architecture, donc les généralités. Le format du manifeste sera dans deux RFC, un pour décrire le format exact (fondé sur CBOR, cf. RFC 8949) et un autre, le RFC 9124 pour le modèle d'information (en gros, la liste des éléments qui peuvent être mis dans le manifeste).
La section 7 de notre RFC fait la synthèse des questions de sécurité liées à la mise à jour. Si une mise à jour fréquente du logiciel est cruciale pour la sécurité, en permettant de fermer rapidement des failles découvertes dans le logiciel de l'objet, la mise à jour peut elle-même présenter des risques. Après tout, mettre à jour son logiciel, c'est exécuter du code récupéré via l'Internet… L'architecture présentée dans ce RFC fournit une sécurité de bout en bout, par exemple en permettant de vérifier l'authenticité de l'image récupérée (et, bien sûr, son intégrité). Pas question d'exécuter un code non authentifié, ce qui est pourtant couramment le cas aujourd'hui avec les mises à jour de beaucoup d'équipements informatiques.
Cette même section en profite pour noter que la cryptographie nécessite de pouvoir changer les algorithmes utilisés. Le RFC cite même l'importance de la cryptographie post-quantique. (C'est un des tous premiers RFC publiés à en parler, après le RFC 8773.) D'un côté, ce genre de préoccupations est assez décalée : la réalité de l'insécurité des objets connectés est tellement abyssale qu'on aura bien des problèmes avant que les calculateurs quantiques ne deviennent une menace réelle. De l'autre, certains objets connectés peuvent rester en service pendant longtemps. Qui sait quelles seront les menaces dans dix ans ? (Un article comme « Quantum Annealing for Prime Factorization » estimait que, dans le pire des cas, les algorithmes pré-quantiques commenceraient à être cassés vers 2030, mais il est très difficile d'estimer la fiabilité de ces prédictions, cf. mon exposé à Pas Sage En Seine.)
J'insiste, mais je vous recommande de lire le RFC 8240 pour bien comprendre tous les aspects du problème. D'autre part, vous serez peut-être intéressés par l'évaluation faite du projet SUIT sous l'angle des droits humains. Cette évaluation suggérait (ce qui n'a pas été retenu) que le chiffrement des images soit obligatoire.
Date de publication du RFC : Avril 2021
Auteur(s) du RFC : O. Sury (Internet Systems Consortium), W. Toorop (NLnet Labs), D. Eastlake 3rd (Futurewei Technologies), M. Andrews (Internet Systems Consortium)
Chemin des normes
Réalisé dans le cadre du groupe de travail IETF dnsop
Première rédaction de cet article le 6 avril 2021
Le RFC 7873 normalisait un mécanisme, les cookies, pour qu'un serveur DNS authentifie raisonnablement l'adresse IP du client (ce qui, normalement, n'est pas le cas en UDP). Comme seul le serveur avait besoin de reconnaitre ses cookies, le RFC 7873 n'imposait pas un algorithme particulier pour les générer. Cela posait problème dans certains cas et notre nouveau RFC propose donc un mécanisme standard pour fabriquer ces cookies.
L'un des scénarios d'utilisation de ces « cookies standards » est le cas d'un serveur anycast dont on voudrait que toutes les instances génèrent des cookies standardisés, pour qu'une instance puisse reconnaitre les cookies d'une autre. Le RFC 7873, section 6, disait bien que toutes les instances avaient intérêt à partir du même secret mais ce n'était pas plus détaillé que cela. La situation actuelle, où chaque serveur peut faire différemment, est décrite dans le RFC par un mot en anglais que je ne connaissais pas, gallimaufry. D'où l'idée de spécifier cet algorithme, pour simplifier la vie des programmeuses et programmeurs avec un algorithme de génération de cookie bien étudié et documenté ; plus besoin d'en inventer un. Et cela permet de créer un service anycast avec des logiciels d'auteurs différents. S'ils utilisent cet algorithme, ils seront compatibles. Il ne s'agit que d'une possibilité : les serveurs peuvent utiliser cet algorithme mais ne sont pas obligés, le RFC 7873 reste d'actualité.
Comment, donc, construire un cookie ?
Commençons par celui du client (section 3). Rappel : le but est
d'authentifier un minimum donc, par exemple, le client doit choisir
un cookie différent par serveur (sinon, un
serveur pourrait se faire passer pour un autre, d'autant plus que le
cookie circule en clair), et, donc, il faut
utiliser quelque chose de spécifique au serveur dans l'algorithme,
par exemple son adresse IP. Par contre, pas besoin de le changer
souvent, il peut parfaitement durer des mois, sauf évidemment si un
élément entrant dans sa construction change, ou est
compromis. Autrefois, il était suggéré d'utiliser l'adresse IP du
client dans la construction du cookie client,
mais cette suggestion a été retirée car le logiciel ne connait
parfois son adresse IP que trop tard (ça dépend de l'API réseau utilisée
et, par exemple, avec l'API sockets de si on
a déjà fait un bind()
et, de toute façon, si on
est derrière un routeur NAT, connaitre
l'adresse IP locale ne sert pas à grand'chose). Néanmoins, pour
éviter qu'un cookie ne permette à un observateur
de relier deux requêtes d'une même machine, le
cookie doit changer quand l'adresse IP change,
comme rappelé par la section 8.1. (C'est particulièrement important
si on utilise des techniques de protection de la vie privée comme
celle du RFC 8981.)
Voilà, c'est tout parce que ce qui est important dans notre RFC, c'est le cookie serveur (section 4), puisque c'est là qu'on voudrait un cookie identique pour toutes les instances du service. Les éléments utilisés pour le générer sont le cookie du client, l'adresse IP du serveur, quelques métadonnées et un secret (qu'il faudra donc partager au sein du service anycast). Le secret changera, par exemple une fois par mois. Une fois qu'on a tous ces éléments, on va ensuite condenser le tout, ici avec SipHash (cf. J. Aumasson, et D. J. Bernstein, « SipHash: A Fast Short- Input PRF »). (Parmi les critères de choix d'une fonction de condensation, il y a les performances, un serveur DNS actif pouvant avoir à faire ce calcul souvent, pour vérifier les cookies.) Le cookie comprendra un numéro de version, un champ réservé, une estampille temporelle et le condensat. L'algorithme de génération du condensat inclus dans le cookie est donc : Condensat = SipHash-2-4 ( Cookie client | Version | Réservé | Estampille | Client-IP, Secret ), avec :
On a vu qu'il fallait changer le secret connu du serveur de temps en temps (voire plus rapidement s'il est compromis). Pour que ça ne casse pas tout (un serveur ne reconnaissant pas les cookies qu'il avait lui-même émis avec l'ancien secret…), il faut une période de recouvrement où le serveur connait déjà le nouveau secret (et accepte les cookies ainsi générés, par exemple par les autres instances du service anycast), puis une période où le serveur génère les cookies avec le nouveau secret, mais continue à accepter les cookies anciens (par exemple gardés par des clients). La section 5 rappelle, comme on le fait pour les remplacements de clés DNSSEC, de tenir compte des TTL pour calculer les délais nécessaires.
Si vous voulez mettre en œuvre vous-même cet algorithme, notez que l'annexe A du RFC contient des vecteurs de test, permettant de vérifier si vous ne vous êtes pas trompé.
La section 2 de notre RFC décrit les changements depuis le RFC 7873. Les algorithmes données comme simples suggestions dans les annexes A.1 et B.1 sont trop faibles et ne doivent pas être utilisés. Celui de l'annexe B.2 ne pose pas de problèmes de sécurité mais celui de notre nouveau RFC est préféré.
Plusieurs mises en œuvre de ce nouvel algorithme ont été faites, et leur interopérabilité testée (notamment au cours du hackathon de la réunion IETF 104 à Prague). Au moins BIND, Knot et getdns ont déjà cet algorithme dans leurs versions publiées.
Date de publication du RFC : Avril 2021
Auteur(s) du RFC : O. Gimenez (Semtech), I. Petrov (Acklio)
Chemin des normes
Réalisé dans le cadre du groupe de travail IETF lpwan
Première rédaction de cet article le 23 avril 2021
Le RFC 8724 décrivait un mécanisme général de compression pour les réseaux LPWAN (réseaux contraints pour objets contraints). Ce nouveau RFC 9011 précise ce mécanisme pour le cas spécifique de LoRaWAN.
Ces « réseaux contraints pour objets contraints » sont décrits dans le RFC 8376. Ils ont des concepts communs mais aussi des différences, ce qui justifie la séparation de SCHC en un cadre générique (celui du RFC 8724) et des spécifications précises par réseau, comme ce que fait notre RFC pour LoRaWAN, la technique qui est utilisée dans divers réseaux déployés. Donc, rappelez-vous, LoRaWAN = la technologie, LoRa = un réseau déployé utilisant cette technologie (mais d'autres réseaux concurrents peuvent utiliser LoRaWAN). LoRaWAN est normalisé par l'alliance LoRa (cf. le texte de la norme) et les auteurs du RFC sont actifs dans cette alliance. Si vous voulez un exemple d'utilisation de LoRaWAN, je recommande cet article en français sur une Gateway LoRaWAN réalisée sur un Raspberry Pi.
La section 3 du RFC fait un rappel de SCHC : si vous n'avez pas le courage de lire le RFC 8724, apprenez que SCHC a deux parties, une de compression des en-têtes, et une de fragmentation, les liens des réseaux contraints ayant souvent une faible MTU. La section 4, elle, explique LoRaWAN (vous avez aussi le RFC 8376, notamment sa section 2.1). La terminologie de SCHC et celle de LoRaWAN ne coïncident pas parfaitement donc il faut se souvenir que Gateway dans LoRaWAN s'appelle plutôt RGW (Radio GateWay) dans SCHC, que le Network Server de LoRaWAN est le NGW (Network GateWay) de SCHC et que les utilisateurs de LoRaWAN doivent se souvenir que leur Application Server est nommé C/D (Compression/Décompression) ou F/R (Fragmentation/Réassemblage) chez SCHC. Les objets connectés par LoRaWAN sont très souvent contraints et LoRaWAN définit trois classes d'objets, de la classe A, la plus contrainte, à la C. Notamment, les objets de la classe A émettent sur le réseau mais n'ont pas de moment d'écoute dédié, ceux de la classe B écoutent parfois, et ceux de la classe C écoutent en permanence, ce qui consomme pas mal d'énergie. Autant dire que les objets de classe C sont en général alimentés en électricité en permanence.
La section 5 est le cœur du RFC, expliquant en détail comment on met en correspondance les concepts abstraits de SCHC avec les détails du protocole LoRaWAN. Ainsi, le RuleID de SCHC est mis sur huit bits, dans le port (Fport) LoRaWAN (norme LoRaWAN, version 1.04, section 4.3.2), juste avant la charge utile. L'annexe A du RFC donne des exemples d'encodage des paquets.
Merci à Laurent Toutain pour sa relecture.
Date de publication du RFC : Mars 2021
Auteur(s) du RFC : R. Ouazana (Linagora)
Chemin des normes
Réalisé dans le cadre du groupe de travail IETF jmap
Première rédaction de cet article le 23 mars 2021
Les avis de remise d'un message (MDN, Message Disposition Notification) sont normalisés dans le RFC 8098. Ce nouveau RFC normalise la façon de les gérer depuis le protocole d'accès au courrier JMAP (qui est, lui, dans les RFC 8620 et RFC 8621).
Un petit rappel sur JMAP : c'est un protocole générique pour accéder à des données distantes, qu'elles concernent la gestion d'un agenda ou celle du courrier. Il est normalisé dans le RFC 8620. Une extension spécifique, JMAP for Mail, décrit comment l'utiliser pour le cas du courrier (rôle pour lequel JMAP concurrence IMAP).
Quant à MDN (Message Disposition
Notifications), c'est un format qui décrit des accusés de
réception du courrier. Il a été spécifié dans le RFC 8098. Un MDN est un message structuré (en MIME, avec
une partie de type
message/disposition-notification
) qui indique
ce qui est arrivé au message. On peut donc envoyer un MDN en
réponse à un message (c'est un accusé de réception, ou avis de
remise), on peut demander l'envoi d'un tel MDN lorsqu'on envoie un
message, on peut recevoir des MDN et vouloir les analyser, par
exemple pour les mettre en rapport avec un message qu'on a
envoyé.
Avant de manier les MDN, il faut les modéliser (section 2). Comme tout en JMAP, un MDN est un objet JSON comprenant entre autres les membres :
forEmailId
: l'identifiant JMAP du
message auquel se réfère ce MDN,originalMessageId
, le Message
ID du message auquel se réfère ce MDN, à ne pas
confondre avec l'identifiant JMAP,includeOriginalMessage
, qui indique
qu'un envoyeur souhaite recevoir le message original dans les
éventuels MDN,disposition
: voir le RFC 8098, section 3.2.6, pour cette notion. Elle indique ce
qui est arrivé au message, et les conditions dans lesquelles
l'action d'émission du MDN a été prise, par exemple
automatiquement, ou bien par une action explicite de
l'utilisateur.L'objet JSON MDN sert à modéliser les MDN reçus mais aussi les options envoyées, comme la demande de génération de MDN par le receveur.
Pour envoyer un MDN, notre RFC introduit une nouvelle
méthode JMAP, MDN/send
. Pour analyser un MDN
entrant, la méthode se nomme MDN/parse
. Voici
un exemple de MDN JMAP :
[[ "MDN/send", { ... "send": { ... "forEmailId": "Md45b47b4877521042cec0938", "subject": "Read receipt for: World domination", "textBody": "This receipt shows that the email has been displayed on your recipient's computer. There is no guaranty it has been read or understood.", "reportingUA": "joes-pc.cs.example.com; Foomail 97.1", "disposition": { "actionMode": "manual-action", "sendingMode": "mdn-sent-manually", "type": "displayed" }, ...
On y voit l'identifiant du message concerné, et l'action qui a été entreprise : le destinataire a activé manuellement l'envoi du message, après avoir vu ledit message.
Et pour demander l'envoi d'un MDN lorsqu'on crée un message ?
C'est la méthode Email/set
du RFC 8621 qui permet de créer le message. On doit juste ajouter
l'en-tête Disposition-Notification-To:
du RFC 8098 :
[[ "Email/set", { "accountId": "ue150411c", "create": { ... "header:Disposition-Notification-To:asText": "joe@example.com", "subject": "World domination", ...
Comme un serveur JMAP annonce ses capacités lors de la connexion
(RFC 8620, section 2), notre RFC ajoute une
nouvelle capacité, urn:ietf:params:jmap:mdn
, ce
qui permet à un serveur de dire qu'il sait gérer les MDN. Elle est
enregistrée
à l'IANA.
Question mise en œuvre de JMAP qui gère les MDN, il y a le serveur Cyrus, voir ce code, et aussi Apache James.
Merci à Raphaël Ouazana pour sa relecture.
Date de publication du RFC : Mars 2021
Auteur(s) du RFC : C. Gomez (UPC), J. Crowcroft (University of Cambridge), M. Scharf (Hochschule Esslingen)
Pour information
Réalisé dans le cadre du groupe de travail IETF lwig
Première rédaction de cet article le 28 mars 2021
À côté de machines disposant de ressources matérielles suffisantes (électricité, processeur, etc), qui peuvent faire tourner des protocoles comme TCP sans ajustements particuliers, il existe des machines dites contraintes, et des réseaux de machines contraintes, notamment dans le cadre de l'Internet des Objets. Ces machines, pauvres en énergie ou en capacités de calcul, doivent, si elles veulent participer à des communications sur l'Internet, adapter leur usage de TCP. Ce RFC documente les façons de faire du TCP « léger ».
Ces CNN (Constrained-Node Networks, réseaux contenant beaucoup d'objets contraints) sont décrits dans le RFC 7228. On parle bien d'objets contraints, soit en processeur, soit en énergie. Un Raspberry Pi ou une télévision connectée ne sont pas des objets contraints, ils peuvent utiliser les systèmes habituels, avec un TCP normal, au contraire des objets contraints, qui nécessitent des technologies adaptées. Le RFC 8352 explique ainsi les problèmes liés à la consommation électrique de certains protocoles. Des protocoles spéciaux ont été développés pour ces objets contraints, comme 6LoWPAN (RFC 4944, RFC 6282 et RFC 6775) ou comme le RPL du RFC 6550 ou, au niveau applicatif, le CoAP du RFC 7252.
Côté transport, on sait que les principaux protocoles de transport actuels sur l'Internet sont UDP et TCP. TCP a été parfois critiqué comme inadapté à l'Internet des Objets. Ces critiques n'étaient pas forcément justifiées mais il est sûr que le fait que TCP ait des en-têtes plutôt longs, pas de multicast et qu'il impose d'accuser réception de toutes les données peut ne pas être optimal pour ces réseaux d'objets contraints. D'autres reproches pouvaient être traités, comme expliqué dans l'article « TCP in the Internet of Things: from ostracism to prominence ». Notez que CoAP, à l'origine, tournait uniquement sur UDP mais que depuis il existe aussi sur TCP (RFC 8323). Les CNN (Constrained-Node Networks) utilisent parfois d'autres protocoles applicatifs tournant sur TCP comme HTTP/2 (RFC 9113) ou MQTT.
TCP est certes complexe si on veut utiliser toutes les optimisations qui ont été développées au fil du temps. Mais elles ne sont pas nécessaires pour l'interopérabilité. Un TCP minimum peut parfaitement communiquer avec des TCP optimisés, et notre RFC explique comment réduire l'empreinte de TCP, tout en restant évidemment parfaitement compatible avec les TCP existants. (Notez qu'il y avait déjà eu des travaux sur l'adaptation de TCP à certains environnements, voir par exemple le RFC 3481.)
Bon, maintenant, au travail. Quelles sont les propriétés des CNN (RFC 7228) qui posent problème avec TCP (section 2 du RFC) ? Ils manquent d'énergie et il ne peuvent donc pas émettre et recevoir en permanence (RFC 8352), ils manquent de processeur, ce qui limite la complexité des protocoles, et ils utilisent souvent des réseaux physiques qui ont beaucoup de pertes (voire qui corrompent souvent les paquets), pas vraiment les réseaux avec lesquels TCP est le plus à l'aise (RFC 3819).
La communication d'un objet contraint se fait parfois à l'intérieur du CNN, avec un autre objet contraint et parfois avec une machine « normale » sur l'Internet. Les types d'interaction peuvent aller de l'unidirectionnel (un capteur transmet une mesure qu'il a faite), à la requête/réponse en passant par des transferts de fichiers (mise à jour du logiciel de l'objet contraint, par exemple). Voyons maintenant comment TCP peut s'adapter (section 3 du RFC).
D'abord, la MTU. En IPv6, faire des paquets de plus de 1 280 octets, c'est prendre le risque de la fragmentation, qui n'est pas une bonne chose pour des objets contraints en mémoire (RFC 8900), qui n'ont en plus pas très envie de faire de la Path MTU discovery (RFC 8201). Donc, notre RFC conseille d'utiliser la MSS (Maximum Segment Size) de TCP pour limiter la taille des paquets. Attention, les CNN tournent parfois sur des réseaux physiques assez spéciaux, où la MTU est bien inférieure aux 1 280 octets dont IPv6 a besoin (RFC 8200, section 5). Par exemple, IEEE 802.15.4 a une MTU de 127 octets seulement. Dans ce cas, il faut prévoir une couche d'adaptation entre IPv6 et le réseau physique (ce que fait le RFC 4944 pour IEE 802.15.4, le RFC 7668 pour Bluetooth LE, le RFC 8105 pour DECT LE, etc). Heureusement, d'autres technologies de réseau physique utilisées dans le monde des CNN n'ont pas ces limites de MTU, c'est le cas par exemple de Master-Slave/Token-Passing (cf. RFC 8163), IEEE 802.11ah, etc.
Deuxième endroit où on peut optimiser, ECN (RFC 3168). ECN permet aux routeurs intermédiaires de marquer dans un paquet que la congestion est proche ; le destinataire peut alors prévenir l'émetteur de ralentir. Le RFC 8087 décrit les avantages de l'ECN. Permettant de détecter l'approche de la congestion avant qu'on ait perdu un seul paquet (et donc sans qu'on ait à dépenser des watts pour retransmettre), l'ECN est particulièrement intéressant pour les CNN. Le RFC 7567 donne des conseils pratiques pour son déploiement.
Un problème classique de TCP sur les liens radio est que TCP interprète une perte de paquet comme signal de congestion, le poussant à ralentir, alors que cette perte peut en fait être due à la corruption d'un paquet (suite à une perturbation radio-électrique, par exemple). Il serait donc intéressant de pouvoir signaler explicitement ce genre de perte (la question était déjà discutée dans le RFC 2757 mais aussi dans l'article « Explicit Transport Error Notification (ETEN) for Error-Prone Wireless and Satellite Networks »). Pour l'instant, il n'existe aucun mécanisme standard pour cela, ce qui est bien dommage.
Pour faire tourner TCP sur une machine contrainte, une technique parfois utilisée est de n'envoyer qu'un segment à la fois (et donc d'annoncer une fenêtre dont la taille est d'un MSS - Maximum Segment Size). Dans ce cas, pas besoin de mémoriser plus qu'un segment de données envoyé mais dont l'accusé de réception n'a pas encore été reçu. C'est très économique, mais ça se paie cher en performances puisqu'il faut attendre l'accusé de réception de chaque segment avant d'en envoyer un autre. La capacité effective du lien va chuter, d'autant plus que certaines optimisations de TCP comme le fast recovery dépendent d'une fenêtre plus grande qu'un seul segment. Au niveau applicatif, on voit la même technique avec CoAP, qui est par défaut purement requête/réponse.
Si on veut faire du TCP « un seul segment », le code peut être
simplifié, ce qui permet de gagner encore en octets, mais notre RFC
rappelle quand même que des options comme MSS (évidemment),
NoOp
et EndOfOptions
restent nécessaires. En revanche, on peut réduire le code en ne
gérant pas les autres options comme
WindowScaling
(RFC 7323),
Timestamps
(RFC 7323) ou
SACK (RFC 2018). TCP a le droit d'ignorer ces options, qui,
en « un seul segment » sont parfois inutiles
(WindowScaling
, SACK) et parfois moins
importantes (Timestamps
). En tout cas, si la
machine a assez de mémoire, il est sûr que transmettre plusieurs
segments avant d'avoir eu l'accusé de réception du premier, et
utiliser des algorithmes comme le fast recovery
améliore certainement les performances. Même chose pour les accusés
de réception sélectifs, les SACK du RFC 2018.
La détermination du RTO (Retransmission TimeOut) est un des points cruciaux de TCP (RFC 6298). S'il est trop long, on attendra longtemps la retransmission, quand un paquet est perdu, s'il est trop court, on ré-émettra parfois pour rien, gâchant des ressources alors que le paquet était juste en retard. Bref, une mise en œuvre de TCP pour les CNN doit soigner ses algorithmes de choix du RTO (cf. RFC 8961).
Continuons avec des conseils sur TCP dans les réseaux d'objets contraints. Notre RFC rappelle que les accusés de réception retardés, utiles pour accuser réception d'une plus grande quantité de données et ainsi diminuer le nombre de ces accusés, peuvent améliorer les performances… ou pas. Cela dépend du type de trafic. Si, par exemple, le trafic est surtout dans une direction, avec des messages courts (ce sera typiquement le cas avec CoAP), retarder les accusés de réception n'est sans doute pas une bonne idée.
Les paramètres par défaut de TCP sont parfois inadaptés aux CNN. Ainsi, le RFC 5681 recommande une taille de fenêtre initiale d'environ quatre kilo-octets. Le RFC 6298 fait des recommandations qui peuvent aboutir à des tailles de fenêtre initiale encore plus grandes. C'est très bien pour un PC connecté via la fibre mais pas pour la plupart des objets contraints, qui demandent des paramètres adaptés. Bref, il ne faut pas lire le RFC 6298 trop littéralement, car il faut en général une taille de fenêtre initiale plus petite.
Il n'y a pas que TCP lui-même, il y a aussi les applications qui l'utilisent. C'est l'objet de la section 4 du RFC. En général, si un objet contraint communique avec un non-contraint, c'est le premier qui initie la connexion (cela lui permet de dormir, et donc d'économiser l'énergie, s'il n'a rien à dire). L'objet contraint a tout intérêt à minimiser le nombre de connexions TCP, pour économiser la mémoire. Certes, cela crée des problèmes de head-of-line blocking (une opération un peu lente bloque les opérations ultérieures qui passent sur la même connexion TCP) mais cela vaut souvent la peine.
Et combien de temps garder la connexion TCP ouverte ? Tant qu'on a des choses à dire, c'est évident, on continue. Mais lorsqu'on n'a plus rien à dire, l'application doit-elle fermer les connexions, qui consomment de la mémoire, sachant que rouvrir la connexion prendra du temps et des ressources (la triple poignée de mains…). C'est un peu le problème de l'automobiliste arrêté qui se demande s'il doit couper son moteur. S'il faut redémarrer tout de suite, il consommera davantage de carburant. D'un autre côté, s'il laisse son moteur tourner, ce sera également un gaspillage. Le problème est soluble si l'application sait exactement quand elle aura à nouveau besoin d'émettre, ou si l'automobiliste sait exactement combien de temps durera l'arrêt mais, en pratique, on ne le sait pas toujours. (Ceci dit, pour l'automobile, le système d'arrêt-démarrage automatique dispense désormais le conducteur du choix.)
Une autre raison pour laquelle il faut être prudent avec les connexions TCP inactives est le NAT. Si un routeur NAT estime que la connexion est finie, il va retirer de ses tables la correspondance entre l'adresse IP interne et l'externe et, lorsqu'on voudra recommencer à transmettre des paquets, ils seront perdus. Le RFC 5382 donne des durées minimales avant ce retrait (deux heures…) mais elles ne sont pas forcément respectées par les routeurs NAT. Ainsi, l'étude « An Experimental Study of Home Gateway Characteristics » trouve que la moitié des boitiers testés ne respectent pas la recommandation du RFC 5382, avec des délais parfois aussi courts que quelques minutes ! Une des façons d'empêcher ces coupures est d'utiliser le mécanisme keep-alive de TCP (RFC 1122, section 4.2.3.6), qui envoie régulièrement des paquets dont le seul but est d'empêcher le routeur NAT d'oublier la connexion. Une autre est d'avoir des « battements de cœur » réguliers dans les applications, comme le permet CoAP (RFC 8323). Et, si on coupe rapidement les connexions TCP inutilisées, avant qu'une stupide middlebox ne le fasse, comment reprendre rapidement ensuite, si le trafic repart ? TCP Fast open (RFC 7413) est une solution possible.
Enfin, la sécurité pose des problèmes particuliers dans les CNN, où les ressources de certaines machines peuvent être insuffisantes pour certaines solutions de sécurité. Ainsi, pour TCP, la solution d'authentification AO (RFC 5925) augmente la taille des paquets et nécessite des calculs supplémentaires.
Il existe un certain nombre de mises en œuvre de TCP qui visent les objets contraints mentionnés dans ce RFC. Une machine 32 bits alimentée en courant en permanence, comme un vieux Raspberry Pi, n'est pas concernée, elle fait tourner le TCP habituel de Linux. On parle ici de TCP pour objets vraiment contraints. C'est par exemple (annexe A du RFC) le cas de :
Un tableau comparatif en annexe A.7 résume les principales propriétés de ces différentes mises en œuvre de TCP sur objets contraints.
Date de publication du RFC : Janvier 2021
Auteur(s) du RFC : J. Snijders (NTT), J. Heitz (Cisco), J. Scudder (Juniper), A. Azimov (Yandex)
Chemin des normes
Réalisé dans le cadre du groupe de travail IETF idr
Première rédaction de cet article le 9 janvier 2021
Ce nouveau RFC normalise un mécanisme pour transmettre un texte en langue naturelle à un pair BGP afin de l'informer sur les raisons pour lesquelles on coupe la session. Il remplace le RFC 8203, notamment en augmentant la longueur maximale du message, afin de faciliter les messages en Unicode.
Le protocole de routage BGP offre un mécanisme
de coupure propre de la session, le message
NOTIFICATION
avec le code
Cease
(RFC 4271,
section 6.7). En prime, le RFC 4486
ajoutait à ce code des sous-codes permettant de préciser les
raisons de la coupure. Une coupure volontaire et manuelle aura
typiquement les sous-codes 2 (Administrative
Shutdown), 3 (Peer De-configured) ou
4 (Administrative Reset). (Ces sous-codes sont
enregistrés
à l'IANA.) Et enfin le RFC 8203,
déjà cité, ajoutait à ces sous-codes du texte libre, où on pouvait
exprimer ce qu'on voulait, par exemple « [#56554] Coupure de toutes les
sessions BGP au DE-CIX pour mise à jour
logicielle, retour dans trente minutes ». Notre RFC ne modifie que
légèrement cette possibilité introduite par le RFC 8203,
en augmentant la taille maximale du texte envoyé.
Bien sûr, la raison de la coupure peut être connue par d'autres moyens. Cela a pu être, par exemple, pour une session au travers d'un point d'échange, un message envoyé sur la liste du point d'échange, annonçant la date (en UTC, j'espère !) et la raison de la coupure. De tels message se voient régulièrement sur les listes, ici au France-IX :
Date: Wed, 16 Dec 2020 11:54:21 +0000 From: Jean Bon <jbon@operator.example> To: <paris@members.franceix.net> Subject: [FranceIX members] [Paris] AS64530 [REF056255] Temporary shut FranceIX sessions Hi France-IX members, This mail is to inform you that we are going to shut down all our sessions on France-IX' Paris POP on 2021-01-05 08:00:00 UTC for 30 minutes, in order to upgrade the router. Please use the ticket number [REF056255] for every correspondance about this action.
Mais quand la coupure effective se produit, on a parfois oublié le message d'avertissement, ou bien on a du mal à le retrouver. D'où l'importance de pouvoir rappeler les informations importantes dans le message de coupure, qui, espérons-le, sera affiché quelque part chez le pair, ou bien journalisé par syslog.
La section 2 décrit le format exact de ce mécanisme. La chaîne
de caractères envoyée dans le message BGP
NOTIFICATION
doit être en
UTF-8. Sa taille maximale est de 255
octets (ce qui ne fait pas 255 caractères, attention). À part ces
exigences techniques, son contenu est laissé à l'appréciation de
l'envoyeur.
La section 3 de notre RFC ajoute quelques conseils opérationnels. Par exemple, si vous utilisez un système de tickets pour suivre vos tâches, mettez le numéro du ticket correspondant à l'intervention de maintenance dans le message. Vos pairs pourront ainsi plus facilement vous signaler à quoi ils font référence.
Attention à ne pas agir aveuglément sur la seule base d'un message envoyé par BGP, notamment parce que, si la session BGP n'était pas sécurisée par, par exemple, IPsec, le message a pu être modifié en route.
L'annexe B du RFC résume les principaux changements depuis le RFC 8203. Le plus important est que la longueur maximale du message passe de 128 à 255 octets, notamment pour ne pas défavoriser les messages qui ne sont pas en ASCII. Comme l'avait fait remarquer lors de la discussion un opérateur du MSK-IX, si la phrase « Planned work to add switch to stack. Completion time - 30 minutes » fait 65 octets, sa traduction en russe aurait fait 119 octets.
Date de publication du RFC : Mai 2021
Auteur(s) du RFC : J. Iyengar (Fastly), I. Swett (Google)
Chemin des normes
Réalisé dans le cadre du groupe de travail IETF quic
Première rédaction de cet article le 28 mai 2021
Pour tout protocole de transport, détecter les pertes de paquets, et être capable d'émettre et de réémettre des paquets sans provoquer de congestion sont deux tâches essentielles. Ce RFC explique comment le protocole QUIC assure cette tâche.
Pour l'instant, TCP reste le principal protocole de transport sur l'Internet. Mais QUIC pourrait le dépasser. QUIC est normalisé dans une série de RFC et notre RFC 9002 se charge d'une tâche délicate et cruciale : expliquer comment détecter les pertes de paquets, et comment ne pas contribuer à la congestion. Voyons d'abord la conception générale (section 3 du RFC). Les messages QUIC sont mis dans des trames, une ou plusieurs trames sont regroupées dans un paquet (qui n'est pas un paquet IP) et un ou plusieurs paquets sont dans un datagramme UDP qu'on envoie à son correspondant. Les paquets ont un numéro (RFC 9000, section 12.3). Ces numéros ne sont pas des numéros des octets dans les données envoyées, notamment, un numéro de paquet ne se répète jamais dans une connexion. Alors qu'on peut envoyer les mêmes données plusieurs fois, s'il y a perte et réémission ; en cas de retransmission, les données sont renvoyées dans un nouveau paquet, avec un nouveau numéro, contrairement à TCP. Cela permet de savoir facilement si c'est une retransmission. (TCP, lui, essaie de déduire l'ordre de distribution du numéro de séquence, et ce n'est pas trivial.)
La plupart des paquets QUIC feront l'objet d'un accusé de réception mais attention. Il y a des trames dont le type exige un accusé de réception et d'autres non. Si un paquet ne contient que des trames n'exigeant pas d'accusé de réception, ce paquet ne sera confirmé par le récepteur qu'indirectement, lors de la réception d'un paquet ultérieur contenant au moins une trame exigeant un accusé de réception.
QUIC n'est pas TCP, cela vaut la peine de le rappeler. La très intéressante section 4 du RFC enfonce le clou en énumérant les différences entre les algorithmes de TCP et ceux de QUIC, pour assurer les mêmes fonctions. Ainsi, dans TCP, tous les octets sont numérotés selon un seul espace de numérotation (les numéros de séquence) alors que QUIC a plusieurs espaces, les paquets servant à établir la connexion ne partagent pas leurs numéros avec ceux des données. QUIC fonctionne ainsi car les premiers sont moins protégés par la cryptographie.
Pour TCP, le numéro de séquence indique à la fois l'ordre
d'émission et l'ordre de l'octet dans le flux de données. Le
problème de cette approche est que, en cas de retransmission, le
numéro de séquence n'indique plus l'ordre d'émission, rendant
difficile de distinguer une émission initiale et une retransmission
(ce qui serait pourtant bien utile pour estimer le RTT). Au contraire, dans
QUIC, le numéro de paquet n'identifie que l'ordre d'émission. La
retransmission a donc forcément un numéro supérieur à l'émission
initiale. Pour déterminer la place des octets dans le flux de
données, afin de s'assurer que l'application reçoive les données
dans l'ordre, QUIC utilise le champ Offset des
trames de type STREAM
, celles qui transmettent
les données (RFC 9000, section
19.8). QUIC a ainsi moins d'ambiguités, par exemple quand il
faut mesurer le taux de pertes.
QUIC, comme TCP, doit estimer le temps optimum pour décider qu'un paquet est perdu (RTO, pour Retransmission TimeOut). QUIC est plus proche de l'algorithme du RFC 8985 que du TCP classique. La section 5 du RFC détaille l'estimation du RTT.
La section 6 porte sur le problème délicat de la détection des pertes de paquets. La plupart des paquets QUIC doivent faire l'objet d'un accusé de réception. S'il n'est pas arrivé avant un temps limite, le paquet est décrété perdu, et il faudra demander une réémission (RFC 9000, section 13.3). Plus précisement, le paquet est considéré comme perdu s'il avait été envoyé avant un paquet qui a fait l'objet d'un accusé de réception et s'il s'est écoulé N paquets depuis ou bien un temps suffisamment long. (TCP fait face à exactement le même défi, et la lecture des RFC 5681, RFC 5827, RFC 6675 et RFC 8985 est recommandée.) La valeur recommandée pour N est 3, pour être proche de TCP. Mais attention si le réseau fait que les paquets arrivent souvent dans le désordre, cela pourrait mener à des paquets considérés à tort comme perdus. Le problème existait déjà pour TCP mais il est pire avec QUIC puisque des équipements intermédiaires sur le réseau qui remettaient les paquets dans l'ordre ne peuvent plus fonctionner avec QUIC, qui chiffre le plus de choses possibles pour éviter ces interventions souvent maladroites. Et le délai avant lequel on déclare qu'un paquet est perdu ? Il doit tenir compte du RTT qu'on doit donc mesurer.
Une fois la ou les pertes détectées, on réémet les paquets. Simple, non ? Sauf qu'il faut éviter que cette réémission n'aggrave les problèmes et ne mène à la congestion (le réseau, trop chargé, perd des paquets, les émetteurs réémettent, chargeant le réseau, donc on perd davantage de paquets, donc les émetteurs réémettent encore plus…). L'algorithme actuellement spécifié pour QUIC (section 7 du RFC) est proche du NewReno de TCP (normalisé dans le RFC 6582). Mais le choix d'un algorithme de contrôle de l'émetteur est unilatéral, et une mise en œuvre de QUIC peut toujours en choisir un autre, comme Cubic (RFC 8312). Évidemment, pas question d'être le gros porc qui s'attribue toute la capacité réseau pour lui seul, et cet algorithme doit de toute façon respecter les principes du RFC 8085 (en résumé : ne soyez pas égoïste, et pensez aux autres, laissez-leur de la capacité).
Pour aider à cette lutte contre la congestion, QUIC, comme TCP, peut utiliser ECN (RFC 3168 et RFC 8311).
Comme TCP, QUIC doit démarrer une nouvelle session doucement (RFC 6928) et non partir bille en tête avec une fenêtre de grande taille.
La réaction aux pertes de paquets peut avoir des conséquences sur
la sécurité (section 8 du RFC). Par exemple, les « signaux »
utilisés par QUIC pour décider qu'il y a eu une perte (l'absence
d'un paquet, le RTT, ECN) ne sont pas protégés par la cryptographie,
contrairement aux données transportées et à certaines
métadonnées. Un attaquant actif peut fausser ces signaux et mener
QUIC à réduire son débit. Il n'y a pas vraiment de protection contre
cela. Autre risque de sécurité, alors que QUIC est normalement conçu
pour priver un observateur de beaucoup d'informations qui, avec TCP
étaient en clair, il n'atteint pas 100 % de succès dans ce
domaine. Par exemple les paquets ne contenant que des accusés de
réception (trames de type ACK
) peuvent être
identifiés par leur taille (ils sont tout petits), et l'observateur
peut alors en déduire des informations sur les performances du
chemin. Si on veut éviter cela, il faut utiliser le
remplissage des accusés de réception.
Vous aimez lire des programmes ? L'annexe A du RFC contient du pseudo-code mettant en œuvre les mécanismes de récupération décrits dans le RFC.
Date de publication du RFC : Mai 2021
Auteur(s) du RFC : M. Thomson (Mozilla), S. Turner (sn3rd)
Chemin des normes
Réalisé dans le cadre du groupe de travail IETF quic
Première rédaction de cet article le 28 mai 2021
Le protocole de transport QUIC est toujours sécurisé par la cryptographie. Il n'y a pas de communication en clair avec QUIC. Cette sécurisation se fait actuellement par TLS mais QUIC utilise TLS d'une manière un peu spéciale, documentée dans ce RFC.
Fini d'hésiter entre une version avec TLS ou une version sans. Avec QUIC, c'est forcément chiffré et, pour l'instant, avec TLS (dans le futur, QUIC pourra utiliser d'autres protocoles, mais ce n'est pas encore défini). QUIC impose en prime au minimum TLS 1.3 (RFC 8446), qui permet notamment de diminuer la latence, la communication pouvant s'établir dans certains cas dès le premier paquet. Petit rappel de TLS, pour commencer : TLS permet d'établir un canal sûr, fournissant authentification du serveur et confidentialité de la communication, au-dessus d'un media non-sûr (l'Internet). TLS est normalement constitué de deux couches, la couche des enregistrements (record layer) et celle de contenu (content layer), qui inclut notamment le mécanisme de salutation initial. On établit une session avec le protocole de salutation (handshake protocol), puis la couche des enregistrements chiffre les données (application data) avec les clés issues de cette salutation. Les clés ont pu être créées par un échange Diffie-Helman, ou bien être pré-partagées (PSK, pour Pre-Shared Key), ce dernier cas permettant de démarrer la session immédiatement (« 0-RTT »).
QUIC utilise TLS d'une façon un peu spéciale. Certains messages
TLS n'existent pas comme ChangeCipherSpec
ou
KeyUpdate
(QUIC ayant ses propres mécanismes
pour changer la cryptographie en route, cf. section 6), et, surtout,
la couche des enregistrements disparait, QUIC faisant le chiffrement
selon son format, mais avec les clés négociées par TLS.
La poignée de mains qui établit la session TLS peut donc se faire de deux façons :
GET
, qui est
idempotente. Et le 0-RTT
pose également problème avec la
PFS (la
confidentialité même en cas de compromission ultérieure des clés).La section 3 du RFC fait un tour d'horizon général du protocole
TLS tel qu'utilisé par QUIC. Comme vu plus haut, la couche des
enregistrements (record layer) telle qu'utilisée
avec TCP n'est plus présente, les messages TLS comme
Handshake
et Alert
sont
directement transportés sur QUIC (qui, contrairement à TCP, permet
d'assurer confidentialité et intégrité). Avec TCP, la mise en
couches était stricte, TLS
étant entièrement au-dessus de TCP, avec QUIC, l'intégration est
plus poussée, QUIC et TLS coopèrent, le premier chiffrant avec les
clés fournies par le second, et QUIC transportant les messages de
TLS. De même, quand on fait tourner un protocole applicatif sur QUIC, par exemple
HTTP/3 (RFC 9113), celui-ci
est directement placé sur QUIC, TLS s'effaçant complètement. Les
applications vont donc confier leurs données à QUIC, pas à TLS. En
simplifiant (beaucoup…), on pourrait dire que TLS ne sert qu'au
début de la connexion. Pour citer Radia
Perlman, « It is misleading to regard this as a
specification of running QUIC over TLS. It is related to TLS in the
same way that DTLS is related to TLS: it imports much of the syntax,
but there are many differences and its security must be evaluated
largely independently. My initial reaction to this spec was to
wonder why it did not simply run QUIC over DTLS . I believe the
answer is that careful integration improves the performance and is
necessary for some of the address agility/transition
design. ».
La section 4 explique plus en détail comment les messages TLS
sont échangés via QUIC. Les messages cryptographiques sont
transportés dans des trames de type CRYPTO
. Par
exemple, le ClientHello
TLS sera dans une trame
CRYPTO
elle-même située dans un paquet QUIC de
type Initial
. Les Alert
sont dans des trames CONNECTION_CLOSE
dont le
code d'erreur est l'alerte TLS. Ce sont les seuls messages que QUIC
passera à TLS, il fait tout le reste lui-même.
On a vu que le principal travail de TLS est de fournir du matériel cryptographique à QUIC. Plus précisément, TLS fournit, après sa négociation avec son pair :
AEAD_AES_128_GCM
(AES avec
GCM) ou
AEAD_CHACHA20_POLY1305
(ChaCha20, RFC 8439),QUIC se servira de tout cela pour chiffrer.
QUIC impose une version minimale de TLS : la 1.3, normalisée dans le RFC 8446. Les versions ultérieures sont acceptées mais elles n'existent pas encore.
Ah et, comme toujours avec TLS, le client doit authentifier le
serveur, typiquement via son certificat. Le
serveur ne doit pas utiliser les possibilités TLS de
ré-authentification ultérieure (message
CertificateRequest
) car le multiplexage utilisé
par QUIC empêcherait de corréler cette demande d'authentification
avec une requête précise du client.
Outre le « 0-RTT » de QUIC, qui permet au client d'envoyer des
données applicatives dès le premier paquet, QUIC+TLS fournit un
autre mécanisme pour gagner du temps à l'établissement de la
connexion, la reprise de session (session
resumption, RFC 8446, section
2.2). Si le client a enregistré les informations nécessaires depuis
une précédente session avec ce serveur, il peut attaquer directement
avec un NewSessionTicket
dans une trame
CRYPTO
et abréger ainsi l'établissement de
session TLS.
Les erreurs TLS, comme bad_certificate
,
unexpected_message
ou
unsupported_extension
, sont définies dans le
RFC 8446, section 6. Dans QUIC, elles sont
transportées dans des trames de type
CONNECTION_CLOSE
, et mises dans le champ
d'erreur (Error Code
, RFC 9000, section 19.19). Notez que ces trames
mènent forcément à la coupure de toute la session QUIC, et il n'y a
donc pas moyen de transporter un simple avertissement TLS.
Bien, maintenant qu'on a vu le rôle de TLS, comment QUIC
va-t-il utiliser les clés pour protéger les paquets ? La section 5
répond à cette question. QUIC va utiliser les clés fournies par TLS
(je simplifie, car QUIC effectue quelques dérivations avant) comme
clés de chiffrement intègre (RFC 5116). Il utilisera l'algorithme de chiffrement
symétrique indiqué par TLS. Tous les paquets ne sont pas
protégés (par exemple ceux de négociation de version, inutiles pour
l'instant puisque QUIC n'a qu'une version, ne bénéficient pas de
protection puisqu'il faudrait connaitre la version pour choisir les
clés de protection). Le cas des paquets Initial
est un peu plus subtil puisqu'ils sont chiffrés, mais avec une clé
dérivée du connection ID, qui circule en
clair. Donc, en pratique, seule leur
intégrité est protégée, par leur
confidentialité (cf. section 7 pour les
conséquences).
J'ai dit que QUIC n'utilisait pas directement les clés fournies par TLS. Il applique en effet une fonction de dérivation, définie dans la section 7.1 du RFC 8446, elle-même définie à partir des fonctions du RFC 5869.
Il existe plein de pièges et de détails à prendre en compte quand
on met en œuvre QUIC+TLS. Par exemple, en raison du réordonnancement
des datagrammes dans le réseau, et des pertes de datagrammes, un
paquet chiffré peut arriver avant le matériel cryptographique qui
permettrait de le déchiffrer, ou bien avant que les affirmations du
pair aient été validées. La section 5.7 du RFC explique comment
gérer ce cas (en gros, jeter les paquets qui sont « en avance », ou
bien les garder pour déchiffrement ultérieur mais ne surtout pas
tenter de les traiter). Autre piège, QUIC ignore les paquets dont la
vérification d'intégrité a échoué, alors que TLS ferme la
connexion. Cela a pour conséquences qu'avec QUIC un attaquant peut
essayer plusieurs fois. Il faut donc compter les échecs et couper la
connexion quand un nombre maximal a été atteint (section 6.6). Bien
sûr, pour éviter de faciliter une attaque par déni de service (où
l'attaquant enverrait plein de mauvais paquets dans l'espoir de
fermer la connexion), ces limites doivent être assez hautes (2^23
paquets pour AEAD_AES_128_GCM
), voir
« Limits on
Authenticated Encryption Use in TLS » ou
« Robust
Channels: Handling Unreliable Networks in the Record Layers of QUIC
and DTLS 1.3 », ainsi que l'annexe B du RFC.
Encore question détails subtils, la poignée de mains de TLS n'est
pas tout à fait la même quand elle est utilisée par QUIC (section
8). Ainsi, ALPN doit être utilisé et avec succès,
autrement on raccroche avec l'erreur
no_application_protocol
.
Le but de TLS est de fournir de la sécurité, notamment confidentialité et authentification, donc il est recommandé de bien lire la section 9, qui traite de la sécurité de l'ensemble du RFC. Ainsi, si on utilise les tickets de session de TLS (RFC 8446, section 4.6.1), comme ils sont transmis en clair, ils peuvent permettre à un observateur de relier deux sessions, même si les adresses IP sont différentes.
Le « 0-RTT » est formidable pour diminuer la latence, mais il
diminue aussi la sécurité : il n'y a pas de protection contre le
rejeu. Si QUIC lui-même n'est pas vulnérable
au rejeu, l'application qui travaille au-dessus de QUIC peut
l'être. Prenez un protocole applicatif qui aurait des services
comme, en HTTP, « envoyez-moi une pizza » (sans doute
avec la méthode POST
), on voit bien que le
rejeu serait problématique. Bref, les applications qui,
contrairement au protocole QUIC, ne sont pas
idempotentes, ont tout intérêt à désactiver
le 0-RTT.
QUIC tournant sur UDP, qui ne protège pas contre l'usurpation d'adresse IP, il
existe en théorie un risque d'attaque par réflexion, avec
amplification. Par exemple, la réponse à un
ClientHello
peut être bien plus grande que le
ClientHello
lui-même. Pour limiter les risques,
QUIC impose que le premier paquet du client ait une taille minimale
(pour réduire le facteur d'amplification), en utilisant le
remplissage, et que le serveur ne réponde pas
avec plus de trois fois la quantité de données envoyée par le
client, tant que l'adresse IP de celui-ci n'a pas été validée.
Plus sophistiquées sont les attaques par canal auxiliaire. Par exemple, si une mise en œuvre de QUIC jette trop vite les paquets invalides, un attaquant qui mesure les temps de réaction pourra en déduire des informations sur ce qui n'allait pas exactement dans son paquet. Il faut donc que toutes les opérations prennent un temps constant.
Et puis, bien sûr, comme tout protocole utilisant la cryptographie, QUIC+TLS a besoin d'un générateur de nombres aléatoires correct (cf. RFC 4086).
Question mise en œuvre, notez qu'on ne peut pas forcément utiliser une bibliothèque TLS quelconque. Il faut qu'elle permette de séparer signalisation et chiffrement et qu'elle permette d'utiliser QUIC comme transport. (Et il n'y a pas d'API standard pour cela.) C'est par exemple le cas de la bibliothèque picotls. Pour OpenSSL, il faut attendre (un patch existe) et cela bloque parfois l'intégration de certains logiciels.
Et question tests, à ma connaissance, on ne peut pas actuellement
utiliser openssl s_client
ou
gnutls-cli
avec un serveur QUIC. Même problème avec le fameux site de
test TLS
.https://ssllabs.com/
Pour terminer, voici l'analyse d'une communication QUIC+TLS,
analyse faite avec Wireshark. D'abord, le
premier paquet, de type QUIC Initial
, qui
contient le ClientHello
dans une trame de type
CRYPTO
:
QUIC IETF 1... .... = Header Form: Long Header (1) .1.. .... = Fixed Bit: True ..00 .... = Packet Type: Initial (0) .... 00.. = Reserved: 0 .... ..11 = Packet Number Length: 4 bytes (3) Version: 1 (0x00000001) Destination Connection ID Length: 8 Destination Connection ID: 345d144296b90cff ... Length: 1226 Packet Number: 0 TLSv1.3 Record Layer: Handshake Protocol: Client Hello Frame Type: CRYPTO (0x0000000000000006) Offset: 0 Length: 384 Crypto Data Handshake Protocol: Client Hello Handshake Type: Client Hello (1) Length: 380 ... Extension: quic_transport_parameters (len=85) Type: quic_transport_parameters (57) Length: 85 Parameter: initial_max_stream_data_bidi_local (len=4) 2097152 Type: initial_max_stream_data_bidi_local (0x05) Length: 4 Value: 80200000 initial_max_stream_data_bidi_local: 2097152 ...
Dans les extensions TLS, notez l'extension spécifique à QUIC,
quic_transport_parameters
. QUIC « abuse » de
TLS en s'en servant pour emballer ses propres paramètres. (La liste
de ces paramètres de transport figure dans un
registre IANA.)
La réponse à ce paquet Initial
contiendra le
ServerHello
TLS. La poignée de mains se
terminera avec les paquets QUIC de type
Handshake
. TLS ne servira plus par la suite,
QUIC chiffrera tout seul.
Date de publication du RFC : Mai 2021
Auteur(s) du RFC : J. Iyengar (Fastly), M. Thomson (Mozilla)
Chemin des normes
Réalisé dans le cadre du groupe de travail IETF quic
Première rédaction de cet article le 28 mai 2021
Le protocole de transport QUIC vient d'être normalisé, dans une série de quatre RFC. J'ai écrit un résumé synthétique de QUIC, cet article porte sur le principal RFC du groupe, celui qui normalise le cœur de QUIC, le mécanisme de transport des données. QUIC, comme TCP, fournit aux protocoles applicatifs un service de transport des données fiable, et en prime avec authentification et confidentialité.
L'un des buts principaux de QUIC est de réduire la latence. La capacité des réseaux informatiques augmente sans cesse, et va continuer de la faire, alors que la latence sera bien plus difficile à réduire. Or, elle est déterminante dans la perception de « vitesse » de l'utilisateur. Quels sont les problèmes que pose actuellement l'utilisation de TCP, le principal protocole de transport de l'Internet ?
Le cahier des charges de QUIC était à peu près :
Certains de ces objectifs auraient pu être atteints en modifiant TCP. Mais TCP étant typiquement mis en œuvre dans le noyau du système d'exploitation, tout changement de TCP met un temps trop long à se diffuser. En outre, si les changements à TCP sont importants, ils peuvent être bloqués par les boitiers intermédiaires, comme si c'était un nouveau protocole.
QUIC peut donc, plutôt qu'à TCP, être comparé à SCTP tournant sur DTLS (RFC 8261), et on peut donc se demander pourquoi ne pas avoir utilisé ce système. C'est parce que :
Pour une comparaison plus détaillée de SCTP et QUIC, voir
l'Internet-Draft draft-joseph-quic-comparison-quic-sctp
.
(SCTP peut aussi tourner sur UDP - RFC 6951
mais j'ai plutôt utilisé SCTP sur DTLS comme point de comparaison,
pour avoir le chiffrement.) Notez que ces
middleboxes intrusives sont particulièrement
répandues dans les réseaux pour mobiles, type
4G (où elles sont parfois appelée
TCP proxies), puisque c'est un monde où on viole
bien plus facilement la neutralité du
réseau.
Faire mieux que TCP n'est pas évident. Ce seul RFC fait 207 pages, et il y a d'autres RFC à lire. Déjà, commençons par un peu de terminologie :
PING
sert uniquement à déclencher l'accusé de
réception, alors que STREAM
contient des
données de l'application.Maintenant, plongeons dans le RFC. Rappelez-vous qu'il est long ! Commençons par les ruisseaux (section 2 du RFC). Ils ressemblent aux ruisseaux de HTTP/2 (RFC 9113), ce qui est logique, QUIC ayant été conçu surtout en pensant à HTTP. Chaque ruisseau est un flux ordonné d'octets. Dans une même connexion QUIC, il y a plusieurs ruisseaux. Les octets d'un ruisseau sont reçus dans l'ordre où ils ont été envoyés (plus exactement, QUIC doit fournir ce service mais peut aussi fournir un service dans le désordre), ce qui n'est pas forcément le cas pour les octets d'une même connexion. (Dans le cas de HTTP, cela sert à éviter qu'une ressource lente à se charger ne bloque tout le monde.) Comme avec TCP, les données sont un flot continu, même si elles sont réparties dans plusieurs trames. La création d'un ruisseau est très rapide, il suffit d'envoyer une trame avec l'identifiant d'un ruisseau et hop, il est créé. Les ruisseaux peuvent durer très longtemps, ou au contraire tenir dans une seule trame.
J'ai parlé de l'identifiant d'un ruisseau. Ce numéro, ce stream ID est pair pour les ruisseaux créés par le client, impair s'ils sont créés par le serveur. Cela permet à client et serveur de créer des ruisseaux sans se marcher sur les pieds. Un autre bit dans l'identifiant indique si le ruisseau est bidirectionnel ou unidirectionnel.
Une application typique va donc créer un ou plusieurs ruisseaux,
y envoyer des données, en recevoir, et fermer les ruisseaux,
gentiment (trame STREAM
avec le bit
FIN
) ou brutalement (trame
RESET_STREAM
). La machine à états complète des
ruisseaux figure dans la section 3 du RFC.
Comme avec TCP, il ne s'agit pas d'envoyer des données au débit
maximum, sans se soucier des conséquences. Il faut se contrôler, à
la fois dans l'intérêt du réseau (éviter la
congestion) et dans celui du récepteur, qui a
peut-être du mal à traiter tout ce qu'on lui envoie. D'où, par
exemple, la trame STOP_SENDING
qui dit à
l'émetteur de se calmer.
Plus fondamentalement, le système de contrôle de QUIC est décrit
dans la section 4. Il fonctionne aussi bien par ruisseau qu'au
niveau de toute la connexion. C'est le récepteur qui contrôle ce que
l'émetteur peut envoyer, en disant « j'accepte au total N
octets ». Il le fait à l'établissement de la connexion puis,
lorsqu'il peut à nouveau traiter des données, via les trames
MAX_DATA
(pour l'ensemble de la connexion) et
MAX_STREAM_DATA
(valables, elles, pour un seul
ruisseau). Normalement, les quantités d'octets que l'émetteur peut
envoyer sont toujours croissantes. Par exemple, à l'établissement de
la connexion, le récepteur annonce qu'il peut traiter 1 024
octets. Puis, une fois qu'il a des ressources disponibles, il
signalera qu'il peut traiter 2 048 octets. Si l'émetteur ne lui
avait transmis que 1 024, cette augmentation lui indiquera qu'il
peut reprendre l'émission. Si l'émetteur envoie plus de données
qu'autorisé, le récepteur ferme brutalement la connexion. À noter
que les trames de type CRYPTO
ne sont pas
concernées car elles peuvent être nécessaires pour changer les
paramètres cryptographiques (RFC 9001, section 4.1.3).
J'ai parlé des connexions QUIC mais pas encore dit comment elles
étaient établies. La section 5 le détaille. Comme QUIC tourne sur
UDP, un certain
nombre de gens n'ont pas compris le rôle d'UDP et croient que QUIC
est sans connexion. Mais c'est faux, QUIC impose l'établissement
d'une connexion, c'est-à-dire d'un état partagé entre l'initiateur
(celui qui sollicite une connexion) et le répondeur. La négociation
initiale permettra entre autres de se mettre d'accord sur les
paramètres cryptographiques, mais aussi sur le protocole applicatif utilisé (par exemple
HTTP). QUIC permet d'envoyer des données dès le premier paquet (ce
qu'on nomme le « 0-RTT ») mais rappelez-vous que, dans ce cas, vous
n'êtes plus protégé contre les attaques par
rejeu. Ce n'est pas grave pour un
GET
HTTP mais cela peut être gênant dans
d'autres cas.
Une fonction essentielle aux connexions QUIC est le
connection ID. Il s'agit d'un identificateur de
la connexion, qui lui permettra notamment de survivre aux
changements de connectivité (passage de 4G dehors en WiFi chez soi,
par exemple) ou aux fantaisies des routeurs NAT qui peuvent
subitement changer les ports utilisés. Quand
un paquet QUIC arrive sur une machine, c'est ce connection
ID qui sert à démultiplexer les paquets entrants, et à
trouver les paramètres cryptographiques à utiliser pour le
déchiffrer. Il n'y a pas qu'un connection ID
mais tout un jeu, car, comme il circule en clair, il pourrait être
utilisé pour suivre à la trace un utilisateur. Ainsi, quand une
machine change d'adresse IP, la bonne pratique est de se mettre à
utiliser un connection ID qui faisait partie du
jeu de départ, mais n'a pas encore été utilisé, afin d'éviter qu'un
surveillant ne fasse le rapprochement entre les deux adresses
IP. Notez que le jeu de connection ID négocié au
début peut ensuite être agrandi avec des trames
NEW_CONNECTION_ID
.
Dans quel cas un port peut-il changer ? QUIC utilise UDP, pour maximiser les chances de passer à travers les pare-feux et les routeurs NAT. Cela pose des problèmes si le boitier intermédiaire fait des choses bizarres. Par exemple, si un routeur NAT décide de mettre fin à une connexion TCP qui n'a rien envoyé depuis longtemps, il peut génerer un message RST (ReSeT) pour couper la connexion. Rien de tel en UDP, où le routeur NAT va donc simplement supprimer de sa table de correspondance (entre adresses publiques et privées) une entrée. Après cela, les paquets envoyés par la machine externe seront jetés sans notification, ceux envoyés par la machine interne créeront une nouvelle correspondance, avec un port source différent et peut-être même une adresse IP source différente. La machine externe risque donc de ne pas les reconnaitre comme des paquets appartenant à la même connexion QUIC. En raison des systèmes de traduction d'adresses, l'adresse IP source et le port source vus par le pair peuvent changer pendant une même « session ». Pour permettre de reconnaitre une session en cours, QUIC utilise donc le connection ID, un nombre de longueur variable généré au début de la connexion (un dans chaque direction) et présent dans les paquets. (Le connection ID source n'est pas présent dans tous les paquets.)
Vous avez vu qu'une connexion QUIC peut parfaitement changer
d'adresse IP en cours de route. Cela aura certainement des
conséquences pour tous les systèmes qui enregistrent les adresses
IP comme identificateur d'un dialogue, du
journal d'un serveur HTTP (qu'est-ce que
Apache va mettre dans son
access_log
?) aux surveillances de la
HADOPI.
Pour limiter les risques qu'une correspondance dans un routeur
faisant de la traduction d'adresses n'expire, QUIC dispose de
plusieurs moyens de
keepalive comme les trames
de type PING
.
Comment l'application qui utilise QUIC va-t-elle créer des connexions et les utiliser ? La norme QUIC, dans notre RFC, ne spécifie pas d'API. Elle expose juste les services que doit rendre QUIC aux applications, notamment :
PING
, par exemple
pour s'assurer que la connexion reste ouverte,Le RFC ne le spécifie pas, mais il faudra évidemment que QUIC permette à l'application d'envoyer et de recevoir des données.
Il n'y a actuellement qu'une seule version de QUIC, la 1, normalisée dans notre RFC 9000 (cf. section 15). Dans le futur, d'autres versions apparaitront peut-être, et la section 6 du RFC explique comment se fera la future négociation de version (ce qui sera un point délicat car il faudra éviter les attaques par repli). Notez que toute version future devra se conformer aux invariants du RFC 8999, une garantie de ce qu'on trouvera toujours dans QUIC.
Un point important de QUIC est qu'il n'y a pas de mode « en
clair ». QUIC est forcément protégé par la
cryptographie. L'établissement de la
connexion impose donc la négociation de paramètres cryptographiques
(section 7). Ces paramètres sont mis dans une trame
CRYPTO
qui fait partie du premier paquet
envoyé. QUIC version 1 utilise TLS (RFC 9001). Le serveur est toujours authentifié, le client peut
l'être. C'est aussi dans cette négociation cryptographique qu'est
choisie l'application, via ALPN (RFC 7301).
Dans le cas courant, quatre paquets sont échangés,
Initial
par chacun des participants, puis
Handshake
. Mais, si le 0-RTT est accepté, des
données peuvent être envoyées par l'initiateur dès le premier
paquet.
Puisqu'UDP,
comme IP, ne protège
pas contre l'usurpation
d'adresse IP, QUIC doit valider les adresses IP utilisées,
pour éviter, par exemple, les attaques par réflexion (section
8). Si un initiateur contacte un répondeur en disant « mon adresse
IP est 2001:db8:dada::1
», il ne faut pas le
croire sur parole, et lui envoyer plein de données sans
vérification. QUIC doit valider
l'adresse IP de son correspondant, et le revalider lorsqu'il
change d'adresse IP. À l'établissement de la connexion, c'est la
réception du paquet Handshake
, proprement
chiffré, qui montre que le correspondant a bien reçu notre paquet
Initial
et a donc bien l'adresse IP qu'il
prétend avoir. En cas de changement d'adresse IP, la validation
vient du fait que le correspondant utilise un des
connection ID qui avait été échangés précédemment
ou d'un test explicite de joignabilité avec les trames
PATH_CHALLENGE
et
PATH_RESPONSE
. Sur ces migrations, voir aussi
la section 9.
Pour les futures connexions, on utilisera un jeton qui avait été
transmis dans une trame NEW_TOKEN
et qu'on a
stocké localement. (C'est ce qui permet le 0-RTT.) Le RFC ne
spécifie pas le format de ce jeton, seule la machine qui l'a créé et
qui l'envoie à sa partenaire a besoin de le comprendre (comme pour un
cookie). Le RFC conseille également de n'accepter
les jetons qu'une fois (et donc de mémoriser leur usage) pour
limiter le risques de rejeu.
Tant que la validation n'a pas été faite, une machine
QUIC ne doit pas envoyer plus de trois fois la quantité de données
reçue (pour éviter les attaques avec
amplification). C'est pour cela que le paquet
Initial
est rempli de manière à atteindre une
taille (1 200 octets, exige le RFC) qui garantit que l'autre machine
pourra répondre, même si elle a beaucoup à dire.
Une fois qu'on est connectés, on peut s'échanger des données, qui
feront l'objet d'accusés de réception de la part du voisin (trames
de type ACK
). Contrairement au TCP classique,
les accusés de réception ne sont pas forcément contigus, comme dans
l'extension SACK du RFC 2018. Si l'accusé de
réception n'est pas reçu, l'émetteur réémet, comme avec TCP.
Bon, une fois qu'on a ouvert la connexion, et échangé des
données, quand on n'a plus rien à dire, que fait-on ? On
raccroche. La section 10 du RFC explique comment se terminent les
connexions QUIC. Cela peut se produire suite à une inactivité
prolongée, suite à une fermeture explicite normale, ou bien avec le
cas particulier de la fermeture sans état. Chacun des partenaires
peut évidemment estimer que, s'il ne s'est rien passé depuis
longtemps, il peut partir. (Cette durée maximale d'attente peut être
spécifiée dans les paramètres à l'établissement de la connexion.)
Mais on peut aussi raccrocher explicitement à tout moment (par
exemple parce que le partenaire n'a pas respecté le protocole QUIC),
en envoyant une trame de type
CONNECTION_CLOSE
. Cela fermera la connexion et,
bien sûr, tous ses ruisseaux.
Pour que la trame CONNECTION_CLOSE
soit
acceptée par l'autre machine, il faut que son émetteur connaisse les
paramètres cryptographiques qui permettront de la chiffrer
proprement. Mais il y a un cas ennuyeux, celui où une des deux
machines a redémarré, tout oublié, et reçoit des paquets d'une
ancienne connexion. Comment dire à l'autre machine d'arrêter d'en
envoyer ? Avec TCP, on envoie un paquet RST
(ReSeT) et c'est bon. Mais cette simplicité est
dangereuse car elle permet également à un tiers de faire des
attaques par déni de service en envoyant des
« faux » paquets RST. Des censeurs ou des FAI voulant
bloquer du trafic pair-à-pair ont déjà
pratiqué ce genre d'attaque. La solution QUIC à ce double problème
est la fermeture sans état (stateless
reset). Cela repose sur l'envoi préalable d'un jeton (cela
peut se faire via un paramètre lors de l'établissement de la
connexion, ou via une trame
NEW_CONNECTION_ID
). Pour pouvoir utiliser ces
jetons, il faudra donc les stocker, mais il ne sera pas nécessaire
d'avoir les paramètres cryptographiques : on ne chiffre pas le
paquet de fermeture sans état, il est juste authentifié (par le
jeton). Si la perte de mémoire est totale (jeton stocké en mémoire
non stable, et perdu), il ne reste plus que les délais de garde pour
mettre fin à la connexion. Évidemment, le jeton ne peut être utilisé
qu'une fois, puisqu'un surveillant a pu le copier. Notez que les
détails de ce paquet de fermeture sans état sont soigneusement
conçus pour que ce paquet soit indistinguable d'un paquet QUIC
« normal ».
Dans un monde idéal, tout fonctionnera comme écrit dans le
RFC. Mais, dans la réalité, des machines ne vont pas suivre le
protocole et vont faire des choses anormales. La section 11 du RFC
couvre la gestion d'erreurs dans QUIC. Le problème peut être dans la
couche de transport, ou dans l'application (et, dans ce cas, il peut
être limité à un seul ruisseau). Lorsque l'erreur est dans la couche
transport et qu'elle semble irrattrapable, on ferme la connexion
avec une trame CONNECTION_CLOSE
. Si le problème
ne touche qu'un seul ruisseau, on peut se contenter d'une trame
RESET_STREAM
, qui met fin juste à ce ruisseau.
On a parlé de paquets et de trames. La section 12 précise ces termes :
Parmi les paquets, il y a les paquets longs et les paquets
courts. Les paquets longs, qui contiennent tous les détails, sont
Initial
, Handshake
,
0-RTT
et Retry
. Ce sont
surtout ceux qui servent à établir la connexion. Les paquets courts
sont le 1-RTT
, qui ne peut être utilisé
qu'après l'établissement complet de la connexion, y compris les
paramètres cryptographiques. Bref, les paquets longs (plus
exactement, à en-tête long) sont plus complets, les paquets courts
(à en-tête court) plus efficaces.
Les paquets sont protégés par la cryptographie. (QUIC n'a pas de
mode en clair.) Mais attention, elle ne protège pas la totalité du
paquet. Ainsi, le connection ID est en clair
puisque c'est lui qui sera utilisé à la destination pour trouver la
bonne connexion et donc les bons paramètres cryptographiques pour
déchiffrer. (Mais il est protégé en intégrité
donc ne peut pas être modifié par un attaquant sans que ce soit
détecté.) De même, les paquets Initial
n'ont de
protection que contre la modification, pas contre l'espionnage. En
revanche, les paquets qui transportent les données
(0-RTT
et 1-RTT
) sont
complètement protégés. Les détails de ce qui est protégé et ce qui
ne l'est pas figurent dans le RFC 9001.
La coalescence de plusieurs paquets au sein d'un seul datagramme UDP vise à augmenter les performances en diminuant le nombre de datagrammes à traiter. (J'en profite pour rappeler que la métrique importante pour un chemin sur le réseau n'est pas toujours le nombre d'octets par seconde qui peuvent passer par ce chemin. Parfois, c'est le nombre de datagrammes par seconde qui compte.) Par contre, si un datagramme qui comprend plusieurs paquets, qui eux-mêmes contiennent des trames de données de ruisseaux différents, est perdu, cela va évidemment affecter tous ces ruisseaux.
Les paquets ont un numéro, calculé différemment dans chaque direction, et partant de zéro. Ce numéro est chiffré. Les réémissions d'un paquet perdu utilisent un autre numéro que celui du paquet original, ce qui permet, contrairement à TCP, de distinguer émission et réémission.
Avec QUIC, les datagrammes ne sont jamais fragmentés (en IPv4), on met le bit DF à 1 pour éviter cela. QUIC peut utiliser la PLPMTUD (RFC 8899) pour trouver la MTU du chemin.
Le format exact des paquets est spécifié en section 17. Un paquet
long (plus exactement, à en-tête long) se reconnait par son premier
bit mis à 1. Il comprend un type (la liste est dans le RFC, elle
n'est pas extensible, il n'y a pas de registre IANA), les
deux connection ID et les données, dont la
signification dépend du type. Les paquets de type
Initial
comportent entre autres dans ces
données un jeton, qui pourra servir, par exemple pour les futurs
connexions 0-RTT. Les paquets de type Handshake
contiennent des trames de type CRYPTO
, qui
indiquent les paramètres cryptographiques. Quant aux paquets courts,
leur premier bit est à 0, et ils contiennent moins d'information,
par exemple, seul le connection ID de destination
est présent, pas celui de la source.
Dans sa partie non chiffrée, le paquet a un bit qui a suscité bien des débats, le spin bit. Comme c'est un peu long à expliquer, ce bit a son propre article.
QUIC chiffre beaucoup plus de choses que TCP. Ainsi, pour les paquets à en-tête court, en dehors du connection ID et de quelques bits dont le spin bit, rien n'est exposé. Par exemple, les accusés de réception sont chiffrés et on ne peut donc pas les observer. Le but est bien de diminuer la vue offerte au réseau (RFC 8546), alors que TCP expose tout (les accusés de réception, les estampilles temporelles, etc). QUIC chiffre tellement qu'il n'existe aucun moyen fiable, en observant le trafic, de voir ce qui est du QUIC et ce qui n'en est pas. (Certaines personnes avaient réclamé, au nom de la nécessité de surveillance, que QUIC se signale explicitement .)
Les différents types de trames sont tous listés en section 19. Il y a notamment :
PADDING
qui permet de
remplir les paquets pour rendre plus
difficile la surveillance,PING
qui permet de garder une connexion
ouverte, ou de vérifier que la
machine distante répond (il n'y a pas de
PONG
, c'est l'accusé de réception de la trame
qui en tiendra lieu),ACK
, les accusés de réception, qui
indiquent les intervalles de numéros de paquets reçus,CRYPTO
, les paramètres cryptographiques
de la connexion,STREAM
, qui contiennent les données,
et créent les ruisseaux ; envoyer une trame
de type STREAM
suffit, s'il n'est pas déjà
créé, à créer le ruisseau correspondant (ils sont identifiés par
un numéro contenu dans cette trame),CONNECTION_CLOSE
, pour mettre fin à la
connexion.Les types de trame figurent dans un registre IANA. On notera que l'encodage des trames n'est pas auto-descriptif : on ne peut comprendre une trame que si on connait son type. C'est plus rapide, mais moins souple et cela veut dire que, si on introduit de nouveaux types de trame, il faudra utiliser des paramètres au moment de l'ouverture de la connexion pour être sûr que l'autre machine comprenne ce type.
Bon, les codes d'erreur, désormais (section 20 du RFC). La liste
complète est dans un
registre IANA, je ne vais pas la reprendre ici. Notons quand
même le code d'erreur NO_ERROR
qui signifie
qu'il n'y a pas eu de problème. Il est utilisé lorsqu'on ferme une
connexion sans que pour autant quelque chose de mal se soit
produit.
Si vous voulez une vision plus concrète de QUIC, vous pouvez regarder mon article d'analyse d'une connexion QUIC.
L'une des principales motivations de QUIC est la sécurité, et il est donc logique qu'il y ait une longue section 21 consacrée à l'analyse détaillée de la sécurité de QUIC. D'abord, quel est le modèle de menace ? C'est celui du RFC 3552. En deux mots : on ne fait pas confiance au réseau, tout intermédiaire entre deux machines qui communiquent peut être malveillant. Il y a trois sortes d'attaquants : les attaquants passifs (qui ne peuvent qu'écouter), les attaquants actifs situés sur le chemin (et qui peuvent donc écouter et écrire) et les attaquants actifs non situés sur le chemin, qui peuvent écrire mais en aveugle. Voyons maintenant les attaques possibles.
La poignée de mains initiale est protégée par TLS. La sécurité de QUIC dépend donc de celle de TLS.
Les attaques par réflexion, surtout dangereuses quand elles se combinent avec une amplification sont gênées, sinon complètement empêchées, par la validation des adresses IP. QUIC ne transmet pas plus de trois fois le volume de données à une adresse IP non validée. Ce point n'a pas forcément été compris par tous, et certains ont paniqué à la simple mention de l'utilisation d'UDP. C'est par exemple le cas de cet article, qui est surtout du FUD d'un vendeur.
Du fait du chiffrement, même un attaquant actif qui serait sur le chemin et pourrait donc observer les paquets, ne peut pas injecter de faux paquets ou, plus exactement, ne peut pas espérer qu'ils seront acceptés (puisque l'attaquant ne connait pas la clé de chiffrement). L'attaquant actif qui n'est pas sur le chemin, et doit donc opérer en aveugle, est évidemment encore plus impuissant. (Avec TCP, l'attaquant actif situé sur le chemin peut insérer des paquets qui seront acceptés. Des précautions décrites dans le RFC 5961 permettent à TCP de ne pas être trop vulnérable à l'attaquant aveugle.)
La possibilité de migration des connexions QUIC (changement de port et/ou d'adresse IP) apporte évidemment de nouveaux risques. La validation du chemin doit être refaite lors de ces changements, autrement, un méchant partenaire QUIC pourrait vous rediriger vers une machine innocente.
Bien sûr, les attaques par déni de service restent
possibles. Ainsi, un attaquant actif sur le chemin qui a la
possibilité de modifier les paquets peut tout simplement les
corrompre de façon à ce qu'ils soient rejetés. Mais il y a aussi des
attaques par déni de service plus subtiles. L'attaque dite
Slowloris vise ainsi à épuiser une autre
machine en ouvrant beaucoup de connexions qui ne seront pas
utilisées. Un serveur utilisant QUIC doit donc se méfier et, par
exemple, limiter le nombre de connexions par client. Le méchant
client peut aussi ouvrir, non pas un grand nombre de connexions mais
un grand nombre de ruisseaux. C'est d'autant plus facile que
d'ouvrir le ruisseau de numéro N (ce qui ne nécessite qu'une seule
trame de type STREAM
) ouvre tous les ruisseaux
jusqu'à N, dans la limite indiquée dans les paramètres de
transport.
On a vu que dans certains cas, une machine QUIC n'a plus les paramètres qui lui permettent de fermer proprement une connexion (par exemple parce qu'elle a redémarré) et doit donc utiliser la fermeture sans état (stateless reset). Dans certains cas, un attaquant peut mettre la main sur un jeton qu'il utilisera ensuite pour fermer une connexion.
Le chiffrement, comme toute technique de sécurité, a ses
limites. Ainsi, il n'empêche pas l'analyse de
trafic (reconnaitre le fichier récupéré à sa taille, par
exemple). D'où les trames de type PADDING
pour
gêner cette attaque.
QUIC a plusieurs registres à l'IANA. Si vous voulez ajouter des valeurs à ces registres, leur politique (cf. RFC 8126) est :
L'annexe A de notre RFC contient du pseudo-code pour quelques algorithmes utiles à la mise en œuvre de QUIC. Par exemple, QUIC, contrairement à la plupart des protocoles IETF, a plusieurs champs de taille variable. Les encoder et décoder efficacement nécessite des algorithmes astucieux, suggérés dans cette annexe.
Il existe d'ores et déjà de nombreuses mises en œuvre de QUIC, l'écriture de ce RFC ayant été faite en parallèle avec d'innombrables hackathons et tests d'interopérabilité. Je vous renvoie à la page du groupe de travail, qui indique également des serveurs QUIC publics. Elles sont dans des langages de programmation très différents, par exemple celle de Cloudflare, Quiche, est en Rust. À ma connaissance, toutes tournent en espace utilisateur mais ce n'est pas obligatoire, QUIC pourrait parfaitement être intégré dans le noyau du système d'exploitation. Une autre bonne source pour la liste des mises en œuvre de QUIC est le Wikipédia anglophone. Notez que le navigateur Web libre Firefox a désormais QUIC.
Quelques lectures pour aller plus loin :
draft-martini-hrpc-quichr
(dont je remercie d'ailleurs les auteurs, qui m'ont bien aidé à
comprendre QUIC).RFC des différentes séries : 0 1000 2000 3000 4000 5000 6000 7000 8000 9000