Je suis Charlie

Autres trucs

Accueil

Seulement les RFC

Seulement les fiches de lecture

Mon livre « Cyberstructure »

Ève

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.


RFC 8999: Version-Independent Properties of QUIC

Date de publication du RFC : Mai 2021
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 28 mai 2021


Ce RFC, qui fait partie de la première fournée sur QUIC, décrit les invariants du protocole QUIC, les choses qui ne changeront pas dans les futures versions de QUIC.

Comme tous les protocoles Internet, QUIC doit faire face à l'ossification, cette tendance à rendre les changements impossibles, en raison du nombre de composants du réseau qui refusent tout comportement qu'ils n'attendaient pas, même si c'était une évolution prévue ou en tout cas possible. Une des méthodes pour limiter cette ossification est la documentation d'invariants, des parties du protocole dont on promet qu'elles ne changeront pas dans les futures versions. Tout le reste peut bouger et les équipements comme les middleboxes peuvent donc s'appuyer sur ce RFC pour faire les choses proprement, en sachant ce qui durera longtemps, et ce qui est susceptible d'évoluer.

La section 15 du RFC 9000 explique le principe des versions de QUIC. QUIC est actuellement à la version 1. C'est la seule pour l'instant. Mais si une version 2 (puis 3, puis 4…) apparait un jour, il faudra négocier la version utilisée entre les deux parties (section 6 du RFC 9000). Ces nouvelles versions pourront améliorer le protocole, répondant à des phénomènes qui ne sont pas forcément prévisibles. Par exemple QUIC v1 utilise forcément TLS pour la sécurité (RFC 9001), mais les futures versions pourront peut-être utiliser un autre protocole cryptographique, plus sûr, ou plus rapide. L'expérience de nombreux protocoles IETF est qu'il est difficile de déployer une nouvelle version d'un protocole déjà en place, car il y a toujours des équipements dans le réseau qui faisaient des suppositions sur le protocole, qui ne sont plus vraies avec la nouvelle version (et, souvent même pas avec l'ancienne…) et qui pertuberont, voire couperont la communication. D'où cette idée de documenter les invariants, en indiquant donc clairement que tout ce qui n'est pas invariant… peut changer. Un exemple d'invariant est « A QUIC packet with a long header has the high bit of the first byte set to 1. All other bits in that byte are version specific. ». Actuellement (version 1), le deuxième bit vaut forcément 1 (RFC 9000, section 17.2), mais ce n'est pas un invariant : les autres versions feront peut-être différemmment.

Concevoir des invariants est tout un art. Trop d'invariants et QUIC ne peut plus évoluer. Pas assez d'invariants et ils ne servent plus à rien. Ainsi, il semble difficile de faire un répartiteur de charge qui marche avec toutes les futures versions de QUIC (pas assez d'invariants pour lui).

Et dans les paquets, qu'est-ce qui sera invariant ? (Petit rappel au passage, un datagramme UDP peut contenir plusieurs paquets QUIC.) Il y a deux sortes de paquets, les longs et les courts. (Plus rigoureusement, ceux à en-tête long et ceux à en-tête court.) On les distingue par le premier bit. Tous les autres bits du premier octet sont spécifiques d'une version particulière de QUIC, et ne sont donc pas invariants. Ce premier octet est suivi d'un numéro de version sur 32 bits (aujourd'hui, forcément 1, sauf en cas de négociation de version, non encore spécifiée), puis du connection ID de la destination (attention : longueur variable, dans les paquets longs, il est encodé sous forme longueur puis valeur, cela permettra d'avoir des identificateurs de connexion très longs dans le futur) puis, mais seulement pour les paquets longs, du connection ID de la source. Tout le reste du paquet dépend de la version de QUIC utilisée.

Notez que la longueur des paquets n'étant pas dans les invariants, on ne peut même pas trouver combien il y a de paquets QUIC dans un datagramme de manière indépendante de la version.

L'identificateur de connexion est une donnée opaque : la façon de le choisir pourra varier dans le futur.

Bien sûr, spécifier rigoureusement les invariants n'empêchera pas les middleboxes de tirer des fausses conclusions et, par exemple, de généraliser des comportements de la version 1 de QUIC à toutes les versions ultérieures (section 7 du RFC). L'annexe A donne une liste (sans doute incomplète) des suppositions qu'un observateur pourrait faire mais qui sont erronées. QUIC essaie de réduire ce qu'on peut observer, en chiffrant au maximum, afin de limiter ces suppositions erronées, mais il reste des choses visibles, qui ne sont pas forcément invariantes. Le RFC écrit ces suppositions de manière neutre, en notant qu'elles sont fausses. J'ai préféré rédiger en insistant sur leur fausseté. Donc, rappelez-vous que, notamment dans les futures versions de QUIC :

  • QUIC n'utilise pas forcément TLS,
  • les paquets longs peuvent apparaitre même après l'établissement de la connexion,
  • il n'y aura pas toujours de phase de connexion au début,
  • le dernier paquet avant une période de silence n'est pas forcément uniquement un accusé de réception,
  • les numéros de paquet ne croissent pas forcément de un à chaque paquet,
  • ce n'est pas toujours le client qui parle le premier,
  • les connection ID peuvent changer très vite,
  • le second bit du premier octet, parfois appelé à tort le « QUIC bit », n'est pas forcément à un, et il avait même été proposé de le faire varier (ce qu'on appelle le graissage, cf. RFC 8701),
  • et plusieurs autres points.

À noter que le numéro de version n'apparait que dans les paquets longs. Un programme qui observe des paquets QUIC de versions différentes ne peut donc analyser les paquets courts que s'il a mémorisé les numéros de versions correspondant à chaque connection ID de destination qu'il voit.

Autre point important : il n'y a pas d'invariant qui identifie un paquet QUIC. Pas de nombre magique ou de chose équivalente. Donc, aucun moyen d'être raisonnablement sûr que ce qui passe est du QUIC. C'est bien sûr volontaire, pour augmenter les chances que la neutralité soit respectée. Mais cela peut amener des middleboxes agressives à essayer de deviner si c'est bien du QUIC ou pas, en se trompant. (Le « QUIC bit » n'est évidemment pas une preuve suffisante, il peut s'agir d'un autre protocole où ce bit vaut 1.)


Téléchargez le RFC 8999


L'article seul

RFC 8998: ShangMi (SM) Cipher Suites for TLS 1.3

Date de publication du RFC : Mars 2021
Auteur(s) du RFC : P. Yang (Ant Group)
Pour information
Première rédaction de cet article le 11 mars 2021


La cryptographie est un outil essentiel pour la sécurité sur les réseaux numériques. N'avoir comme algorithmes de cryptographie que des algorithmes développés à l'étranger peut être jugé dangereux pour la sécurité nationale. Des pays comme la Russie et, bien sûr, les États-Unis, recommandent ou imposent des algorithmes « nationaux ». La Chine s'y met, avec les algorithmes de chiffrement ShangMi (« SM »), dont ce RFC décrit l'utilisation dans TLS.

Comme ces algorithmes sont obligatoires en Chine pour certaines applications (comme c'est le cas de l'algorithme russe Magma décrit dans le RFC 8891), il était nécessaire que TLS (RFC 8446) puisse les utiliser, indépendamment de l'opinion des cryptographes « occidentaux » à leur sujet. Ce RFC traite de deux algorithmes de chiffrement symétrique (« SM4 ») avec chiffrement intègre, une courbe elliptique (« curveSM2 «), un algorithme de condensation (« SM3 »), un algorithme de signature (« SM2 ») utilisant curveSM2 et SM3, et un d'échange de clés fondé sur ECDHE sur SM2. (Au passage, saviez-vous qu'il existe une courbe elliptique française officielle ?)

On notera que les Chinois n'ont pas poussé leurs algorithmes qu'à l'IETF, certains sont aussi normalisés à l'ISO (ISO/IEC 14888-3:2018, ISO/IEC 10118-3:2018 et ISO/IEC 18033-3:2010).

Le RFC ne décrit pas les algorithmes eux-mêmes, uniquement comment les utiliser dans le contexte de TLS 1.3 (RFC 8446). Si vous êtes curieux, les normes chinoises sont :

Les deux algorithmes de chiffrement symétrique sont désormais dans le registre IANA sous les noms de TLS_SM4_GCM_SM3 et TLS_SM4_CCM_SM3. L'algorithme de signature, sm2sig_sm3 est dans le registre approprié. La courbe elliptique curveSM2 a été ajoutée à un autre registre.

Je ne connais pas de mise en œuvre de ces algorithmes dans les bibliothèques TLS habituelles. Si vous avez des informations… Mais Wireshark va bientôt savoir les afficher.

Ah, et si vous vous intéressez à l'Internet en Chine, je vous recommande le livre de Simone Pieranni, Red Mirror.


Téléchargez le RFC 8998


L'article seul

RFC 8997: Deprecation of TLS 1.1 for Email Submission and Access

Date de publication du RFC : Mars 2021
Auteur(s) du RFC : L. Velvindron (cyberstorm.mu), S. Farrell (Trinity College Dublin)
Chemin des normes
Réalisé dans le cadre du groupe de travail IETF uta
Première rédaction de cet article le 24 mars 2021


Dans le cadre général de l'abandon des versions 1.0 et 1.1 du protocole TLS (cf. RFC 8996), ce RFC déclare que ces vieilles versions ne doivent plus être utilisées pour le courrier (mettant ainsi à jour le RFC 8314).

Ce RFC 8314 spécifiait l'usage de TLS pour la soumission et la récupération de courrier. Sa section 4.1 imposait une version minimale de TLS, la 1.1. Cette 1.1 étant officiellement abandonnée pour obsolescence dans le RFC 8996, la version minimale est maintenant la 1.2. C'est tout, ce RFC est simple et court.


Téléchargez le RFC 8997


L'article seul

RFC 8996: Deprecating TLSv1.0 and TLSv1.1

Date de publication du RFC : Mars 2021
Auteur(s) du RFC : K. Moriarty (Dell EMC), S. Farrell (Trinity College Dublin)
Réalisé dans le cadre du groupe de travail IETF tls
Première rédaction de cet article le 24 mars 2021
Dernière mise à jour le 25 mars 2021


Ce RFC est très court, car il s'agit juste de formaliser une évidence : les versions 1.0 et 1.1 du protocole de cryptographie TLS ne devraient plus être utilisées, elles souffrent de diverses failles, notamment de sécurité. Les seules versions de TLS à utiliser sont la 1.2 (recommandée depuis 2008 !) et la 1.3 (publiée en 2018). Ainsi, une bibliothèque TLS pourra retirer tout le code correspondant à ces versions abandonnées, ce qui diminuera les risques (moins de code, moins de bogues).

Que reproche-t-on exactement à ces vieux protocoles (section 1 du RFC) ?

  • Ils utilisent des algorithmes de cryptographie dépassés et dangereux, par exemple TLS 1.0 impose de gérer Triple DES, et SHA-1 est utilisé à plusieurs endroits. Un des points les plus problématiques à propos des vieilles versions de TLS est en effet leur dépendance vis-à-vis de cet algorithme de condensation SHA-1. Celui-ci est connu comme vulnérable.
  • Ils ne permettent pas d'utiliser les algorithmes modernes, notamment le chiffrement intègre.
  • Indépendamment des défauts de ces vieux protocoles, le seul fait d'avoir quatre versions à gérer augmente les risques d'erreur, pouvant mener à des attaques par repli.
  • Des développeurs de bibliothèques TLS ont manifesté leur souhait de retirer les vieilles versions de TLS, ce qui implique leur abandon officiel par l'IETF.
  • Pour davantage de détails sur les faiblesses reprochées aux vieilles versions de TLS, regardez le rapport SP800-52r2 du NIST ou bien le RFC 7457. S'il existe bien des contournements connus pour certaines de ces vulnérabilités, il est quand même plus simple et plus sûr d'abandonner ces anciennes versions 1.0 et 1.1.

Désormais, la programmeuse ou le programmeur qui veut faire mincir son code en retirant TLS 1.0 et 1.1 peut, si des utilisateurs contestent, s'appuyer sur cette décision de l'IETF. Désormais, la règle est simple : le client ne doit pas proposer TLS 1.0 et 1.1, et s'il le fait le serveur ne doit pas l'accepter. Cela concerne de nombreux RFC, qui mentionnaient 1.0 et 1.1, et tous n'ont pas encore été mis à jour. Ainsi, le RFC 7562 est toujours d'actualité, simplement la mention qu'il fait de TLS 1.1 est réputée supprimée. De même, le RFC 7525, qui résume les bonnes pratiques d'utilisation de TLS doit désormais se lire en oubliant les quelques endroits où il cite encore TLS 1.1. D'autres RFC avaient déjà été abandonnés, comme par exemple le RFC 5101.

Donc, pour résumer les points pratiques de ce RFC (sections 4 et 5) :

  • N'utilisez pas TLS 1.0. Le client TLS ne doit pas le proposer dans son ClientHello, le serveur TLS ne doit jamais l'accepter.
  • N'utilisez pas TLS 1.1. Le client TLS ne doit pas le proposer dans son ClientHello, le serveur TLS ne doit jamais l'accepter.

Si vous êtes programmeu·r·se, virez le code de TLS 1.0 et 1.1 de vos logiciels. (OpenSSL a prévu de le faire en 2022.) Notez que certains protocoles récents comme Gemini ou DoH (RFC 8484) imposaient déjà TLS 1.2 au minimum.

Comme le note la section 7 du RFC, suivre les recommandations de sécurité exposées ici va affecter l'interopérabilité : on ne pourra plus communiquer avec les vieilles machines. J'ai à la maison une vieille tablette pour laquelle le constructeur ne propose pas de mise à jour logicielle et qui, limitée à TLS 1.0, ne peut d'ores et déjà plus se connecter à beaucoup de sites Web en HTTPS. L'obsolescence programmée en raison de la sécurité… Plus grave, des organisations peuvent être coincées avec une vieille version de TLS sur des équipements, par exemple de contrôle industriel, qu'on ne peut pas mettre à jour. (Lors des discussions à l'IETF sur ce RFC, des personnes avaient suggéré d'attendre que le niveau d'utilisation de TLS 1.0 et 1.1 tombe en dessous d'une certaine valeur, avant d'abandonner officiellement ces protocoles. L'IETF a finalement choisi une approche plus volontariste. Mais pensez aux établissements comme les hôpitaux, avec tous les systèmes contrôlés par des vieux PC pas mettables à jour.) Comme toujours en sécurité, il n'y a pas de solution parfaite, uniquement des compromis. Le site de test TLS https://www.ssllabs.com/ montre ici un site Web d'une banque qui continue à proposer TLS 1.0 et 1.1, ce qui baisse sa note globale mais est peut-être justifié par le désir de ne pas laisser tomber les clients qui ne peuvent pas facilement changer leur logiciel : labanquepostale-tls1.png

Au contraire, voici ce qu'affiche un Firefox récent quand on essaie de se connecter à un vieux site Web qui n'accepte toujours pas TLS 1.2 : cig-old-tls.png

À noter que DTLS 1.0 (RFC 4347) est également abandonné. Cela laisse DTLS 1.2, le 1.1 n'ayant jamais été normalisé, et le 1.3 n'étant pas prêt.

Les RFC 2246 (TLS 1.0) et RFC 4346 (TLS 1.1) ont été officiellement reclassifiés comme n'ayant plus qu'un intérêt historique. Le RFC 7507 est également déclassé, le mécanisme qu'il décrit n'étant utile qu'avec TLS 1.0 et 1.1.


Téléchargez le RFC 8996


L'article seul

RFC 8989: Additional Criteria for Nominating Committee Eligibility

Date de publication du RFC : Février 2021
Auteur(s) du RFC : B.E. Carpenter (Univ. of Auckland), S. Farrell (Trinity College Dublin)
Expérimental
Première rédaction de cet article le 8 février 2021


L'épidémie de Covid-19 a remis en cause pas mal de choses dans nos sociétés, certaines fois très gravement. En moins dramatique, elle oblige l'IETF à adapter ses processus, étant donné qu'aucune réunion en présentiel n'a pu se tenir en 2020 et qu'on ne sait pas quand cela pourra reprendre. Or, la présence physique aux réunions était nécessaire pour se qualifier pour le NomCom, le comité de nomination. Ce RFC propose à titre expérimental de nouveaux critères de sélection.

Ce NomCom a pour tâche, décrite dans le RFC 8713, de désigner des personnes nommées pour remplir certains rôles à l'IETF. Il est composé de volontaires qui, normalement, doivent avoir participé en présentiel à un minimum de réunions IETF. Aujourd'hui, comme vous le savez, les réunions physiques ne sont plus possibles (à l'heure où j'écris, la réunion de San Francisco est très incertaine). Ce RFC modifie donc les règles pour l'année 2021 (le RFC 8788 avait déjà traité le cas de 2020). Il est officiellement expérimental (cf. RFC 3933) ; si les vaccins tiennent leur promesse, l'idée pourra être abandonnée et on reviendra aux pratiques d'avant. Mais, à l'heure actuelle, personne ne peut faire de pronostics sérieux dans ce domaine… Peut-être faudra-t-il se résigner à une modification permanente du RFC 8713 (section 2 de notre RFC).

En attendant que le futur s'éclaircisse, les règles pour cette année sont donc modifiées (section 4). Au lieu du seul critère de présence physique aux réunions, on accepte désormais pour sièger au NomCom les gens qui ont participé aux réunions en ligne (qui nécessitent une inscription et un enregistrement de « présence »), ou bien ont été président d'un groupe de travail ou bien auteur d'au moins deux RFC dans les cinq dernières années. Comme pour le critère précédent de présence physique, le but (section 3) est de vérifier que les membres du NomCom connaissent bien l'IETF.

D'autres critères auraient été possibles, mais ont été rejetés (section 5), comme l'écriture d'Internet-Drafts ou l'envoi de messages sur les listes de diffusion de l'IETF. Notez que la qualité des contributions à l'IETF ne rentre pas en ligne de compte, car elle est subjective.

Pour déterminer la liste des critères, le RFC se fonde sur une analyse de données (annexe A). Les données de départ ne sont pas parfaites (si quelqu'un s'inscrit à une réunion sous le nom de John Smith et à la suivante en mettant J. A. Smith, il apparaitra comme deux personnes différentes) mais c'est mieux que rien. Les jolis diagrammes de Venn en art ASCII montrent que les nouveaux critères permettent en effet d'élargir l'ensemble des volontaires potentiels, et que les critères rejetés n'auraient de toute façon pas eu d'influence.

L'expérience a fait l'objet d'un rapport publié en septembre 2021.


Téléchargez le RFC 8989


L'article seul

RFC 8985: The RACK-TLP Loss Detection Algorithm for TCP

Date de publication du RFC : Février 2021
Auteur(s) du RFC : Y. Cheng, N. Cardwell, N. Dukkipati, P. Jha (Google)
Chemin des normes
Réalisé dans le cadre du groupe de travail IETF tcpm
Première rédaction de cet article le 23 février 2021


Ce RFC normalise un nouvel algorithme pour la détection de paquets perdus dans TCP. Dans des cas comme celui où l'application n'a rien à envoyer (fin des données, ou attente d'une requête), RACK-TLP détecte les pertes plus rapidement, en utilisant des mesures fines du RTT, et en sollicitant des accusés de réception dans de nouveaux cas.

Pour comprendre cet algorithme et son intérêt, il faut revoir à quoi sert un protocole de transport comme TCP. Les données sont transmises sous forme de paquets (au format IP, dans le cas de l'Internet), paquets qui peuvent être perdus en route, par exemple si les files d'attente d'un routeur sont pleines. Le premier travail d'un protocole de transport est de réémettre les paquets perdus, de façon à ce que les applications reçoivent bien toutes les données (et dans l'ordre car, en prime, les paquets peuvent arriver dans le désordre, ce qu'on nomme un réordonnancement, qui peut être provoqué, par exemple, lorsque deux chemins sont utilisés pour les paquets). Un protocole de transport a donc besoin de détecter les pertes. Ce n'est pas si évident que cela. La méthode « on note quand on a émis un paquet et, si on n'a pas reçu d'accusé de réception au bout de N millisecondes, on le réémet » fonctionne, mais elle serait très inefficace. Notamment, le choix de N est difficile : trop court, et on déclarerait à tort des paquets comme perdus, réémettant pour rien, trop long et et ne détecterait la perte que trop tard, diminuant la capacité effective.

Le RFC original sur TCP, le RFC 793, expliquait déjà que N devait être calculé dynamiquement, en tenant compte du RTT attendu. En effet, si l'accusé de réception n'est pas arrivé au bout d'une durée égale au RTT, attendre davantage ne sert à rien. (Bien sûr, c'est plus compliqué que cela : le RTT peut être difficile à mesurer lorsque des paquets sont perdus, il varie dans le temps, puisque le réseau a pu changer, et il peut y avoir des temps de traitement qui s'ajoutent au RTT, il faut donc garder un peu de marge.) Armé de cette mesure dynamique du RTT, TCP peut calculer un RTO (Retransmission TimeOut, RFC 793, section 3.7) qui donne le temps d'attente. Le concept de RTO a été ensuite détaillé dans le RFC 6298 qui imposait un minimum d'une seconde (ce qui est beaucoup, par exemple à l'intérieur d'un même centre de données).

Mais d'autres difficultés surgissent ensuite. Par exemple, les accusés de réception de TCP indiquent le dernier octet reçu. Si on envoie trois paquets (plus rigoureusement trois segments, le terme utilisé par TCP) de cent octets, et qu'on reçoit un accusé de réception pour le centième octet indique que le premier paquet est arrivé mais ne dit rien du troisième. Peut-être est-il arrivé et que le second est perdu. Dans ce cas, la réémission du troisième paquet serait du gaspillage. Les accusés de réception cumulatifs de TCP ne permettent pas au récepteur de dire qu'il a reçu les premier et troisième paquets. Les SACK (Selective Acknowledgments) du RFC 2018 ont résolu ce problème. Dans l'exemple ci-dessus, SACK aurait permis au récepteur de dire « j'ai reçu les octets 0 - 100 et 200 - 300 [le second nombre de chaque bloc indique l'octet suivant le bloc] ».

Ensuite, on peut optimiser l'opération en n'attendant pas le RTO, dont on a vu qu'il était souvent trop long. L'idée de base est que, si le récepteur reçoit des données qui ne sont pas dans l'ordre attendu (le troisième paquet, alors qu'on n'a toujours pas vu le second), on renvoie un accusé de réception pour les données déjà reçues et déjà confirmées. Lorsque l'émetteur reçoit un accusé de réception qu'il a déjà vu (en fait, plusieurs), il comprend que des données manquent et il peut réémettre tout de suite, même si le RTO n'est pas écoulé. Ce mécanisme d'accusés de réception dupliqués (DUPACK, pour DUPlicate ACKnowledgment) a été décrit dans le RFC 5681, puis le RFC 6675. Une de ses faiblesses, que corrige notre RFC, est qu'il ne peut pas être utilisé à la fin d'une transmission, puisqu'il n'y a plus de données qui arrivent, empêchant le récepteur de détecter les pertes ou réordonnancements, et obligeant à attendre le RTO.

Et enfin, il faut se rappeler que le protocole de transport a une autre importante responsabilité : s'il doit s'assurer que toutes les données arrivent, et donc réémettre les paquets manquants, il doit aussi le faire en évitant la congestion (RFC 5681). Si des paquets se perdent, c'est peut-être que le réseau est saturé et réémettre trop brutalement pourrait encore aggraver la situation. Ceci dit, l'algorithme RACK-TLP décrit dans notre RFC ne traite que de la détection de pertes, les méthodes précédentes pour le contrôle de congestion restent valables. (C'est une des nouveautés de RACK-TLP : il sépare la détection de pertes du contrôle de congestion, pour lequel il existe plusieurs algorithmes comme le NewReno du RFC 6582 ou le CUBIC du RFC 8312. Ce découplage permet de faire évoluer séparement les algorithmes, et rend donc le système plus souple.)

Voici, désolé pour cette introduction un peu longue, on peut maintenant passer au sujet principal de ce RFC, RACK-TLP. Cet algorithme, ou plutôt ces deux algorithmes, vont être plus efficaces (détecter les pertes plus vite) que DUPACK, notamment à la fin d'une transmission, ou lorsqu'il y a des pertes des paquets retransmis, ou encore des réordonnancements fréquents des paquets (section 2.2 de notre RFC). RACK (Recent ACKnowledgment) va utiliser le RTT des accusés de réception pour détecter certaines pertes, alors que TLP (Tail Loss Probe) va émettre des paquets de données pour provoquer l'envoi d'accusés de réception par le récepteur.

La section 3 du RFC donne une vision générale des deux algorithmes. Commençons par RACK (Recent ACKnowledgment). Son but va être de détecter plus rapidement les pertes lorsque un paquet arrive au récepteur dans le désordre (paquet des octets 200 à 300 alors que le précédent était de 0 à 100, par exemple). Il va utiliser SACK (RFC 2018) pour cela, et RACK dépend donc du bon fonctionnement de SACK (section 4) et en outre, au lieu de ne regarder que les numéros de séquence contenus dans les ACK, comme le faisait DUPACK, il va également regarder le temps écoulé depuis l'émission d'un paquet. Les détails figurent en section 6 du RFC.

Quant à TLP (Tail Loss Probe), son rôle est de titiller le récepteur pour que celui-ci émette des accusés de récéption, même s'il n'en voyait pas la nécessité. Par exemple, si on arrive à la fin d'une session, l'émetteur a fini d'envoyer ses paquets, mais pas encore reçu les accusés de réception. Aucun envoi ne déclenchera donc DUPACK. Le principe de TLP est donc de solliciter un accusé de réception pour voir si on obtient un duplicata. RACK pourra alors utiliser cet accusé dupliqué. (Les deux algorithmes sont forcément utilisés ensemble, on n'utilise pas TLP sans RACK.) Les détails de TLP sont dans la section 7.

La résolution doit être meilleure qu'un quart du RTT. À l'intérieur d'un centre de données, cela implique de pouvoir compter les microsecondes, et, sur l'Internet public, les millisecondes.

La section 9 du RFC discute des avantages et inconvénients de RACK-TLP. Le gros avantage est que tout segment de données envoyé, même si c'est une retransmission, peut permettre de détecter des pertes. C'est surtout intéressant lorsqu'on est à la fin des données (et il n'y a donc plus rien à transmettre) ou lorsque des retransmissions sont perdues. Outre le cas de la fin du flot de données, les propriétés de RACK-TLP sont également utiles lorsque l'application garde la connexion ouverte mais n'a temporairement rien à transmettre (cas de sessions interactives, ou de protocoles requête/réponse comme EPP, où il ne se passe pas forcément quelque chose en permanence).

Mais comme la perfection n'existe pas en ce bas monde, RACK-TLP a aussi des inconvénients. Le principal est qu'il oblige à garder davantage d'état puisqu'il faut mémoriser l'heure de départ de chaque segment (contrairement à ce que proposait le RFC 6675), et avec une bonne résolution (un quart du RTT, demande le RFC). Une telle résolution n'est pas facile à obtenir dans un centre de données où les RTT sont très inférieurs à la milliseconde. Ceci dit, certaines mises en œuvre de TCP font déjà cela, même si, en théorie, elles pourraient se contenter de moins.

RACK-TLP coexiste avec d'autres algorithmes de détection de pertes, comme le classique RTO (RFC 6298) mais aussi avec ceux des RFC 7765, RFC 5682 et RFC 3522. De même, il coexiste avec divers algorithmes de contrôle de la congestion (RFC 5681, RFC 6937) puisqu'il se contente de détecter les pertes, il ne décide pas de comment on évite la congestion. En gros, RACK-TLP dit « ce segment est perdu » et la décision de réémission de celui-ci passera par l'algorithme de contrôle de la congestion, qui verra quand envoyer ces données à nouveau.

RACK-TLP est explicitement conçu pour TCP, mais il pourra aussi être utilisé dans le futur avec d'autres protocoles de transport, comme le futur QUIC.

RACK-TLP ne date pas d'aujourd'hui et il est déjà mis en œuvre dans Linux, FreeBSD et dans des systèmes d'exploitation moins connus comme Windows.


Téléchargez le RFC 8985


L'article seul

RFC 8984: JSCalendar: A JSON Representation of Calendar Data

Date de publication du RFC : Juillet 2021
Auteur(s) du RFC : N. Jenkins, R. Stepanek (Fastmail)
Chemin des normes
Réalisé dans le cadre du groupe de travail IETF calext
Première rédaction de cet article le 20 juillet 2021


Beaucoup d'applications gèrent des agendas, avec des réunions à ne pas oublier, des évènements récurrents, des rendez-vous précis. Le format traditionnel d'échange de ces applications est iCalendar, normalisé dans le RFC 5545 (ou, dans sa déclinaison en JSON, jCal, dans le RFC 7265). Ce RFC propose une syntaxe JSON mais, surtout, un modèle de données très différent, JSCalendar. Le but est de remplacer iCalendar.

Les principes de base ? Simplicité (au moins dans les cas simples, car les calendriers sont des bêtes compliquées…), uniformité (autant que possible, une seule façon de représenter un évènement), tout en essayant de permettre des conversions depuis iCalendar (RFC 5545 et RFC 7986), donc en ayant un modèle de données compatible. JSCalendar, comme son nom l'indique, utilise JSON, plus exactement le sous-ensemble i-JSON, normalisé dans le RFC 7493.

Bon, mais pourquoi ne pas avoir gardé iCalendar ? Parce qu'il est trop complexe avec plusieurs formats de date, parce que ses règles de récurrence sont ambigües et difficiles à comprendre, parce que sa syntaxe est mal définie. jCal n'a pas ce dernier problème mais il garde tous les autres. On voit même des logiciels utiliser leur propre représentation en JSON des données iCalendar au lieu d'utiliser jCal. Bref, JSCalendar préfère repartir, sinon de zéro, du moins d'assez loin.

Voici un exemple très simple et très minimal de représentation d'un évènement en JSCalendar :

{
     "@type": "Event",
     "uid": "a8df6573-0474-496d-8496-033ad45d7fea",
     "updated": "2020-01-02T18:23:04Z",
     "title": "Some event",
     "start": "2020-01-15T13:00:00",
     "timeZone": "America/New_York",
     "duration": "PT1H"
}
  

Comme avec iCalendar, les données JSCalendar peuvent être échangées par courrier ou avec n'importe quel autre protocole de son choix comme JMAP ou WebDAV.

Avant d'attaquer ce format JSCalendar, rappelez-vous qu'il utilise JSON et que la terminologie est celle de JSON. Ainsi, on nomme « objet » ce qui, selon le langage utilisé, serait un dictionnaire ou un tableau associatif. Ensuite, outre les types de base de JSON, JSCalendar a des types supplémentaires (section 1), notamment :

  • Id, un identificateur, pour que les objets aient un nom simple et unique (c'est une chaîne de caractères JSON). On peut par exemple utiliser un UUID (RFC 4122). Dans l'exemple ci-dessus, le membre uid de l'objet est un Id.
  • UTCDateTime, une date-et-heure (un instant dans le temps) au format du RFC 3339 dans une chaîne de caractères JSON, est obligatoirement en UTC. Un tel type est intéressant pour les évènements internationaux comme une vidéoconférence. Dans l'exemple ci-dessus, le membre updated est de ce type.
  • LocalDateTime, une date-et-heure selon le fuseau horaire local. Un tel type est utile pour les évènements locaux, comme un pique-nique. Dans l'exemple ci-dessus, qui concerne une réunion en présentiel, start est de ce type. On a besoin des deux types, à la fois parce que les évènements en distanciel et en présentiel n'ont pas les mêmes besoins, et aussi parce que le décalage entre les deux peut varier. Les calendriers, c'est compliqué et le RFC cite l'exemple de la LocalDateTime 2020-10-04T02:30:00 qui n'existe pas à Melbourne car le passage à l'heure d'été fait qu'on saute de 2h à 3h, mais qui peut apparaitre dans les calculs (start + une durée) ou bien si les règles de l'heure d'été changent.
  • Duration est une durée, exprimée comme en iCalendar (PT1H pour « une heure » dans l'exemple ci-dessus, ce serait P1Y pour une année). Gag : une journée (P1D) ne fait pas forcément 24 heures, par exemple si on passe à l'heure d'été pendant cette journée.
  • SignedDuration, une durée qui peut être négative.
  • TimeZoneId, un identifiant pour un fuseau horaire, pris dans la base de l'IANA, par exemple Europe/Paris.
  • PatchObject est un pointeur JSON (ces pointeurs sont normalisés dans le RFC 6901) qui permet de désigner un objet à modifier.
  • Relation est un objet, et plus un simple type scalaire comme les précédents. Il permet d'indiquer une relation entre deux objets, notamment vers un objet parent ou enfant..
  • Link est également une relation et donc un objet, mais vers le monde extérieur. Il a des propriétés comme href (dont la valeur est, comme vous vous en doutez, un URI) ou cid qui identifie le contenu vers lequel on pointe, dans la syntaxe du RFC 2392.

Les types de JSCalendar figurent dans un registre IANA, qu'on peut remplir avec la procédure « Examen par un expert » du RFC 8126.

Bien, maintenant que nous avons tous nos types, construisons des objets. Il y en a de trois types (section 2) :

  • Event (l'exemple ci-dessus, regardez sa propriété @type) est un évènement ponctuel, par exemple une réunion professionnelle ou une manifestation de rue. Il a une date-et-heure de départ, et une durée.
  • Task est une tâche à accomplir, elle peut avoir une date-et-heure limite et peut avoir une durée estimée.
  • Group est un groupe d'évènements JSCalendar.

JSCalendar n'a pas de règles de canonicalisation (normalisation) générales, car cela dépend trop de considérations sémantiques que le format ne peut pas connaitre. Par exemple, JSON permet des tableaux, et JSCalendar utilise cette possibilité mais, quand on veut décider si deux tableaux sont équivalents, doit-on tenir compte de l'ordre des éléments ([1, 2] == [2, 1]) ? Cela dépend de l'application et JSCalendar ne fixe donc pas de règles pour ce cas. (Un cas rigolo et encore pire est celui des valeurs qui sont des URI puisque la canonicalisation des URI dépend du plan - scheme.)

Quelles sont les propriétés typiques des objets JSCalendar (section 4) ? On trouve notamment, communs aux trois types d'objets (évènement, tâche et groupe) :

  • @type, le type d'objet (Event dans l'exemple ci-dessus, un des trois types possibles).
  • uid (a8df6573-0474-496d-8496-033ad45d7fea dans l'exemple), l'identificateur de l'objet (il est recommandé que ce soit un des UUID du RFC 4122).
  • prodID, un identificateur du logiciel utilisé, un peu comme le User-Agent: de HTTP. Le RFC suggère d'utiliser un FPI.
  • updated, une date-et-heure de type UTCDateTime.
  • title et description, des chaînes de caractères utiles.
  • locations (notez le S) qui désigne les lieux physiques de l'évènement. C'est compliqué. Chaque lieu est un objet de type Location qui a comme propriétés possibles un type (tiré du registre des types de lieux créé par le RFC 4589), et une localisation sous forme de latitude et longitude (RFC 5870).
  • Tous les évènements ne sont pas dans le monde physique, certains se produisent en ligne, d'où le virtualLocations, qui indique des informations comme l'URI (via la propriété du même nom). Ainsi, une conférence en ligne organisée par l'assocation Parinux via BigBlueButton aura comme virtualLocations {"@type": "VirtualLocation", "uri": "https://bbb.parinux.org/b/ca--xgc-4r3-n8z", …}.
  • color permet de suggérer au logiciel une couleur à utiliser pour l'affichage, qui est une valeur RGB en hexadécimal ou bien un nom de couleur CSS.

Et je suis loin d'avoir cité toutes les propriétés possibles, sans compter celles spécifiques à un type d'objet.

Pour le cas de locations, le RFC fournit un exemple de vol international (quand il était encore possible de prendre l'avion) :

   {
     "...": "",
     "title": "Flight XY51 to Tokyo",
     "start": "2020-04-01T09:00:00",
     "timeZone": "Europe/Berlin",
     "duration": "PT10H30M",
     "locations": {
       "418d0b9b-b656-4b3c-909f-5b149ca779c9": {
         "@type": "Location",
         "rel": "start",
         "name": "Frankfurt Airport (FRA)"
       },
       "c2c7ac67-dc13-411e-a7d4-0780fb61fb08": {
         "@type": "Location",
         "rel": "end",
         "name": "Narita International Airport (NRT)",
         "timeZone": "Asia/Tokyo"
       }
     }
   }
  

Notez les UUID pour identifier les lieux, et le changement de fuseau horaire entre le départ et l'arrivée. Si l'évènement a lieu à la fois en présentiel et en distanciel (ici, le concert est dans un lieu physique identifié par ses coordonnées géographiques, mais aussi diffusé en ligne), cela peut donner :

{
  "...": "",
  "title": "Live from Music Bowl: The Band",
  "description": "Go see the biggest music event ever!",
  "locale": "en",
  "start": "2020-07-04T17:00:00",
  "timeZone": "America/New_York",
  "duration": "PT3H",
  "locations": {
    "c0503d30-8c50-4372-87b5-7657e8e0fedd": {
      "@type": "Location",
      "name": "The Music Bowl",
      "description": "Music Bowl, Central Park, New York",
      "coordinates": "geo:40.7829,-73.9654"
    }
  },
  "virtualLocations": {
    "1": {
      "@type": "VirtualLocation",
      "name": "Free live Stream from Music Bowl",
      "uri": "https://stream.example.com/the_band_2020"
    }
    },
    ...
  

Les fournisseurs de logiciel peuvent ajouter des propriétés définies par eux. Dans ce cas, le RFC recommande fortement qu'ils les nomment en les faisant précéder d'un nom de domaine identifiant le fournisseur. Si celui-ci a example.com et veut une propriété toto, il la nommera example.com:toto. C'est évidemment une solution temporaire, les fournisseurs ont tout intérêt à enregistrer ces propriétés pour qu'elles puissent servir à tout le monde. Le mécanisme d'enregistrement de nouvelles propriétés est « Examen par un expert » (RFC 8126) et les propriétés sont dans un registre IANA.

Passons maintenant à un aspect compliqué mais indispensable des calendriers : les règles de récurrence, comme « Réunion de service tous les premiers lundis du mois à 10 h, sauf jour férié ». Il est important de maintenir ces évènements récurrents sous forme de règles, et de ne pas de les instancier immédiatement, car l'application des règles peut donner des résultats différents dans le futur. JSCalendar permet une propriété recurrenceRules qui décrit ces règles de récurrence et évidemment une excludedRecurrenceRules car s'il n'y a pas d'exceptions, ce n'est pas drôle. Exprimer les règles n'est pas facile. L'idée est de spécifier la récurrence sous forme d'une série de règles, chacune indiquant une fréquence (annuelle, mensuelle, etc), à chaque fois à partir de la propriété start, le calendrier utilisé (qui doit être un des calendriers disponibles dans CLDR), la marche à suivre quand une règle produit une date invalide dans ce calendrier (annuler l'évènement, le mettre avant, le mettre après), et plein d'autres propriétés optionnelles comme le jour du mois (« réunion tous les 6 du mois »), de la semaine (« tous les mercredis »), etc. Il faut ensuite plusieurs pages au RFC pour expliquer la façon subtile dont les règles sont appliquées, et se combinent. Les exceptions de la excludedRecurrenceRules fonctionnent de la même façon, et sont ensuite soustraites des dates-et-heures sélectionnées par les règles.

Le RFC fournit cet exemple de récurrence : le premier avril arrive tous les ans (et dure une journée, notez le duration) :

    {
     "...": "",
     "title": "April Fool's Day",
     "showWithoutTime": true,
     "start": "1900-04-01T00:00:00",
     "duration": "P1D",
     "recurrenceRules": [{
       "@type": "RecurrenceRule",
       "frequency": "yearly"
     }]
   }
  

Alors qu'ici, on fait du yoga une demi-heure chaque jour à 7 h du matin :

   {
     "...": "",
     "title": "Yoga",
     "start": "2020-01-01T07:00:00",
     "duration": "PT30M",
     "recurrenceRules": [{
       "@type": "RecurrenceRule",
       "frequency": "daily"
     }]
   }
  

Bon, ensuite, quelques détails pour aider le logiciel à classer et présenter les évènements. Un évènement peut avoir des propriétés comme priority (s'il y a deux réunions en même temps, laquelle choisir ?), freeBusyStatus (est-ce que cet évènement fait que je doive être considéré comme occupé ou est-il compatible avec autre chose ?), privacy (cet évènement peut-il être affiché publiquement ?), participationStatus (je viendrai, je ne viendrai pas, je ne sais pas encore…), et plein d'autres encore.

Il y a aussi des propriétés concernant d'autres sujets, par exemple l'adaptation locale. Ainsi, la propriété localizations indique la ou les langues à utiliser (leur valeur est une étiquette de langue du RFC 5646).

Toutes les propriétés vues jusqu'à présent étaient possibles pour tous les types d'objets JSCalendar (évènement, tâche et groupe). D'autres propriétés sont spécifiques à un ou deux types :

  • Les évènements (Event) peuvent avoir entre autres un start (un LocalDateTime qui indique le début de l'évènement) et un duration (la durée de l'évènement).
  • Les tâches (Task) peuvent avoir un start mais aussi un due qui indique la date où elles doivent être terminées, et un percentComplete (pour les chefs de projet…).
  • Les groupes (Group) ont, par exemple, entries, qui indique les objets qui sont membres du groupe.

Les fichiers à ce format JSCalendar sont servis sur l'Internet avec le type application/jscalendar+json.

Pour terminer, voyons un peu la sécurité de JSCalendar (section 7). Évidemment, toutes les informations stockées dans un calendrier sont sensibles : rares sont les personnes qui accepteraient de voir la totalité de leur agenda être publiée ! Celui-ci permet en effet de connaitre le graphe social (les gens qu'on connait), les lieux où passe la personne, ses habitudes et horaires, bref, que des choses personnelles. Toute application qui manipule des données JSCalendar doit donc soigneusement veiller à la confidentialité de ces données, et doit les protéger.

Le risque d'accès en lecture n'est pas le seul, la modification non autorisée de l'agenda serait également un problème, elle pourrait permettre, par exemple, de faire déplacer une personne en un lieu donné. D'autres conséquences d'une modification pourraient toucher la facturation (location d'une salle pendant une certaine durée) ou d'autres questions de sécurité (activer ou désactiver une alarme à certains moments). L'application qui manie du JSCalendar doit donc également empêcher ces changements non autorisés.

Notez que ces problèmes de sécurité ne concernent pas le format à proprement parler, mais les applications qui utilisent ce format. Rien dans JSCalendar, dans ce RFC, ne le rend particulièrement vulnérable ou au contraire protégé, tout est dans l'application.

Les règles de récurrence sont complexes et, comme tout programme, elles peuvent entrainer des conséquences imprévues, avec consommation de ressources informatiques associées. Un exemple aussi simple que la session de yoga quotidienne citée plus haut pourrait générer une infinité d'évènements, si elle était mise en œuvre par une répétition systématique, jusqu'à la fin des temps. Les programmes doivent donc faire attention lorsqu'ils évaluent les règles de récurrence.

L'un des buts d'un format standard d'évènements est évidemment l'échange de données. Il est donc normal et attendu qu'une application de gestion d'agenda reçoive des objets JSCalendar de l'extérieur, notamment via l'Internet. On voit souvent du iCalendar en pièce jointe d'un courrier, par exemple. Il ne faut pas faire une confiance aveugle à ces données venues d'on ne sait où, et ne pas tout intégrer dans le calendrier du propriétaire. L'authentification du courrier (par exemple avec DKIM, RFC 6376) aide un peu, mais n'est pas suffisante.

Les fuseaux horaires sont une source de confusion sans fin. Un utilisateur qui n'est pas attentif, lorsqu'on lui dit qu'un évènement a lieu à 10h30 (UTC-5) peut croire que c'est 10h30 de son fuseau horaire à lui. (Et encore, ici, j'ai indiqué le fuseau horaire de manière lisible, contrairement à ce que font la plupart des Étatsuniens, qui utilisent leurs sigles à eux comme PST, ou ce que font les Français qui n'indiquent pas le fuseau horaire, persuadés qu'ils sont que le monde entier est à l'heure de Paris.) Cette confusion peut être exploitée par des méchants qui utiliseraient les fuseaux horaires de manière délibérement peu claire pour tromper quelqu'un sur l'heure d'un évènement. (Dans la série Mad Men, les hommes qui ne supportent pas l'arrivée d'une femme dans le groupe lui donnent des informations trompeuses sur les heures de réunion, pour qu'elle manque ces évènements.)

Où trouve-t-on du JSCalendar à l'heure actuelle ? Mobilizon ne l'a pas encore (l'action « Ajouter à mon agenda » exporte du iCalendar). Fastmail annonce qu'ils gèrent JSCalendar (mais apparemment seulement dans les échanges JMAP, pas avec l'import/export normal). Cyrus l'a aussi (si vous avez des détails, ça m'intéresse). Pour le cas des récurrences, vous avez une intéressante mise en œuvre en Python. Attention, il y a aussi plein de logiciels qui s'appellent « jscalendar » (notamment des widgets JavaScript pour afficher un calendrier dans un formulaire Web) mais qui n'ont aucun rapport avec ce RFC.


Téléchargez le RFC 8984


L'article seul

RFC 8982: Registration Data Access Protocol (RDAP) Partial Response

Date de publication du RFC : Février 2021
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 10 février 2021


Le protocole de récupération d'informations auprès d'un registre RDAP peut parfois rapporter une grande quantité d'informations pour chaque objet récupéré (un nom de domaine, par exemple). Le RDAP originel ne permettait pas au serveur de ne renvoyer qu'une information partielle s'il trouvait que cela faisait trop. C'est désormais possible avec l'extension de ce RFC.

Le but est évidemment d'économiser les ressources du serveur, aussi bien que celles du client, en récupérant et en transférant moins de données. Avant notre RFC, la seule solution était de tout envoyer au client, même s'il n'en utiliserait pas la plus grande partie. (Si, au lieu de ne vouloir qu'une partie de chaque objet, on veut une partie des objets, il faut regarder le RFC 8977.)

Ce RFC étend donc le langage de requêtes de RDAP (normalisé dans le RFC 9082), avec un paramètre fieldSet (section 2 de notre RFC) qui permet de sélectionner une partie des champs de la réponse. Voici un exemple d'URL pour une requête de tous les noms de domaine de .com :

https://rdap.verisign.example/domains?name=*.com&fieldSet=afieldset
  

Et comment on connait les champs possibles ? Notre RFC introduit un subsetting_metadata qui indique l'ensemble de champs courant, et les ensembles possibles, chacun identifié par un nom, et comportant les liens (RFC 8288) à utiliser. (Vous aurez une erreur HTTP 400 si vous êtes assez vilain·e pour faire une requête RDAP avec un ensemble inexistant.) Et pour savoir si le serveur RDAP que vous interrogez accepte cette nouvelle extension permettant d'avoir des réponses partielles, regardez si subsetting apparait dans le tableau rdapConformance de la réponse du serveur (cette extension est désormais dans le registre IANA). Voici l'exemple que donne le RFC :


{
     "rdapConformance": [
       "rdap_level_0",
       "subsetting"
     ],
     ...
     "subsetting_metadata": {
       "currentFieldSet": "afieldset",
       "availableFieldSets": [
         {
         "name": "anotherfieldset",
         "description": "Contains some fields",
         "default": false,
         "links": [
           {
           "value": "https://example.com/rdap/domains?name=example*.com
                     &fieldSet=afieldset",
           "rel": "alternate",
           "href": "https://example.com/rdap/domains?name=example*.com
                    &fieldSet=anotherfieldset",
           "title": "Result Subset Link",
           "type": "application/rdap+json"
           }
         ]
         },
       ...
       ]
     },
     ...
     "domainSearchResults": [
       ...
     ]
}

  

Mais il y a encore plus simple que de regarder subsetting_metadata : notre RFC décrit, dans sa section 4, trois ensembles de champs standards qui, espérons-le, seront présents sur la plupart des serveurs RDAP :

  • id, qui ne renvoie dans la réponse que les identificateurs des objets (handle pour les contacts, unicodeName pour les noms de domaine, etc).
  • brief, un peu plus bavard, qui renvoie l'information minimale (telle qu'estimée par le serveur) sur les objets.
  • full, qui renvoie la réponse complète (actuellement, avant le déploiement de ce RFC 8982, c'est la valeur par défaut).

Un exemple de réponse avec l'ensemble id, où on n'a que le nom (ASCII) des domaines :


   {
     "rdapConformance": [
       "rdap_level_0",
       "subsetting"
     ],
     ...
     "domainSearchResults": [
       {
         "objectClassName": "domain",
         "ldhName": "example1.com",
         "links": [
           {
           "value": "https://example.com/rdap/domain/example1.com",
           "rel": "self",
           "href": "https://example.com/rdap/domain/example1.com",
           "type": "application/rdap+json"
           }
         ]
       },
       {
         "objectClassName": "domain",
         "ldhName": "example2.com",
         "links": [
           {
           "value": "https://example.com/rdap/domain/example2.com",
           "rel": "self",
           "href": "https://example.com/rdap/domain/example2.com",
           "type": "application/rdap+json"
           }
         ]
       },
       ...
     ]
   }

  

Quelles mises en œuvre sont disponibles ? Il en existe une chez .it mais elle n'est accessible sur leur serveur de test que si on est authentifié.

Comme RDAP, contrairement à whois, permet l'authentification des clients (RFC 7481), on pourra imaginer des cas où l'ensemble complet de la réponse ne sera envoyé qu'à certains utilisateurs (section 8).

L'annexe A du RFC revient sur un choix sur lequel je suis passé rapidement : le fait que le client spécifie le nom d'un ensemble pré-défini de champs, plutôt que la liste des champs qui l'intéressent, ce qui serait plus souple. Mais cela aurait été compliqué pour les structures JSON profondes (il faudrait une syntaxe riche et peu lisible), et cela aurait compliqué les autorisations : que faire si un client demande deux champs et qu'il n'est autorisé que pour un seul ? Outre ces questions génériques de tout protocole utilisant JSON, il y avait des problèmes spécifiques à RDAP, comme le fait que le contenu sur une entité peut être réparti dans plusieurs structures JSON (ah, le jCard…) Des solutions existent, comme le langage de requêtes CQL, mais il a semblé trop coûteux pour un intérêt limité.


Téléchargez le RFC 8982


L'article seul

RFC 8981: Temporary Address Extensions for Stateless Address Autoconfiguration in IPv6

Date de publication du RFC : Février 2021
Auteur(s) du RFC : F. Gont (SI6 Networks), S. Krishnan (Kaloom), T. Narten, R. Draves (Microsoft Research)
Chemin des normes
Réalisé dans le cadre du groupe de travail IETF 6man
Première rédaction de cet article le 1 mars 2021


Une des particularités d'IPv6 est de disposer d'un mécanisme, l'autoconfiguration sans état qui permet à une machine de se fabriquer une adresse IP globale sans serveur DHCP. Autrefois, ce mécanisme créait souvent l'adresse IP à partir de l'adresse MAC de la machine et une même machine avait donc toujours le même identifiant (IID, Interface IDentifier, les 64 bits les plus à droite de l'adresse IPv6), même si elle changeait de réseau et donc de préfixe. Il était donc possible de « suivre à la trace » une machine, ce qui pouvait poser des problèmes de protection de la vie privée. Notre RFC, qui remplace le RFC 4941 fournit une solution, sous forme d'un mécanisme de création d'adresses temporaires, partiellement aléatoires (ou en tout cas imprévisibles). Les changements sont assez importants depuis le RFC 4941, qui traitait un peu les adresses IPv6 temporaires comme des citoyennes de deuxième classe. Désormais, elles sont au contraire le choix préféré.

L'autoconfiguration sans état (SLAAC, pour Stateless Address Autoconfiguration), normalisée dans le RFC 4862, est souvent présentée comme un des gros avantages d'IPv6. Sans nécessiter de serveur central (contrairement à DHCP), ce mécanisme permet à chaque machine d'obtenir une adresse globale (IPv4, via le protocole du RFC 3927, ne permet que des adresses purement locales) et unique. Cela se fait en concaténant un préfixe (par exemple 2001:db8:32:aa12::), annoncé par le routeur, à un identifiant d'interface (par exemple 213:e8ff:fe69:590d, l'identifiant de mon PC portable - les machines individuelles, non partagées, comme les ordiphones sont particulièrement sensibles puisque la connaissance de la machine implique celle de son maître), typiquement dérivé de l'adresse MAC.

Partir de l'adresse MAC présente des avantages (quasiment toute machine en a une, et, comme elle est relativement unique, l'unicité de l'adresse IPv6 est obtenue facilement) mais aussi un inconvénient : comme l'identifiant d'interface est toujours le même, cela permet de reconnaître une machine, même lorsqu'elle change de réseau. Dès qu'on voit une machine XXXX:213:e8ff:fe69:590d (où XXXX est le préfixe), on sait que c'est mon PC. Un tiers qui écoute le réseau peut ainsi suivre « à la trace » une machine, et c'est pareil pour les machines avec lesquelles on correspond sur l'Internet. Or, on ne peut pas cacher son adresse IP, il faut l'exposer pour communiquer. Le RFC 7721 analyse le problème plus en détail (voir aussi le RFC 7707).

Les sections 1.2 et 2 de notre RFC décrivent la question à résoudre. Elles notent entre autre que le problème n'est pas aussi crucial que l'avaient prétendu certains. En effet, il existe bien d'autres façons, parfois plus faciles, de suivre une machine à la trace, par exemple par les fameux petits gâteaux de HTTP (RFC 6265) mais aussi par des moyens de plus haute technologie comme les métadonnées de la communication (taille des paquets, écart entre les paquets) ou les caractéristiques matérielles de l'ordinateur, que l'on peut observer sur le réseau. Parmi les autres méthodes, notons aussi que si on utilise les adresses temporaires de notre RFC 8981 mais qu'on configure sa machine pour mettre dynamiquement à jour un serveur DNS avec cette adresse, le nom de domaine suffirait alors à suivre l'utilisateur à la trace !

La section 2.2 commence à discuter des solutions possibles. DHCPv6 (RFC 3315, notamment la section 12) serait évidemment une solution, mais qui nécessite l'administration d'un serveur. L'idéal serait une solution qui permette, puisque IPv6 facilite l'existence de plusieurs adresses IP par machine, d'avoir une adresse stable pour les connexions entrantes et une adresse temporaire, non liée à l'adresse MAC, pour les connexions sortantes, celles qui « trahissent » la machine. C'est ce que propose notre RFC, en générant les adresses temporaires selon un mécanisme pseudo-aléatoire (ou en tout cas imprévisible à un observateur extérieur).

Enfin, la section 3 décrit le mécanisme de protection lui-même. Deux algorithmes sont proposés mais il faut bien noter qu'une machine est toujours libre d'en ajouter un autre, le mécanisme est unilatéral et ne nécessite pas que les machines avec qui on correspond le comprennent. Il suffit que l'algorithme respecte ces principes :

  • Les adresses IP temporaires sont utilisées pour les connexions sortantes (pour les entrantes, on a besoin d'un identificateur stable, un serveur n'a pas de vie privée),
  • Elles sont… temporaires (typiquement quelques heures),
  • Cette durée de vie limitée suit les durées de vie indiquées par SLAAC, et, en prime, on renouvelle les IID (identifiants d'interface) tout de suite lorsque la machine change de réseau, pour éviter toute corrélation,
  • L'identifiant d'interface est bien sûr différent pour chaque interface, pour éviter qu'un observateur ne puisse détecter que l'adresse IP utilisée en 4G et celle utilisée en WiFi désignent la même machine,
  • Ces adresses temporaires ne doivent pas avoir de sémantique visible (cf. RFC 7136) et ne pas être prévisibles par un observateur.

Le premier algorithme est détaillé dans la section 3.3.1 et nécessite l'usage d'une source aléatoire, selon le RFC 4086. On génère un identifiant d'interface avec cette source (attention, certains bits sont réservés, cf. RFC 7136), on vérifie qu'il ne fait pas partie des identifiants réservés du RFC 5453, puis on la préfixe avec le préfixe du réseau.

Second algorithme possible, en section 3.3.2, une génération à partir de l'algorithme du RFC 7217, ce qui permet d'utiliser le même algorithme pour ces deux catégories d'adresses, qui concernent des cas d'usage différents. Le principe est de passer les informations utilisées pour les adresses stables du RFC 7217 (dont un secret, pour éviter qu'un observateur ne puisse déterminer si deux adresses IP appartiennent à la même machine), en y ajoutant le temps (pour éviter la stabilité), à travers une PRF comme SHA-256. Plus besoin de générateur aléatoire dans ce cas.

Une fois l'adresse temporaire générée (les détails sont dans la section 3.4), et testée (DAD), il faut la renouveler de temps en temps (sections 3.5 et 3.6, cette dernière expliquant pourquoi renouveler une fois par jour est plus que suffisant). L'ancienne adresse peut rester active pour les connexions en cours mais plus pour de nouvelles connexions.

Notez que le RFC impose de fournir un moyen pour activer ou débrayer ces adresses temporaires, et que cela soit par préfixe IP, par exemple pour débrayer les adresses temporaires pour les adresses locales du RFC 4193.

Maintenant que l'algorithme est spécifié, la section 4 du RFC reprend de la hauteur et examine les conséquences de l'utilisation des adresses temporaires. C'est l'occasion de rappeler un principe de base de la sécurité : il n'y a pas de solution idéale, seulement des compromis. Si les adresses temporaires protègent davantage contre le « flicage », elles ont aussi des inconvénients comme de rendre le déboguage des réseaux plus difficile. Par exemple, si on note un comportement bizarre associé à certaines adresses IP, il sera plus difficile de savoir s'il s'agit d'une seule machine ou de plusieurs. (Le RFC 7217 vise justement à traiter ce cas.)

Et puis les adresses temporaires de notre RFC ne concernent que l'identifiant d'interface, le préfixe IP reste, et il peut être révélateur, surtout s'il y a peu de machines dans le réseau. Si on veut vraiment éviter le traçage par adresse IP, il faut utiliser Tor ou une technique équivalente (cf. section 9).

L'utilisation des adresses temporaires peut également poser des problèmes avec certaines pratiques prétendument de sécurité comme le fait, pour un serveur, de refuser les connexions depuis une machine sans enregistrement DNS inverse (un nom correspondant à l'adresse, via un enregistrement PTR). Cette technique est assez ridicule mais néanmoins largement utilisée.

Un autre conflit se produira, note la section 8, si des mécanismes de validation des adresses IP source sont en place dans le réseau. Il peut en effet être difficile de distinguer une machine qui génère des adresses IP temporaires pour se protéger contre les indiscrets d'une machine qui se fabrique de fausses adresses IP source pour mener une attaque.

Quelles sont les différences entre le précédent RFC et celui-ci ? La section 5 du RFC résume les importants changements qu'il y a eu depuis le RFC 4941. Notamment :

  • Abandon de MD5 (cf. RFC 6151),
  • Autorisation de n'avoir que des adresses temporaires (le RFC 4941 imposait qu'une adresse stable reste présente),
  • Autoriser les adresses temporaires à être utilisées par défaut, ce qui était déjà le cas en pratique sur la plupart des systèmes d'exploitation (depuis l'époque du RFC 4941, la gravité de la surveillance de masse est mieux perçue),
  • Réduction de la durée de vie recommandée, et recommandation qu'elle soit partiellement choisie au hasard, pour éviter que toutes les adresses soient refaites en même temps,
  • Un algorithme de génération des identifiants d'interface, reposant sur une mémoire stable de la machine, a été supprimé,
  • Un autre a été ajouté, se fondant sur le RFC 7217,
  • Intégration des analyses plus détaillées qui ont été faites de la sécurité d'IPv6 (RFC 7707, RFC 7721 et RFC 7217),
  • Correction des bogues.

Passons maintenant aux mises en œuvre. L'ancienne norme, le RFC 4941, est appliqué par presque tous les systèmes d'exploitation, souvent de manière plus protectrice de la vie privée, par exemple en étant activé par défaut. Les particularités de notre nouveau RFC ne sont pas toujours d'ores et déjà présentes. Ainsi, il existe des patches pour FreeBSD et pour Linux mais pas forcément intégrés aux versions officielles. Sur Linux, le paramètre sysctl qui contrôle ce protocole est net.ipv6.conf.XXX.use_tempaddr où XXX est le nom de l'interface réseau, par exemple eth0. En mettant dans le fichier /etc/sysctl.conf :

# Adresses temporaires du RFC 8981
net.ipv6.conf.default.use_tempaddr = 2

On met en service les adresses temporaires, non « pistables » pour toutes les interfaces (c'est le sens de la valeur default). Pour s'assurer que les réglages soient bien pris en compte, il vaut mieux faire en sorte que le module ipv6 soit chargé tout de suite (sur Debian, le mettre dans /etc/modules) et que les interfaces fixes aient leur propre réglage. Par exemple, sur Debian, on peut mettre dans /etc/network/interfaces :

iface eth2 inet dhcp
   pre-up sysctl -w net.ipv6.conf.eth2.use_tempaddr=2

Ou alors il faut nommer explicitement ses interfaces :

net.ipv6.conf.eth0.use_tempaddr = 2

Notez aussi qu'ifconfig n'affiche pas quelles adresses sont les temporaires (mais ip addr show le fait).

Notons que l'adresse « pistable » est toujours présente mais vient s'y ajouter une adresse temporaire choisie au hasard. Selon les règles du RFC 6724, rappelées dans la section 3.1 de notre RFC, c'est cette adresse temporaire qui sera choisie, en théorie, pour les connexions sortantes (voir aussi le RFC 5014 pour une API permettant de contrôler ce choix). Avec Linux, il faut pour cela que use_tempaddr vale plus que un (sinon, l'adresse temporaire est bien configurée mais pas utilisée par défaut). ifconfig affichera donc :

wlan0     Link encap:Ethernet  HWaddr 00:13:e8:69:59:0d  
...
          inet6 addr: 2001:db8:32:aa12:615a:c7ba:73fb:e2b7/64 Scope:Global
          inet6 addr: 2001:db8:32:aa12:213:e8ff:fe69:590d/64 Scope:Global

puis, au démarrage suivant, l'adresse temporaire (la première ci-dessus) changera :

wlan0     Link encap:Ethernet  HWaddr 00:13:e8:69:59:0d
...
          inet6 addr: 2001:db8:32:aa12:48a9:bf44:5167:463e/64 Scope:Global
          inet6 addr: 2001:db8:32:aa12:213:e8ff:fe69:590d/64 Scope:Global

Sur NetBSD, il n'y a qu'une seule variable syscvtl pour toutes les interfaces. Il suffit donc de mettre dans /etc/sysctl.conf :

net.inet6.ip6.use_tempaddr=1

Pour que l'adresse temporaire soit utilisée par défaut, c'est net.inet6.ip6.prefer_tempaddr. Sur FreeBSD, je n'ai pas essayé, mais je suppose que les variables sysctl au nom bien parlant net.inet6.ip6.use_tempaddr et net.inet6.ip6.prefer_tempaddr sont là pour cela.


Téléchargez le RFC 8981


L'article seul

RFC 8980: Report from the IAB Workshop on Design Expectations vs. Deployment Reality in Protocol Development

Date de publication du RFC : Février 2021
Auteur(s) du RFC : J. Arkko, T. Hardie
Pour information
Première rédaction de cet article le 20 février 2021


L'atelier de l'IAB DEDR (Design Expectations vs. Deployment Reality) s'est tenu à Kirkkonummi en juin 2019 (oui, il faut du temps pour publier un RFC de compte-rendu). Son but était, par delà la constatation que le déploiement effectif des protocoles conçus par l'IETF ne suit pas toujours les attentes initiales, d'explorer pourquoi cela se passait comme cela et ce qu'on pouvait y changer.

Souvent, lors de la mise au point d'un nouveau protocole, les personnes qui y travaillent ont en tête un modèle de déploiement. Par exemple, pour le courrier électronique, l'idée dominante était qu'il y aurait souvent un serveur de messagerie par personne. On sait que ce n'est pas la situation actuelle. En raison de soucis de sécurité, de pressions économiques et/ou politiques, et d'autres facteurs, on a aujourd'hui un courrier nettement plus centralisé, avec un caractère oligopolistique marqué, dominé par une poignée d'acteurs qui dictent les règles. Cette centralisation n'est pas souhaitable (sauf pour les GAFA). Mais où est-ce que le dérapage a commencé ? Parfois, cela a été rapide, et parfois cela a pris beaucoup de temps.

Quelques exemples :

  • Le courrier électronique, on l'a dit, était conçu autour de l'idée qu'il y aurait beaucoup de serveurs, gérés par des acteurs différents. (À une époque, dans le monde universitaire, il était courant que chaque station de travail ait un serveur de courrier, et ses propres adresses.) Aujourd'hui, il est assez centralisé, chez des monstres comme Gmail et Outlook.com. Un des facteurs de cette centralisation est le spam, contre lequel il est plus facile de se défendre si on est gros.
  • Le DNS avait été prévu pour une arborescence de noms assez profonde (l'exemple traditionnel était l'ancien domaine de premier niveau .us qui était découpé en entités géographiques de plus en plus spécifiques). Mais l'espace des noms est aujourd'hui plus plat que prévu. On voit même des organisations qui ont, mettons, example.com et qui, lançant un nouveau produit, mettons foobar, réservent foobar.com, voire foobar-example.com au lieu de tout simplement créer foobar.example.com (ce qui serait en outre moins cher). Un autre exemple de centralisation est la tendance à abandonner les résolveurs locaux (qui, il est vrai, sont souvent mal gérés ou menteurs) pour des gros résolveurs publics comme Google Public DNS ou Quad9 (et cette centralisation, contrairement à ce qui est souvent prétendu, n'a rien à voir avec DoH).
  • Le Web est également un exemple de déviation des principes originels, vers davantage de centralisation. Le concept de base est très décentralisé. N'importe qui peut installer un serveur HTTP sur son Raspberry Pi et faire partie du World Wide Web. Mais cela laisse à la merci, par exemple, des dDoS ou, moins dramatiquement, de l'effet Slashdot (même si un site Web statique peut encaisser une charge importante). On constate que de plus en plus de sites Web dépendent donc de services centralisés, CDN ou gros relais comme Cloudflare.
  • Et ça ne va pas forcément s'arranger avec de nouvelles technologies, par exemple l'apprentissage automatique, qui sont difficiles à décentraliser.

L'IAB avait produit un RFC sur la question « qu'est-ce qui fait qu'un protocole a du succès », le RFC 5218. Dans un autre document, le RFC 8170, l'IAB étudiait la question des transitions (passage d'un protocole à un autre, ou d'une version d'un protocole à une autre). L'atelier de Kirkkonummi avait pour but de poursuivre la réflexion, en se focalisant sur les cas où les suppositions de base n'ont pas tenu face à la réalité.

L'agenda de l'atelier était structuré en cinq sujets, le passé (qu'avons-nous appris), les principes (quelles sont les forces à l'œuvre et comment les contrer ou les utiliser), la centralisation (coûts et bénéfices), la sécurité et le futur (fera-t-on mieux à l'avenir et savons-nous comment). 21 articles ont été reçus (ils sont en ligne), et 30 personnes ont participé.

Sur le passé, l'atelier a étudié des exemples de déploiement de protocoles, comme PKIX, DNSSEC, le NAT, l'IoT, etc. Souvent, ce qui a été effectivement déployé ne correspondait pas à ce qui était prévu. Par exemple, un protocole très demandé n'a pas eu le succès attendu (c'est le cas de DNSSEC : tout le monde réclame de la sécurité, mais quand il faut travailler pour la déployer, c'est autre chose) ou bien il n'a pas été déployé comme prévu. C'est d'autant plus vrai que l'IETF n'a pas de pouvoir : elle produit des normes et, ensuite, d'autres acteurs décident ou pas de déployer, et de la manière qu'ils veulent (le « marché » sacré). Le RFC cite quelques leçons de l'expérience passée :

  • Les retours du terrain arrivent toujours trop tard, bien après que la norme technique ait été bouclée. C'est particulièrement vrai pour les retours des utilisateurs individuels (ceux que, d'après le RFC 8890, nous devons prioriser).
  • Les acteurs de l'Internet font des choses surprenantes.
  • On constate la centralisation mais on ne peut pas forcément l'expliquer par les caractéristiques des protocoles. Rien dans HTTP ou dans SMTP n'impose la centralisation qu'on voit actuellement.
  • En revanche, certaines forces qui poussent à la centralisation sont connues : cela rend certains déploiements plus faciles (c'est un argument donné pour refuser la décentralisation). Et les solutions centralisées ont nettement la faveur des gens au pouvoir, car ils préfèrent travailler avec un petit nombre d'acteurs bien identifiés. (C'est pour cela qu'en France, tous les hommes politiques disent du mal des GAFA tout en combattant les solutions décentralisées, qui seraient plus difficiles à contrôler.)
  • On ne sait pas toujours si un nouveau protocole sera vraiment utile ou pas (cf. la 5G et le porno dans l'ascenseur).
  • Il est facile de dire que l'IETF devrait davantage interagir avec tel ou tel groupe, mais certains groupes sont difficiles à toucher (le RFC 8890 détaille ce problème).

Sur les principes, on a quand même quelques conclusions solides. Par exemple, il est clair qu'un nouveau protocole qui dépend de plusieurs autres services qui ne sont pas encore déployés aura davantage de mal à s'imposer qu'un protocole qui peut s'installer unilatéralement sans dépendre de personne. Et puis bien sûr, il y a l'inertie. Quand quelque chose marche suffisamment bien, il est difficile de la remplacer. (Le RFC donne l'exemple de BGP, notamment de sa sécurité.) Et cette inertie génère le phénomène bien reconnu de la prime au premier arrivant. Si un protocole fournit un nouveau service, un remplaçant aura le plus grand mal à s'imposer, même s'il est meilleur, face au tenant du titre. Le monde du virtuel est lourd et ne bouge pas facilement. Quand un protocole a eu un succès fou (terme défini et discuté dans le RFC 5218), le remplacer devient presque impossible. Autre principe analysé : l'IETF ne devrait de toute façon pas avoir d'autorité sur la façon dont ses protocoles sont déployés. Ce n'est pas son rôle et elle n'a pas de légitimité pour cela. Et, on l'a dit, les usages sont durs à prévoir. L'atelier a aussi constaté que certains modèles de déploiement, qui s'appliquent à beaucoup de cas, n'avaient pas été prévus et planifiés. C'est le cas de l'extrême centralisation, bien sûr, mais également le cas de « tout via le Web » qui fait que tout nouveau service sur l'Internet tend à être accessible uniquement à distance, avec les protocoles et les formats du Web. Cela tend à créer des silos fermés, même s'ils utilisent des protocoles ouverts. (C'est en réponse à ce problème qu'a été créée la licence Affero.)

Concernant le troisième sujet, la centralisation, l'atelier a constaté que la tendance à la centralisation n'est pas toujours claire dès le début. Le RFC cite l'exemple (très mauvais, à mon avis) de DoH. Autre constatation, la sécurité, et notamment le risque d'attaques par déni de services réparties est un puissant facteur de centralisation. Si vous voulez gérer un site Web sur un sujet controversé, avec des opposants puissants et prêts à tout, vous êtes quasiment obligé de faire appel à un hébergement de grande taille (face aux dDoS, la taille compte). Ceci dit, l'atelier a aussi identifié des forces qui peuvent aller en sens contraire à la centralisation. Une fédération peut mieux résister aux attaques (qui ne sont pas forcément techniques, cela peut être aussi la censure) qu'un gros silo centralisé, le chiffrement peut permettre même aux petits de limiter la surveillance exercée par les gros, les navigateurs Web peuvent adopter de meilleures pratiques pour limiter les données envoyées à la grosse plate-forme à laquelle on accède (toute cette liste du RFC est d'un optimisme souvent injustifié…), l'interopérabilité peut permettre davantage de concurrence (cf. le bon rapport du Conseil National du Numérique), et certaines tendance lourdes peuvent être combattues également par des moyens non-techniques (le RFC cite la régulation, ce qui n'est pas facilement accepté par les libertariens, même si ceux-ci ne sont pas plus nombreux à l'IETF qu'ailleurs).

Quatrième sujet, la sécurité. Traditionnellement, le modèle de menace de l'Internet (RFC 3552 mais aussi RFC 7258) est que tout ce qui est entre les deux machines qui communiquent peut être un ennemi. Cela implique notamment le choix du chiffrement de bout en bout. Toutefois, ce modèle de menace ne marche pas si c'est l'autre pair qui vous trahit. Ainsi, utiliser TLS quand vous parlez à Facebook vous protège contre des tiers mais pas contre Facebook lui-même. À l'heure où tant de communications en ligne sont médiées par des GAFA, c'est un point à prendre en considération. (Attention à ne pas jeter le bébé avec l'eau du bain ; le chiffrement de bout en bout reste nécessaire, mais n'est pas suffisant. Certains FAI remettent en cause le modèle de menace traditionnel pour combattre ou restreindre le chiffrement, en faisant comme si les GAFA étaient les seuls à faire de la surveillance.) Les participants à l'atelier sont plutôt tombés d'accord sur la nécessité de faire évoluer le modèle de menace mais il faudra veiller à ce que ne soit pas un prétexte pour relativiser l'importance du chiffrement, qui reste indispensable. À noter une autre limite d'un modèle de menace qui ne mentionnerait que les tiers situés sur le trajet : non seulement la partie distante (par exemple Facebook) peut être une menace, mais votre propre machine peut vous trahir, par exemple si vous n'utilisez pas du logiciel libre.

Enfin, cinquième et dernier sujet, le futur. L'IETF n'a pas de pouvoir contraignant sur les auteurs de logiciels, sur les opérateurs ou sur les utilisateurs (et heureusement). Elle ne peut agir que via ses normes. Par exemple, pour l'Internet des Objets, le RFC 8520 permet davantage d'ouverture dans la description des utilisations qu'un objet fera du réseau. Outre la production de bonnes normes, l'IETF peut être disponible quand on a besoin d'elle, par exemple comme réserve de connaissance et d'expertise.

La conclusion du RFC (section 5) indique que le problème est complexe, notamment en raison de la variété des parties prenantes : tout le monde n'est pas d'accord sur les problèmes, et encore moins sur les solutions. (Il n'existe pas de « communauté Internet », à part dans des discours politiciens.) D'autre part, certains des problèmes n'ont pas de solution évidente. Le RFC cite ainsi les dDoS, ou le spam. Cette absence de solution satisfaisante peut mener à déployer des « solutions » qui ont un rôle négatif. Le RFC note ainsi à juste titre que l'absence d'une solution de paiement en ligne correcte (anonyme, simple, bon marché, reposant sur des normes ouvertes, etc) pousse à faire dépendre la rémunération des créateurs de la publicité, avec toutes ses conséquences néfastes. Et l'Internet fait face à de nombreux autres défis stratégiques, comme la non-participation des utilisateurs aux questions qui les concernent, ou comme la délégation de décisions à des logiciels, par exemple le navigateur Web.

On l'a dit, la conclusion est que l'IETF doit se focaliser sur ce qu'elle sait faire et bien faire, la production de normes. Cela implique :

  • Travailler sur une mise à jour du modèle de menace cité plus haut,
  • Réfléchir aux mesures qui peuvent être prises pour limiter la centralisation,
  • Mieux documenter les principes architecturaux de l'Internet, notamment le principe de bout en bout,
  • Travailler sur les systèmes de réputation (cf. RFC 7070),
  • Etc.

Un autre compte-rendu de cet atelier, mais très personnel, avait été fait par Geoff Huston.


Téléchargez le RFC 8980


L'article seul

RFC 8978: Reaction of Stateless Address Autoconfiguration (SLAAC) to Flash-Renumbering Events

Date de publication du RFC : Mars 2021
Auteur(s) du RFC : F. Gont (SI6 Networks), J. Zorz (6connect), R. Patterson (Sky UK)
Pour information
Réalisé dans le cadre du groupe de travail IETF v6ops
Première rédaction de cet article le 11 mars 2021


Au moment où je commençais à écrire cet article, mon FAI a trouvé drôle de changer le préfixe IPv6 attribué à mon réseau à la maison. Cela a parfaitement illustré le problème que décrit ce RFC : comment réagissent les machines IPv6 qui ont obtenu une adresse dynamique lors d'une rénumérotation brutale et sans avertissement ? En résumé : ça ne se passe pas toujours bien.

Posons le problème. Notre RFC se concentre sur le cas des machines qui ont obtenu une adresse par le système SLAAC (StateLess Address AutoConfiguration), normalisé dans le RFC 4862. Le routeur émet des messages RA (Router Advertisement) qui indiquent quel est le préfixe d'adresses IP utilisé sur le réseau local. Les machines vont alors prendre une adresse dans ce préfixe, tester qu'elle n'est pas déjà utilisée et c'est parti. Le routeur n'a pas besoin de mémoriser quelles adresses sont utilisées, d'où le terme de « sans état ». L'information distribuée par ces RA a une durée de vie, qui peut être de plusieurs jours, voire davantage.

Maintenant, envisageons un changement du préfixe, quelle que soit sa raison. Si ce changement est planifié, le routeur va accepter les deux préfixes, il va annoncer le nouveau mais l'ancien marchera encore, pendant la durée de vie qui était annoncée, et tout le monde sera heureux et communiquera. Mais si le changement de préfixe n'est pas planifié ? Par exemple, si le routeur obtient lui-même le préfixe dynamiquement (par exemple par le DHCP-PD du RFC 8415) puis qu'il redémarre et qu'il n'avait pas noté le préfixe précédent ? Il ne pourra alors pas continuer à router l'ancien préfixe, que les machines du réseau local utiliseront encore pendant un certain temps, à leur grand dam.

C'est ce qui s'est passé le 19 janvier 2021 (et les jours suivants), lorsque Free a subitement renuméroté les préfixes IPv6 d'un bon nombre de clients. Free n'utilise pas DHCP-PD, je suppose que les Freebox étaient configurées par un autre procédé, en tout cas, les utilisateurs n'ont pas été prévenus. Sur le réseau local, les machines avaient acquis les deux préfixes, l'ancien, conservé en mémoire, et le nouveau. Et l'ancien préfixe ne marchait plus (n'était pas routé), entrainant plein de problèmes. (Notons que l'adresse IPv4 avait également été changée mais ce n'est pas le sujet ici.) Il a fallu redémarrer toutes les machines, pour qu'elles oublient l'ancien préfixe. Une illustration parfaite du problème qui a motivé ce RFC : SLAAC avait été prévu pour des préfixes qui changent rarement et de manière planifiée (avec réduction préalable de la durée de vie, pour que la transition soit rapide). Les surprises, qu'elles soient dues à un problème technique ou simplement au manque de planification, ne sont pas prises en compte.

À part le mode Yolo de Free, dans quelles conditions aura-t-on de ces renumérotages brutaux, ces flash-renumbering events ? Un exemple est celui où le RA qui doit annoncer la fin de l'ancien préfixe (avec une durée de vie nulle) s'est perdu (le multicast n'est pas toujours fiable). Mais, de toute façon, envoyer ce RA d'avertissement suppose de savoir qu'un nouveau préfixe a remplacé l'ancien. Or, cette information n'est pas toujours disponible. Un exemple est celui où il n'y a même pas de RA d'avertissement, car le CPE obtient son préfixe IPv6 par DHCP-PD (Prefix Delegation, RFC 8415, section 6.3), avant de le redistribuer sur le réseau local en SLAAC. Si ce CPE redémarre, et n'a pas de mémoire permanente, il va peut-être obtenir un autre préfixe via DHCP-PD, ce qui fera un flash-renumbering event sur le réseau local. (Il peut aussi y avoir désynchronisation entre la durée de vie des RA faits via SLAAC et la durée du bail DHCP. En théorie, c'est interdit, mais certains CPE où les deux protocoles sont gérés par des modules logiciels différents font cette erreur.)

La Freebox n'utilise apparemment pas DHCP-PD mais le même problème d'ignorance du préfixe précédent peut survenir si la mise à jour des box est faite par un autre moyen de synchronisation. Bref, des tas de choses peuvent aller mal, particulièrement si l'opérateur réseau est négligent (ce qui a été le cas de Free) mais même parfois s'il essaie de bien faire. À partir de là, si certaines machines du réseau local continuent à utiliser l'ancien préfixe, leurs paquets seront probablement jetés par le routeur et aucune communication ne sera possible.

Le RFC 4861 suggère, dans sa section 6.2.1, pour les durées de validité des préfixes annoncés, des valeurs très élevées (une semaine pour la durée préférée et un mois pour la durée maximale). En cas de renumérotation brutale, c'est beaucoup trop (voir aussi la section 2.2), une machine pourrait rester déconnectée pendant une semaine, voire davantage. Divers trucs permettent d'améliorer un peu les choses, mais avec d'autres inconvénients. Il n'y a pas de méthode propre pour nettoyer les machines du réseau local de la mauvaise information. Bien sûr, le mieux serait qu'il n'y ait pas de renumérotation brutale mais ne nous faisons pas d'illusions : cela arrivera et il faut des solutions pour réparer.

La section 2 du RFC analyse en détail certains aspects du problème. Ainsi, est-ce qu'on ne pourrait pas tout simplement attribuer des préfixes IP fixes aux clients ? Cela simplifierait certainement beaucoup de choses. Mais une étude récente indique qu'un tiers des FAI britanniques utilisent des préfixes dynamiques pour IPv6. C'est certainement pénible pour les clients qui veulent, par exemple, héberger un serveur. Mais c'est un état des choses qu'il va falloir traiter. Même si ces FAI changeaient leurs pratiques, des problèmes subsisteraient, par exemple avec des routeurs à la maison qui sous-alloueraient des préfixes plus spécifiques à une partie de la maison et le feraient de façon dynamique. D'autant plus que l'idéal serait que les clients aient le choix, les adresses fixes pouvant (RFC 7721) poser des problèmes de vie privée (le RFC 4941 ne fait varier que la partie de l'adresse IP spécifique à la machine). Ainsi, la DPA allemande suggère de ne pas les utiliser.

Le fond du problème est évidemment que le routeur qui émet des RA n'a aucun moyen de supprimer les préfixes anciens s'il ne les connait pas. S'ils les connaissait, cela serait trivial, en les annonçant avec une durée de vie de zéro mais attention, lisez plus loin. Dans les cas de redémarrage d'un routeur qui n'a pas de mémoire permanente, ce qui est oublié est oublié et le routeur ne peut pas dire « ce préfixe n'existe plus ». Mais même si le routeur connait les anciens préfixes, il y a un piège avec le point e) de la section 5.5.3 du RFC 4862 : on ne peut pas réduire la durée de vie en dessous de deux heures. C'est déjà mieux qu'une semaine mais c'est encore trop long. Autre piège, même une fois qu'un préfixe n'est plus utilisé pour de nouvelles communications, il peut encore servir pour répondre aux anciennes, donc est encore considéré comme valide pour ce réseau, ce qui fait qu'il n'est pas possible de communiquer avec le nouveau titulaire de ce préfixe (s'il y en a un).Par exemple ce patch Linux viole le RFC 4862 pour permettre une meilleure robustesse en cas de renumérotation.

Autre sujet sur lequel il faudrait peut-être améliorer les protocoles, l'interaction entre DHCP et SLAAC, qui reste peu spécifiée. Au minimum, le logiciel dans le routeur qui reçoit les préfixes par DHCP-PD devrait transmettre au logiciel qui envoie les RA quelles sont les durées de vie à utiliser pour qu'elles soient cohérentes avec la durée du bail DHCP.

Bon, et qu'est-ce qu'on peut faire aujourd'hui pour atténuer les conséquences du problème ? Comme dit plus haut, on pourrait n'utiliser que des préfixes fixes, mais cela ne semble pas réaliste. On pourrait surtout changer les paramètres SLAAC pour des valeurs plus réalistes, en mettant 45 minutes comme durée de vie préférée et 90 minutes comme durée maximale de validité. De telles valeurs conviendraient mieux à la grande majorité des réseaux que les valeurs du RFC 4861. En l'absence de mécanisme pour invalider rapidement les anciens préfixes, de courtes durées de vie sont préférables. Dans certains cas (si on est sûrs qu'il n'y aura pas de rénumérotation brutale et/ou si des machines sont longtemps déconnectées du réseau), des valeurs plus longues peuvent être préférables.

Dans le futur, des travaux sur les CPE (cf. RFC 7084) pourront améliorer les choses .


Téléchargez le RFC 8978


L'article seul

RFC 8977: Registration Data Access Protocol (RDAP) Query Parameters for Result Sorting and Paging

Date de publication du RFC : Janvier 2021
Auteur(s) du RFC : M. Loffredo (IIT-CNR/Registro.it), M. Martinelli (IIT-CNR/Registro.it), S. Hollenbeck (Verisign Labs)
Chemin des normes
Réalisé dans le cadre du groupe de travail IETF regext
Première rédaction de cet article le 24 janvier 2021


Le protocole RDAP, normalisé notamment dans les RFC 9082 et RFC 9083, permet de récupérer des informations structurées sur des trucs (oui, j'ai écrit « trucs ») enregistrés auprès d'un registre, par exemple des domaines auprès d'un registre de noms de domaine. Voici un RFC tout juste publié qui ajoute à RDAP la possibilité de trier les résultats et également de les afficher progressivement (paging). C'est évidemment surtout utile pour les requêtes de type « recherche », qui peuvent ramener beaucoup de résultats.

Avec des requêtes « exactes » (lookup dans le RFC 9082), le problème est moins grave. Ici, je cherche juste de l'information sur un nom de domaine et un seul :

% curl https://rdap.nic.bzh/domain/chouchen.bzh
...
   "events" : [
      {
         "eventDate" : "2017-07-12T10:18:12Z",
         "eventAction" : "registration"
      },
      {
         "eventDate" : "2020-07-09T09:49:06Z",
         "eventAction" : "last changed"
      },
...
  

Mais si je me lançais dans une recherche plus ouverte (search dit le RFC 9082), avec par exemple la requête domains (notez le S à la fin, cf. RFC 9082, section 3.2.1), le nombre de résultats pourrait être énorme. Par exemple, si je demandais tous les domaines en .bzh avec https://rdap.nic.bzh/rdap/domains?name=*.bzh, j'aurais une réponse d'une taille conséquente. (Et je ne vous dis pas pour .com…)

En pratique, cette requête ne fonctionnera pas car je ne connais aucun registre qui autorise les recherches RDAP aux utilisateurs anonymes. Ceux-ci ne peuvent faire que des requêtes exactes, à la fois pour épargner les ressources informatiques (cf. la section 7 du RFC sur la charge qu'impose les recherches), et pour éviter de distribuer trop d'informations à des inconnus pas toujours bien intentionnés. Pour effectuer ces recherches, il faut donc un compte et une autorisation. Autrement, vous récupérez un code 401, 403 ou bien carrément une liste vide.

Et si vous avez une telle autorisation, comment gérer une masse importante de résultats ? Avec le RDAP originel, vous récupérez la totalité des réponses, que vous devrez analyser, et ce sera à vous, client, de trier. (Si le serveur n'envoie qu'une partie des réponses, pour épargner le client et ses propres ressources, il n'a malheureusement aucun moyen de faire savoir au client qu'il a tronqué la liste.) C'est tout le but de notre RFC que de faire cela côté serveur. Des nouveaux paramètres dans la requête RDAP vont permettre de mieux contrôler les réponses, ce qui réduira les efforts du client RDAP, du serveur RDAP et du réseau, et permettra d'avoir des résultats plus pertinents.

La solution ? La section 2 de notre RFC décrit les nouveaux paramètres :

  • count : le client demande à être informé du nombre de trucs que contient la liste des réponses.
  • sort : le client demande à trier les résultats.
  • cursor : ce paramètre permet d'indiquer un endroit particulier de la liste (par exemple pour la récupérer progressivement, par itérations successives).

Par exemple, https://example.com/rdap/domains?name=example*.com&count=true va récupérer dans .com (si le registre de .com acceptait cette requête…) tous les noms de domaine dont le nom commence par example, et indiquer leur nombre. Une réponse serait, par exemple :

"paging_metadata": {
       "totalCount": 43
},
"domainSearchResults": [
       ...
]    
  

(paging_metadata est expliqué plus loin.)

Pour le paramètre sort, le client peut indiquer qu'il veut un tri, sur quel critère se fait le tri, et que celui-ci doit être dans l'ordre croissant (a pour ascending) ou décroissant (d pour descending). Ainsi, https://example.com/rdap/domains?name=*.com&sort=name demande tous les noms en .com triés par nom. https://example.com/rdap/domains?name=*.com&sort=registrationDate:d demanderait tous les noms triés par date d'enregistrement, les plus récents en premier.

L'ordre de tri dépend de la valeur JSON du résultat (comparaison lexicographique pour les chaînes de caractères et numérique pour les nombres), sauf pour les adresses IP, qui sont triées selon l'ordre des adresses et pour les dates qui sont triées dans l'ordre chronologique. Ainsi, l'adresse 9.1.1.1 est inférieure à 10.1.1.1 (ce qui n'est pas le cas dans l'ordre lexicographique). Le RFC fait remarquer que tous les SGBD sérieux ont des fonctions pour traiter ce cas. Ainsi, dans PostgreSQL, la comparaison des deux chaînes de caractères donnera :

=> SELECT '10.1.1.1' < '9.1.1.1';
 t
  

Alors que si les adresses IP sont mises dans une colonne ayant le type correct (INET), on a le bon résultat :

=> SELECT '10.1.1.1'::INET < '9.1.1.1'::INET;
 f
  

Après le sort=, on trouve le nom de la propriété sur laquelle on trie. C'est le nom d'un membre de l'objet JSON de la réponse. Non, en fait, c'est plus compliqué que cela. Certains membres de la réponse ne sont pas utilisables (comme roles, qui est multi-valué) et des informations importantes (comme registrationDate cité en exemple plus haut) ne sont pas explicitement dans la réponse. Notre RFC définit donc une liste de propriétés utilisables, et explique comment on les calcule (par exempe, registrationDate peut se déduire des events). Plutôt que ces noms de propriétés, on aurait tout pu faire en JSONpath ou JSON Pointer (RFC 6901) mais ces deux syntaxes sont complexes et longues ($.domainSearchResults[*].events[?(@.eventAction='registration')].eventDate est le JSONPath pour registrationDate). La mention en JSONPath du critère de tri est donc facultative.

Et, bien sûr, si le client envoie un nom de propriété qui n'existe pas, il récupérera une erreur HTTP 400 avec une explication en JSON :

{
       "errorCode": 400,
       "title": "Domain sorting property 'unknown' is not valid",
       "description": [
           "Supported domain sorting properties are:"
           "'aproperty', 'anotherproperty'"
       ]

}
  

Et le troisième paramètre, cursor ? Ce RFC fournit deux méthodes pour indiquer où on en est dans la liste des résultats, la pagination par décalage (offset pagination) et celle par clé (keyset pagination, qu'on trouve parfois citée sous le nom ambigu de cursor pagination, qui désigne plutôt une méthode avec état sur le serveur). Ces deux méthodes ont en commun de ne pas nécessiter d'état du côté du serveur. La pagination par décalage consiste à fournir un décalage depuis le début de la liste et un nombre d'éléments désiré, par exemple « donne-moi 3 éléments, commençant au numéro 10 ». Elle est simple à mettre en œuvre, par exemple avec SQL :

SELECT truc FROM Machins ORDER BY chose LIMIT 3 OFFSET 9;  
  

Mais elle n'est pas forcément robuste si la base est modifiée pendant ce temps : passer d'une page à l'autre peut faire rater des données si une insertion a eu lieu entretemps (cela dépend aussi de si la base est relue à chaque requête paginée) et elle peut être lente (surtout avec RDAP où la construction des réponses prend du temps, alors que celles situées avant le début de la page seront jetées). L'autre méthode est la pagination par clé où on indique une caractéristique du dernier objet vu. Si les données sont triées, il est facile de récupérer les N objets suivants. Par exemple en SQL :

SELECT truc FROM Machins WHERE chose > [la valeur] ORDER BY chose LIMIT 3;
  

Un inconvénient de cette méthode est qu'il faut un champ (ou un ensemble de champs) ayant un ordre (et pas de duplicata). RDAP rend cela plus difficile, en agrégeant des informations provenant de différentes tables (cf. l'annexe B du RFC). (Voir des descriptions de cette pagination par clé dans « Paginating Real-Time Data with Keyset Pagination » ou « Twitter Ads API », pour Twitter.) RDAP permet les deux méthodes, chacune ayant ses avantages et ses inconvénients. L'annexe B du RFC explique plus en détail ces méthodes et les choix faits. (Sinon, en dehors de RDAP, un bon article sur le choix d'une méthode de pagination, avec leur mise en œuvre dans PostgreSQL est « Five ways to paginate in Postgres, from the basic to the exotic ».) Pour RDAP, un https://example.com/rdap/domains?name=*.com&cursor=offset:9,limit:3 récupérerait une page de trois éléments, commençant au dixième, et https://example.com/rdap/domains?name=*.com&cursor=key:foobar.com trouverait les noms qui suivent foobar.com. Notez qu'en réalité, vous ne verrez pas directement le décalage, la clé et la taille de la page dans l'URL : ils sont encodés pour permettre d'utiliser des caractères quelconques (et aussi éviter que le client ne les bricole, il est censé suivre les liens, pas fabriquer les URL à la main). Les exemples du RFC utilisent Base64 pour l'encodage, en notant qu'on peut certainement faire mieux.

La capacité du serveur à mettre en œuvre le tri et la pagination s'indiquent dans le tableau rdapConformance avec les chaînes sorting et paging (qui sont désormais dans le registre IANA). Par exemple (pagination mais pas tri) :

"rdapConformance": [
           "rdap_level_0",
           "paging"
     ]
  

Il est recommandé que le serveur documente ces possibilités dans deux nouveaux éléments qui peuvent être présents dans une réponse, sorting_metadata et paging_metadata. Cela suit les principes d'auto-découverte de HATEOAS. Dans la description sorting_metadata, on a currentSort qui indique le critère de tri utilisé, et availableSorts qui indique les critères possibles. Chaque critère est indiqué avec un nom (property), le fait qu'il soit le critère par défaut ou pas, éventuellement une expression JSONPath désignant le champ de la réponse utilisé et enfin une série de liens (RFC 8288) qui vont nous indiquer les URL à utiliser. Pour paging_metadata, on a totalCount qui indique le nombre d'objets sélectionnés, pageSize qui indique le nombre récupérés à chaque itération, pageNumber qui dit à quelle page on en est et là encore les liens à suivre. Ce pourrait, par exemple, être :

"sorting_metadata": {
        "currentSort": "name",
        "availableSorts": [
          {
          "property": "registrationDate",
          "jsonPath": "$.domainSearchResults[*].events[?(@.eventAction==\"registration\")].eventDate",
          "default": false,
          "links": [
            {
            "value": "https://example.com/rdap/domains?name=example*.com&sort=name",
            "rel": "alternate",
            "href": "https://example.com/rdap/domains?name=example*.com&sort=registrationDate",
            "title": "Result Ascending Sort Link",
            "type": "application/rdap+json"
            },
            {
            "value": "https://example.com/rdap/domains?name=example*.com&sort=name",
            "rel": "alternate",
            "href": "https://example.com/rdap/domains?name=example*.com&sort=registrationDate:d",
            "title": "Result Descending Sort Link",
            "type": "application/rdap+json"
            }
          ]
	  ...
  

Ici, sorting_metadata nous indique que le tri se fera sur la base du nom, mais nous donne les URL à utiliser pour trier sur la date d'enregistrement. Quant à la pagination, voici un exemple de réponse partielle, avec les liens permettant de récupérer la suite :

"paging_metadata": {
       "totalCount": 73,
       "pageSize": 50,
       "pageNumber": 1,
       "links": [
         {
         "value": "https://example.com/rdap/domains?name=example*.com",
         "rel": "next",
         "href": "https://example.com/rdap/domains?name=example*.com&cursor=wJlCDLIl6KTWypN7T6vc6nWEmEYe99Hjf1XY1xmqV-M=",
         "title": "Result Pagination Link",
         "type": "application/rdap+json"
         }
       ]
  

L'idée de permettre le contrôle des réponses via des nouveaux paramètres (count, sort et cursor, présentés ci-dessus), vient entre autre du protocole OData. Une autre solution aurait été d'utiliser des en-têtes HTTP (RFC 7231). Mais ceux-ci ne sont pas contrôlables depuis le navigateur, ce qui aurait réduit le nombre de clients possibles. Et puis cela rend plus difficile l'auto-découverte des extensions, qui est plus pratique via des URL, cette auto-découverte étant en général considérée comme une excellente pratique REST.

Il existe à l'heure actuelle une seule mise en œuvre de ce RFC dans le RDAP non public de .it. La documentation est en ligne.

Notre RFC permet donc de ne récupérer qu'une partie des objets qui correspondent à la question posée. Si on veut plutôt récupérer une partie seulement de chaque objet, il faut utiliser le RFC 8982.


Téléchargez le RFC 8977


L'article seul

RFC 8976: Message Digest for DNS Zones

Date de publication du RFC : Février 2021
Auteur(s) du RFC : D. Wessels (Verisign), P. Barber (Verisign), M. Weinberg (Amazon), W. Kumari (Google), W. Hardaker (USC/ISI)
Chemin des normes
Réalisé dans le cadre du groupe de travail IETF dnsop
Première rédaction de cet article le 10 février 2021


Ce nouveau RFC normalise un mécanisme pour ajouter aux zones DNS un condensat qui permet de vérifier leur intégrité. Pourquoi, alors qu'on a TSIG, TLS et SSH pour les transporter, et PGP et DNSSEC pour les signer ? Lisez jusqu'au bout, vous allez voir.

Le nouveau type d'enregistrement DNS créé par ce RFC se nomme ZONEMD et sa valeur est un condensat de la zone. L'idée est de permettre au destinataire d'une zone DNS de s'assurer qu'elle n'a pas été modifiée en route. Elle est complémentaire des différentes techniques de sécurité citées plus haut. Contrairement à DNSSEC, elle protège une zone, pas un enregistrement.

Revenons un peu sur la terminologie. Une zone (RFC 8499, section 7) est un ensemble d'enregistrements servi par les mêmes serveurs. Ces serveurs reçoivent typiquement la zone depuis un maître, et utilisent souvent la technique AXFR (RFC 5936) pour cela. Ce transfert est souvent (mais pas toujours) protégé par le mécanisme TSIG (RFC 8945), qui permet de s'assurer que la zone est bien celle servie par le maître légitime. D'autres techniques de transfert peuvent être utilisées, avec leur propre mécanisme de sécurité. On peut par exemple utiliser rsync sur SSH. Les zones sont souvent stockées dans des fichiers de zone, du genre :

@ IN  SOA ns4.bortzmeyer.org. hostmaster.bortzmeyer.org. (
        2018110902
        7200
        3600
        604800
        43200 )

  IN  NS  ns4.bortzmeyer.org.
  IN  NS  ns2.bortzmeyer.org.
  IN  NS  ns1.bortzmeyer.org.
  IN  NS  puck.nether.net.

  IN MX 0 mail.bortzmeyer.org.
  IN TXT  "v=spf1 mx -all"

  IN  A 92.243.4.211
  IN AAAA 2001:4b98:dc0:41:216:3eff:fe27:3d3f
  
www  IN CNAME ayla.bortzmeyer.org.

sub IN NS toto.example.  
  

Donc, les motivations de notre RFC sont d'abord le désir de pouvoir vérifier une zone après son transfert, indépendamment de la façon dont elle a été transférée. Une zone candidate évidente est la racine du DNS (cf. section 1.4.1), publiquement disponible, et que plusieurs résolveurs copient chez eux (RFC 8806). Ensuite, l'idée est de disposer d'un contrôle minimum (une somme de contrôle) pour détecter les modifications accidentelles (le rayon cosmique qui passe). Un exemple de modification accidentelle serait une troncation de la fin de la zone, d'habitude difficile à détecter puisque les fichiers de zone n'ont pas de marques de fin (contrairement à, par exemple, un fichier JSON). Notez que la « vraie » vérification nécessite DNSSEC. Si la zone n'est pas signée, le mécanisme décrit dans ce RFC ne fournit que la somme de contrôle : c'est suffisant contre les accidents, pas contre les attaques délibérées. Mais, de toute façon, tous les gens sérieux utilisent DNSSEC, non ?

Il existe de nombreuses techniques qui fournissent un service qui ressemble plus ou moins à un des deux services mentionnés ci-dessus. L'un des plus utilisés est certainement TSIG (RFC 8945). Beaucoup de transferts de zone (cf. RFC 5936) sont ainsi protégés par TSIG. Il a l'inconvénient de nécessiter un secret partagé entre serveur maître et serveur esclave. SIG(0) (RFC 2931) n'a pas cet inconvénient mais n'est quasiment jamais mis en œuvre. Toujours dans la catégories des protections du canal, on a TLS et l'IETF travaille sur un mécanisme de protection des transferts par TLS, mais on peut aussi imaginer d'utiliser simplement DoT (RFC 7858).

Ceci dit, toutes ces techniques de protection du canal ont le même défaut : une fois le transfert fait, elles ne servent plus à rien. Pas moyen de vérifier si le fichier est bien le fichier authentique. Ainsi, un serveur faisant autorité qui charge une zone à partir d'un fichier sur le disque ne peut pas vérifier que ce fichier n'a pas été modifié. Bien sûr, les protections fournies par le système de fichiers offrent certaines garanties, mais pas parfaites, par exemple si un programme a planté et laissé le fichier dans un état invalide (oui, ça s'est déjà produit). Bref, on préférerait une protection des données et pas seulement du canal.

Alors, pourquoi ne pas simplement utiliser DNSSEC, qui fournit effectivement cette sécurité des données ? Le problème est que dans une zone, seules les informations faisant autorité sont signées. Les délégations (vers un sous-domaine, comme sub.bortzmeyer.org dans l'exemple plus haut) et les colles (adresses IP des serveurs de noms qui sont dans la zone qu'ils servent) ne sont pas signés. Pour une zone comme la racine, où il n'y a quasiment que des délégations, ce serait un problème. Et ce serait encore pire avec les zones qui utilisent NSEC3 (RFC 5155) avec opt-out puisqu'on ne sait même plus si un nom non-signé existe ou pas. Donc, DNSSEC est très bien, mais ne suffit pas pour notre cahier des charges.

On a d'autres outils pour la sécurité des données, le plus évident étant PGP (RFC 4880). D'ailleurs, ça tombe bien, la racine des noms de domaine est déjà distribuée avec une signature PGP :


% wget -q https://www.internic.net/domain/root.zone 
% wget -q https://www.internic.net/domain/root.zone.sig
% gpg --verify root.zone.sig root.zone     
gpg: Signature made Wed Jan 13 08:22:50 2021 CET
gpg:                using DSA key 937BB869E3A238C5
gpg: Good signature from "Registry Administrator <nstld@verisign-grs.com>" [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: F0CB 1A32 6BDF 3F3E FA3A  01FA 937B B869 E3A2 38C5

  

Notez que Verisign utilise une signature détachée du fichier. Cela permet que le fichier reste un fichier de zone normal et chargeable, mais cela casse le lien entre la signature et le fichier. Bref, cela ne résout pas complètement le problème. (Les historiens et historiennes s'amuseront de noter qu'une autre solution, très proche de celle de notre RFC, figurait déjà dans le RFC 2065, mais qu'elle n'avait eu aucun succès.)

Bon, passons maintenant à la solution (section 1.3). On l'a dit, le principe est d'avoir un enregistrement de type ZONEMD qui contient un condensat de la zone. Il est généré par le responsable de la zone et quiconque a téléchargé la zone, par quelque moyen que ce soit, peut vérifier qu'il correspond bien à la zone. En prime, si la zone est signée avec DNSSEC, on peut vérifier que ce ZONEMD est authentique.

Du fait que ce condensat couvre l'intégralité de la zone, il faut le recalculer entièrement si la zone change, si peu que ce soit. Cette solution ne convient donc pas aux grosses zones très dynamiques (comme .fr). Dans le futur, le ZONEMD est suffisamment extensible pour que des solutions à ce problème lui soient ajoutées. En attendant, ZONEMD convient mieux pour des zones comme la racine (rappel, elle est disponible en ligne), ou comme la zone d'une organisation. La racine est un cas particulièrement intéressant car elle est servie par un grand nombre de serveurs, gérés par des organisations différentes. Sans compter les gens qui la récupèrement localement (RFC 8806). Le contrôle de son intégrité est donc crucial. Il y a une discussion en ce moment au sein du RZERC pour proposer l'ajout de ZONEMD dans la racine.

Pour la zone d'une organisation, notre RFC rappelle qu'il est recommandé d'avoir une diversité des serveurs de nom, afin d'éviter les SPOF et que c'est donc une bonne idée d'avoir des serveurs esclaves dans d'autres organisations. Comme cette diversité peut entrainer des risques (le serveur esclave est-il vraiment honnête ? le transfert s'est-il bien passé ?), là aussi, la vérification de l'intégrité s'impose. Autre scénario d'usage, les distributions de fichiers de zone, comme le fait l'ICANN avec CZDS ou comme le fait le registre du .ch (téléchargez ici). D'une manière générale, ce contrôle supplémentaire ne peut pas faire de mal.

Plongeons maintenant dans les détails techniques avec la section 2 du RFC, qui explique le type d'enregistrement ZONEMD (code 63). Il se trouve forcément à l'apex de la zone. Il comprend quatre champs :

  • Numéro de série, il permet de s'assurer qu'il couvre bien la zone qu'on veut vérifier (dont le numéro de série est dans le SOA).
  • Plan, la méthode utilisée pour génerer le condensat. Pour l'instant, un seul plan est normalisé, nommé Simple, il fonctionne par un calcul sur l'entièreté de la zone. On peut ajouter des plans au registre, selon la procédure « Spécification nécessaire ».
  • Algorithme, est l'algorithme de condensation utilisé, SHA-384 ou SHA-512 à l'heure actuelle mais on pourra en ajouter d'autres au registre (« Spécification nécessaire », là encore).
  • Et le condensat lui-même.

Question présentation dans les fichiers de zone, ça ressemble à :

example.com. 86400 IN ZONEMD 2018031500 1 1 (
       FEBE3D4CE2EC2FFA4BA99D46CD69D6D29711E55217057BEE
       7EB1A7B641A47BA7FED2DD5B97AE499FAFA4F22C6BD647DE )
  

Ici, le numéro de série est 2018031500, le plan 1 (Simple) et l'algorithme de condensation SHA-384.

Il peut y avoir plusieurs ZONEMD dans un fichier, ils doivent avoir des couples {Plan, Algorithme} différents. (Le but est de fournir plusieurs techniques de vérification, et de permettre de passer en souplesse de l'une à l'autre.)

Comment exactement se fait le calcul du condensat ? La section 3 le détaille. D'abord, on supprime de la zone les éventuels ZONEMD existants, et on place un ZONEMD bidon (il en faut un, en cas de signature DNSSEC, pour éviter de casser la signature). Si on a DNSSEC, on signe alors la zone (et il faudra re-faire le RRSIG du ZONEMD après), on canonicalise la zone (le condensat est calculé sur le format « sur le câble » pas sur le format texte), et on calcule ensuite le condensat sur la concaténation des enregistrements (à l'exclusion du ZONEMD bidon, qui avait été mis pour DNSSEC, et de sa signature). On génère ensuite le vrai ZONEMD, puis, si on a DNSSEC, on recalcule sa signature. Autant dire que vous ne le ferez pas à la main ! L'endroit logique pour faire ce calcul du ZONEMD est dans le logiciel qui fait les signatures DNSSEC. Notez que, contrairement à DNSSEC, tous les enregistrements sont utilisés, y compris ceux qui ne font pas autorité comme les délégations et les colles.

Un mot sur la canonicalisation. Tout calcul d'un condensat cryptographique doit se faire sur des données canonicalisées, c'est-à-dire mises sous une forme canonique, une forme unique et standardisée. Comme le changement d'un seul bit change complètement le condensat, il ne faut pas laisser des petites variations de détail (par exemple, pour le DNS, entre noms comprimés et non-comprimés) à l'imagination de chaque programmeur. Voilà pourquoi notre RFC spécifie rigoureusement une forme canonique (par exemple, les noms ne doivent pas être comprimés).

La vérification se fait de la même façon (section 4 du RFC). On vérifie que le numéro de série est le même, on canonicalise, on concatène, on condense et on vérifie qu'on trouve bien ce qui était dans le ZONEMD. (S'il y a plusieurs ZONEMD, le premier qui marche suffit.) Le mieux est évidemment de tout valider avec DNSSEC, en plus.

La section 6 de notre RFC revient sur la sécurité du ZONEMD, ce qu'il garantit et ce qu'il ne garantit pas. D'abord, le point le plus important : sans DNSSEC, ZONEMD n'est qu'une somme de contrôle, il garantit l'intégrité mais pas l'authenticité, et il ne protège donc que contre les modifications accidentelles (ce qui est déjà très bien !) Le bit flipping sera détecté mais pas l'attaque délibérée et soignée. Ainsi, sans DNSSEC, un attaquant n'aurait, par exemple, qu'à retirer l'enregistrement ZONEMD de la zone avant de faire ses modifications (une attaque par repli).

D'autre part, les algorithmes de cryptographie vieillissent (RFC 7696) et ZONEMD ne peut donc pas garantir de sécurité sur le long terme. D'ailleurs, sur le long terme, on n'aurait probablement plus les clés DNSSEC disponibles.

Plus drôle, ZONEMD permet, comme toute technique de sécurité, de nouveaux modes de déni de service, comme l'a déjà expérimenté tout utilisateur de la cryptographie. Comme le condensat est strictement binaire (il colle aux données, ou pas du tout), la zone entière peut être considérée comme invalide si un problème modifie ne serait-ce qu'un seul bit. Le souci de l'intégrité de la zone et celui de sa disponibilité sont ici en conflit, et le responsable de la zone doit choisir.

Si vous aimez les comparatifs de performance, la section 7 du RFC discute de chiffres. Par exemple, une zone de 300 000 enregistrements a été ZONEMDifiée en deux secondes et demi sur un serveur ordinaire. C'est encore trop lent pour être utilisable sur des zones comme .com mais cela montre que des grandes zones qui ne changent pas trop souvent peuvent être protégées.

Et question mises en œuvre, ça se passe comment ? J'ai testé l'excellente bibliothèque ldns. Le code avec calcul de ZONEMD devrait être publié dans une version officielle en 2021 mais, en attendant, c'est déjà dans git, en https://github.com/NLnetLabs/ldns. Une fois compilée, on peut fabriquer le ZONEMD (ici, avec la zone d'exemple montrée au début de cet article). On génère une clé, puis on signe, avec calcul de ZONEMD :

%  ./ldns-keygen -a ED25519 bortzmeyer.org
Kbortzmeyer.org.+015+00990

% ./ldns-signzone -o bortzmeyer.org -z 1:1  bortzmeyer-org.zone  Kbortzmeyer.org.+015+00990

% cat bortzmeyer-org.zone.signed
...
bortzmeyer.org.	3600	IN	ZONEMD	2018110902 1 1 b1af60c3c3e88502746cf831d2c64a5399c1e3c951136d79e63db2ea99898ba9f2c4f9b29e6208c09640aaa9deafe012
bortzmeyer.org.	3600	IN	RRSIG	ZONEMD 15 2 3600 20210215174229 20210118174229 990 bortzmeyer.org. FJ98//KDBF/iMl1bBkjGEBT3eeuH/rJDoGRDAoCBEocudLbA7F/38qwLpsPkr7oQcgbkLzhyHA7BXG/5fAwkAg==

(L'argument 1:1 signifie plan Simple et algorithme SHA-384.) On peut ensuite vérifier la zone. Si elle a un enregistrement ZONEMD, il est inclus dans la vérification :

% ./ldns-verify-zone  bortzmeyer-org.zone.signed
Zone is verified and complete             
   

Si je modifie une délégation (qui n'est pas protégée par DNSSEC mais qui l'est par ZONEMD) :

% ./ldns-verify-zone  bortzmeyer-org.zone.signed 
There were errors in the zone
  

Il y a aussi les dns-tools des gens du .cl. Pour BIND, c'est en cours de réflexion. Apparemment, Unbound y travaille également.


Téléchargez le RFC 8976


L'article seul

RFC 8975: Network Coding for Satellite Systems

Date de publication du RFC : Janvier 2021
Auteur(s) du RFC : N. Kuhn (CNES), E. Lochin (ENAC)
Pour information
Réalisé dans le cadre du groupe de recherche IRTF nwcrg
Première rédaction de cet article le 23 janvier 2021


Prenons de la hauteur (c'est le cas de le dire). Les communications via satellite posent des tas de problèmes techniques. L'envoi de chaque bit coûte cher, et dans certains cas d'usage, les pertes de paquets sont nombreuses, et on cherche donc à optimiser. Ce RFC explique comment on peut utiliser le network coding pour améliorer les performances de ces liaisons. Mais c'est encore un travail en cours.

Le network coding est un mécanisme d'encodage des données qui combine différentes données dans une même chaîne de bits, avant de les re-séparer à l'autre bout. Dans le cas le plus trivial on fait un XOR des données. Mais il y a d'autres moyens comme la convolution. Le network coding n'est pas un algorithme unique mais une classe de méthodes. Ces méthodes permettent de faire voyager davantage de données sur un même canal et/ou de diminuer le délai de transmission total, en ajoutant de la redondance. Le nom de network coding venait de l'encodage réalisé dans le réseau, pour tenir compte de sa topologie. Mais on peut utiliser des techniques du même genre de bout en bout (c'est le cas de FEC, dans le RFC 5052). (Je vous laisse en apprendre davantage sur Wikipédia). La question de l'encodage des données étant complexe, je vous renvoie également au RFC 8406 qui décrit le travail du groupe Network Coding de l'IRTF et définit les notions importantes.

Une idée de la latence dans une liaison satellite ? Le RFC observe que le RTT est typiquement de 0,7 secondes (deux passages par le satellite, la limite de la vitesse de la lumière, et le temps de traitement).

On peut se servir des satellites pour des tas de choses dans le domaine des télécommunications mais ce RFC se focalise sur l'accès à l'Internet par satellite, tel que normalisé par l'ETSI dans « Digital Video Broadcasting (DVB); Second Generation DVB Interactive Satellite System (DVB-RCS2); Part 2: Lower Layers for Satellite standard ». (Le RFC recommande également l'article de Ahmed, T., Dubois, E., Dupe, JB., Ferrus, R., Gelard, P., et N. Kuhn, « Software-defined satellite cloud RAN ».)

Le RFC décrit des scénarios où le network coding permet de gagner en capacité et en latence effective, en encodant plus efficacement les communications. Par exemple, en combinant le trafic de deux utilisateurs, on obtient parfois une quantité de données à transmettre qui est inférieure à celle de deux transmissions séparées, économisant ainsi de la capacité. Le RFC cite également l'exemple du multicast où, lorsqu'un des destinataires n'a pas reçu un paquet, on peut, pour éviter de faire attendre tout le monde, utiliser le network coding pour ré-envoyer les données en même temps que la suite du transfert. Là encore, la combinaison de toutes les données fait moins d'octets que lors de transmissions séparées. Cela pourrait s'adapter à des protocoles multicast comme NORM (RFC 5740) ou FLUTE (RFC 6726).

Le network coding permet également d'ajouter de la redondance aux données, sinon gratuitement, du moins à un « coût » raisonnable. Cela peut servir pour les cas de pertes de données comme le précédent. Un autre cas de perte est celui où la liaison satellite marche bien mais c'est tout près de l'utilisateur, par exemple dans son WiFi, que les paquets se perdent. Vu la latence très élevée des liaisons satellite, réémettre a des conséquences très désagréables sur la capacité effective (et sur le délai total). Une solution traditionnellement utilisée était le PEP (qui pose d'ailleurs souvent des problèmes de neutralité). Vous pouvez voir ici un exemple où le résultat est plutôt bon. Mais avec le chiffrement systématique, ces PEP deviennent moins efficaces. Cela redonne toute son importance au network coding.

Enfin, des pertes de paquets, avec leurs conséquences sur la latence car il faudra réémettre, se produisent également quand un équipement terminal passe d'une station de base à l'autre. Là encore, le network coding peut permettre d'éviter de bloquer le canal pendant la réémission.

Tout cela ne veut pas dire que le network coding peut être déployé immédiatement partout et va donner des résultats mirifiques. La section 4 du RFC décrit les défis qui restent à surmonter. Par exemple, les PEP (RFC 3135) ont toujours un rôle à jouer dans la communication satellitaire mais comment combiner leur rôle dans la lutte contre la congestion avec le network coding ? Faut-il réaliser cet encodage dans le PEP ? D'autre part, le network coding n'est pas complètement gratuit. Comme tout ajout de redondance, il va certes permettre de rattrapper certaines pertes de paquet, mais il occupe une partie du réseau. Et dans quelle couche ajouter cette fonction ? (Les couches hautes ont l'avantage de fenêtres - le nombre d'octets en transit - plus grandes, alors que les couches basses ne voient pas plus loin que le bout du paquet.)

Outre l'utilisation de satelittes pour la connectivité Internet des Terriens, il y a aussi une utilisation des communications spatiales pour échanger avec des vaisseaux lointains. La latence très élevée et l'indisponibilité fréquente de la liaison posent des défis particuliers. C'est ce qu'on nomme le DTN (Delay/Disruption Tolerant Network, décrit dans le RFC 4838). Le network coding peut aussi être utilisé ici, comme présenté dans l'article de Thai, T., Chaganti, V., Lochin, E., Lacan, J., Dubois, E., et P. Gelard, «  Enabling E2E reliable communications with adaptive re-encoding over delay tolerant networks ».

Et, comme le rappelle la section 9 du RFC, il faut aussi tenir compte du chiffrement, qui est évidemment obligatoire puisque les liaisons avec les satellites peuvent trop facilement être écoutées.

Merci à Nicolas Kuhn et Emmanuel Lochin pour leur relecture attentive, sans laquelle il y aurait bien plus d'erreurs. (Je me suis aventuré très en dehors de mon domaine de compétence.) Les erreurs qui restent sont évidemment de mon fait.


Téléchargez le RFC 8975


L'article seul

RFC 8973: DDoS Open Threat Signaling (DOTS) Agent Discovery

Date de publication du RFC : Janvier 2021
Auteur(s) du RFC : M. Boucadair (Orange), T. Reddy (McAfee)
Chemin des normes
Réalisé dans le cadre du groupe de travail IETF dots
Première rédaction de cet article le 13 janvier 2021


Le protocole DOTS, normalisé dans les RFC 8811, RFC 8782 et RFC 8783, sert à coordonner la réponse à une attaque par déni de service, entre la victime de l'attaque (le client DOTS) et un service d'atténuation de l'attaque (le serveur DOTS). Mais comment le client trouve-t-il son serveur ? Il peut y avoir une configuration manuelle, mais ce RFC propose aussi des moyens automatiques, basés sur un choix de plusieurs techniques, dont DHCP et NAPTR.

Attention, cela permet de trouver le serveur, mais pas le fournisseur du service d'atténuation. Car il faut un accord (souvent payant) avec ce fournisseur, et un échange de mécanismes d'authentification. Cette partie doit se faire manuellement. Le protocole de notre RFC prend ensuite le relais.

Notez aussi qu'il n'y a pas un seul moyen de découverte du serveur. La section 3 du RFC explique en effet que, vu la variété des cas d'utilisation de DOTS, on ne peut pas s'en tirer avec un seul mécanisme. Parfois le client a un CPE géré par le FAI, sur lequel on peut s'appuyer pour trouver le serveur DOTS, et parfois pas. Parfois, il faudra utiliser les résolveurs DNS d'un opérateur et parfois ce ne sera pas nécessaire. Parfois l'atténuateur est le FAI et parfois pas. Bref, il faut plusieurs solutions.

Voyons d'abord la procédure générale (section 4). Personnellement, je pense que le client DOTS doit donner la priorité aux configurations manuelles (DOTS est un système de sécurité, un strict contrôle de ce qui se passe est préférable). Mais le RFC ne décrit pas les choses ainsi. Il expose trois mécanismes, le premier, qualifié de configuration explicite, étant composé de deux techniques très différentes, la configuration manuelle ou bien DHCP. À noter au passage que la configuration manuelle peut indiquer le nom ou l'adresse IP mais, si elle indique l'adresse IP, le nom sera quand même obligatoire car il servira pour la vérification du certificat.

L'ordre des préférences entre ces mécanismes est imposé, pour que le résultat de la découverte soit prédictible. D'abord l'explicite (manuel ou DHCP, section 5), puis la résolution de service (section 6) puis la découverte de service (section 7).

Première technique automatique à utiliser, DHCP (section 5). Ce protocole va être utilisé pour récupérer le nom du serveur DOTS (le nom et pas seulement l'adresse IP car on en aura besoin pour authentifier la session TLS). Avec DHCPv6 (RFC 8415), l'option DHCP pour récupérer le nom est 141 en IPv6 et 147 pour IPv4. Une autre option permet de récupérer les adresses IP du serveur DOTS.

Deuxième technique, la résolution de service. Il faut partir d'un nom, qui peut être configuré manuellement ou bien obtenu par DHCP. Ce n'est pas le nom du serveur DOTS, contrairement au cas en DHCP pur, mais celui du domaine dans lequel le client DOTS se « trouve ». On va alors utiliser S-NAPTR (RFC 3958) sur ce nom, pour obtenir les noms des serveurs. L'étiquette à utiliser est DOTS pour le service (enregistré à l'IANA) et signal (RFC 8782) ou data (RFC 8783) pour le protocole (également à l'IANA). Par exemple si le client DOTS est dans le domaine example.net, il va faire une requête DNS de type NAPTR. Si le domaine comportait un enregistrement pour le service DOTS, il est choisi, et on continue le processus (compliqué !) de NAPTR ensuite. Si on cherche le serveur pour le protocole de signalisation, on pourrait avoir successivement quatre requêtes DNS :

example.net.   IN NAPTR 100 10 "" DOTS:signal.udp "" signal.example.net.

signal.example.net. IN NAPTR 100 10 "s" DOTS:signal.udp "" _dots-signal._udp.example.net.

_dots-signal._udp.example.net.  IN SRV   0 0 5000 a.example.net.

a.example.net.   IN AAAA  2001:db8::1    
  

Troisième technique, la découverte de service (DNS-SD) du RFC 6763. On cherche alors un enregistrement de type PTR dans _dots-signal.udp.example.net. Il nous donnera un nom pour lequel on fera une requête SRV.

DOTS servant en cas d'attaque, il faut prévoir la possibilité que l'attaquant tente de perturber ou de détourner ce mécanisme de découverte du serveur. Par exemple, on sait que DHCP n'est pas spécialement sécurisé (euphémisme !). D'autre part, DOTS impose TLS, il faut donc un nom à vérifier dans le certificat (oui, on peut mettre des adresses IP dans les certificats mais c'est rare). Quant aux techniques reposant sur le DNS, le RFC conseille d'utiliser DNSSEC, ce qui semble la moindre des choses pour une technique de sécurité. Il suggère également de faire la résolution DNS via un canal sûr par exemple avec DoT (RFC 7858) ou DoH (RFC 8484).

Apparemment, il existe au moins une mise en œuvre de DOTS qui inclut les procédures de découverte de notre RFC, mais j'ignore laquelle.


Téléchargez le RFC 8973


L'article seul

RFC 8972: Simple Two-Way Active Measurement Protocol Optional Extensions

Date de publication du RFC : Janvier 2021
Auteur(s) du RFC : G. Mirsky, X. Min (ZTE Corp), H. Nydell (Accedian Networks), R. Foote (Nokia), A. Masputra (Apple), E. Ruffini (OutSys)
Chemin des normes
Réalisé dans le cadre du groupe de travail IETF ippm
Première rédaction de cet article le 22 janvier 2021


Le protocole STAMP, normalisé dans le RFC 8762, est un protocole pour piloter des mesures de performance vers un réflecteur qui renverra les paquets de test. Ce nouveau RFC étend STAMP pour ajouter un identificateur de session, et la possibilité d'ajouter des tas d'options sous forme de TLV.

Commençons par l'identificateur de session. Une session STAMP est normalement identifiée par le tuple classique {protocole (forcément UDP), adresse IP source, adresse IP destination, port source, port destination}. Ce n'est pas toujours pratique donc notre RFC ajoute un identificateur explicite, le SSID (STAMP Session Identifier). Il fait deux octets et est choisi par l'envoyeur. Mais où le mettre dans le paquet ? Le format original prévoyait qu'une partie du paquet STAMP soit composée de zéros, et c'est dans cette partie qu'on logera le SSID. Donc, si, après l'estimation de l'erreur de mesure, on trouve deux octets qui ne sont pas nuls, c'est le SSID.

La section 4 liste les TLV qui peuvent désormais être ajoutés aux paquets STAMP. Ces TLV sont situés à la fin du paquet STAMP. Ils commencent par une série de bits qui indiquent si le réflecteur connait le TLV en question, et si le réflecteur a détecté des problèmes par exemple un TLV à la syntaxe incorrecte (cf. le registre IANA). Les principaux TLV sont :

  • Le type 1 permet de faire du remplissage, pour créer des paquets de la taille souhaitée,
  • Le type 2 demande au réflecteur des informations sur son adresse MAC et autres identificateurs,
  • Le type 3 demande une estampille temporelle (avec en prime indication de la méthode de synchronisation d'horloge utilisée, par exemple NTP),
  • Le type 8 permet de vérifier l'intégrité du paquet par HMAC (voir la section 6 du RFC pour les détails),
  • Etc.

Si cela ne suffit pas, d'autres types de TLV pourront être créés dans le futur et mis dans le registre IANA. La politique d'enregistrement (cf. RFC 8126) est « examen par l'IETF » pour la première moitié de la plage disponible et « premier arrivé, premier servi » ensuite.


Téléchargez le RFC 8972


L'article seul

RFC 8970: IMAP4 Extension: Message Preview Generation

Date de publication du RFC : Décembre 2020
Auteur(s) du RFC : M. Slusarz (Open-Xchange)
Chemin des normes
Réalisé dans le cadre du groupe de travail IETF extra
Première rédaction de cet article le 19 décembre 2020


Le protocole IMAP d'accès aux boites aux lettres (RFC 3501) continue à évoluer et reçoit de nouvelles extensions. Celle normalisée dans ce RFC permet au client IMAP, le MUA, de ne récupérer qu'une partie du message, afin d'afficher un avant-gout de celui-ci à l'utilisateur humain, qui pourra ainsi mieux choisir s'il veut lire ce message ou pas.

Il y a déjà des clients de messagerie qui présentent le début du message mais, sans l'extension de ce RFC, cela nécessite de tout récupérer (un FETCH BODYSTRUCTURE pour savoir quelle partie MIME récupérer, suivi d'un FETCH BODY, et sans possibilité de les exécuter en parallèle) avant de ne sélectionner qu'une partie. Au contraire, avec l'extension PREVIEW (« avant-gout »), c'est le serveur IMAP qui va sélectionner cet avant-gout. Avantages : une présélection qui est identique sur tous les clients, moins de travail pour le client et surtout moins de données transmises. Avant, le client était forcé de récupérer beaucoup de choses car il ne pouvait pas savoir à l'avance combien d'octets récolter avant de générer l'avant-gout. Si le message était du texte brut, OK, mais si c'était de l'HTML, il pouvait être nécessaire de ramasser beaucoup d'octets de formatage et de gadgets avant d'en arriver au vrai contenu. (Ou bien, il fallait procéder progressivement, récupérant une partie du message, puis, si nécessaire, une autre, ce qui augmentait la latence.)

Donc, concrètement, comment ça se passe ? La section 3 de notre RFC décrit l'extension en détail. Elle a la forme d'un attribut PREVIEW qui suit la commande FETCH (RFC 3501, section 6.4.5). Voici un exemple, la commande étant étiquetée MYTAG01 :

Client :    
MYTAG01 FETCH 1 (PREVIEW)

Serveur :
* 1 FETCH (PREVIEW "Bonjour, voulez-vous gagner plein d'argent rapidement ?")
MYTAG01 OK FETCH complete.
  

Idéalement, le serveur renvoie toujours le même avant-gout pour un message donné (mais le RFC ne l'impose pas car cela peut être difficile dans certains cas, par exemple en cas de mise à jour du logiciel du serveur, qui change l'algorithme de génération des avant-gouts).

La syntaxe formelle de l'attribut PREVIEW est en section 6 du RFC.

Le format de l'avant-gout est forcément du texte brut, encodé en UTF-8, et ne doit pas avoir subi d'encodages baroques comme Quoted-Printable. Sa longueur est limitée à 256 caractères (caractères Unicode, pas octets, attention si vous programmez un client et que votre tampon est trop petit).

Le contenu de l'avant-gout est typiquement composé des premiers caractères du message. Cela implique qu'il peut contenir des informations privées et il ne doit donc être montré qu'aux clients qui sont autorisés à voir le message complet.

Parfois, le serveur ne peut pas générer un avant-gout, par exemple si le message est chiffré avec OpenPGP (RFC 4880) ou bien si le message est entièrement binaire, par exemple du PNG. Dans ces cas, le serveur est autorisé à renvoyer une chaîne de caractères vide.

Si le serveur génère un avant-gout lui-même (du genre « Image de 600x600 pixels, prise le 18 décembre 2020 », en utilisant les métadonnées de l'image), il est recommandé qu'il choisisse la langue indiquée par l'extension LANGUAGE (RFC 5255).

Comme l'avant-gout n'est pas forcément indispensable pour l'utilisateur, le RFC suggère (section 4) de le charger en arrière-plan, en affichant la liste des messages sans attendre tous ces avant-gouts.

Le serveur IMAP qui sait générer ces avant-gouts l'annonce via la capacité PREVIEW, qui est notée dans le registre des capacités. Voici un exemple :

Client :
MYTAG01 CAPABILITY

Serveur :
* CAPABILITY IMAP4rev1 PREVIEW
MYTAG01 OK Capability command completed.

Client :
MYTAG02 FETCH 1 (RFC822.SIZE PREVIEW)

Serveur :
* 1 FETCH (RFC822.SIZE 5647 PREVIEW {200}
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
Curabitur aliquam turpis et ante dictum, et pulvinar dui congue.
ligula nullam
)
MYTAG02 OK FETCH complete. 
  

Attention si vous mettez en œuvre cette extension, elle nécessite davantage de travail du serveur, donc un client méchant pourrait surcharger ledit serveur. Veillez bien à authentifier les clients, pour retrouver le méchant (section 7 du RFC).

Cette extension est déjà mise en œuvre dans Dovecot et Cyrus.


Téléchargez le RFC 8970


L'article seul

RFC 8968: Babel Routing Protocol over Datagram Transport Layer Security

Date de publication du RFC : Janvier 2021
Auteur(s) du RFC : A. Decimo (IRIF, University of Paris-Diderot), D. Schinazi (Google LLC), J. Chroboczek (IRIF, University of Paris-Diderot)
Chemin des normes
Réalisé dans le cadre du groupe de travail IETF babel
Première rédaction de cet article le 12 janvier 2021


Le protocole de routage Babel, normalisé dans le RFC 8966, n'offre par défaut aucune sécurité. Notamment, il n'y a aucun moyen d'authentifier un routeur voisin, ni de s'assurer que les messages suivants viennent bien de lui. Sans même parler de la confidentialité de ces messages. Si on veut ces services, ce RFC fournit un moyen : sécuriser les paquets Babel avec DTLS.

La sécurité est évidemment souhaitable pour un protocole de routage. Sans elle, par exemple, une méchante machine pourrait détourner le trafic. Cela ne veut pas dire qu'on va toujours employer une solution de sécurité. Babel (RFC 8966) est conçu entre autres pour des réseaux peu ou pas gérés, par exemple un événement temporaire où chacun apporte son PC et où la connectivité se fait de manière ad hoc. Dans un tel contexte, déployer de la sécurité ne serait pas facile. Mais, si on y tient, Babel fournit désormais deux mécanismes de sécurité (cf. RFC 8966, section 6), l'un, plutôt simple et ne fournissant pas de confidentialité, est décrit dans le RFC 8967, l'autre, bien plus complet, figure dans notre RFC et repose sur DTLS (qui est normalisé dans le RFC 6347). DTLS, version datagramme de TLS, fournit authentification, intégrité et confidentialité.

L'adaptation de DTLS à Babel est décrite dans la section 2 du RFC. Parmi les problèmes, le fait que DTLS soit très asymétrique (client-serveur) alors que Babel est pair-à-pair, et le fait que DTLS ne marche qu'en unicast alors que Babel peut utiliser unicast ou multicast. Donc, chaque routeur Babel doit être un serveur DTLS, écoutant sur le port 6699 (qui n'est pas le 6696 du Babel non sécurisé). Le routeur Babel continue à écouter sur le port non chiffré, et, lorsqu'il entend un nouveau routeur s'annoncer en multicast, il va tenter de communiquer en DTLS avec lui. Puisque DTLS, comme TLS, est client-serveur, il faut décider de qui sera le client et qui sera le serveur : le routeur de plus faible adresse sera le client (par exemple, fe80::1:2 est inférieure à fe80::2:1. Le serveur différencie les sessions DTLS par le port source ou par les connection identifiers du draft draft-ietf-tls-dtls-connection-id. Comme avec le Babel non chiffré, les adresses IPv6 utilisées doivent être des adresses locales au lien. Client et serveur doivent s'authentifier, en suivant les méthodes TLS habituelles (chacun doit donc avoir un certificat).

Une fois que la session DTLS est en marche, les paquets Babel unicast passent sur cette session et sont donc intégralement (du premier au dernier octet) protégés par le chiffrement. Pour que la sécurité fournie par DTLS soit ne soit pas compromise, le routeur ne doit plus envoyer à son voisin de paquets sur le port non chiffré, à part des paquets Hello qui sont transmis en multicast (pour lequel DTLS ne marche pas) et qui permettent de découvrir d'éventuels nouveaux voisins (la règle exacte est un peu plus compliquée, regardez le RFC). On ne peut donc plus envoyer d'information en multicast (à part les Hello). En réception, un nœud Babel sécurisé doit ignorer les paquets non protégés par DTLS, sauf l'information de type Hello.

Une stricte sécurité nécessite qu'un routeur Babel sécurisé n'accepte plus de voisins qui ne gèrent pas DTLS, et ignore donc tous leurs paquets sauf ceux qui contiennent un TLV Hello. Autrement, les communications non sécurisées ficheraient en l'air la confidentialité de l'information. Si on veut une sécurité plus relaxée, un nœud peut faire tourner les deux versions du protocole, sécurisée et non sécurisée, mais alors, exige le RFC, uniquement sur des interfaces réseau différentes.

Comme toutes les fois où on rajoute des données, on risque de dépasser la MTU. La section 3 du RFC nous rappelle qu'il faut éviter d'envoyer des paquets qui seront fragmentés et donc qu'il faut éviter de fabriquer des paquets qui, après chiffrement, seraient plus gros que la MTU.

Rien n'est parfait en ce bas monde et l'ajout de DTLS, s'il résout quelques problèmes de sécurité, ne fait pas tout, et même parfois ajoute des ennuis. La section 5 du RFC rappelle ainsi qu'un méchant pourrait tenter des négociations DTLS nombreuses avec une machine afin de la faire travailler pour rien (une variante de Slowloris). Limiter le nombre de connexions par client n'aiderait pas puisqu'il pourrait toujours mentir sur son adresse IP.

Même en faisant tourner Babel sur DTLS, les messages de type Hello peuvent toujours être envoyés sans protection, et donc manipulés à loisir. Un routeur Babel doit donc toujours être capable d'utiliser des Hello unicast et protégés.

Et puis bien sûr, limitation classique de TLS, authentifier un routeur voisin et sécuriser la communication avec lui ne signifie pas que ce voisin est honnête ; il peut toujours mentir, par exemple en annonçant des routes vers des préfixes qu'il ne sait en fait pas joindre.

À ma connaissance, il n'existe pas encore de mise en œuvre de ce RFC.


Téléchargez le RFC 8968


L'article seul

RFC 8967: Message Authentication Code (MAC) Authentication for the Babel Routing Protocol

Date de publication du RFC : Janvier 2021
Auteur(s) du RFC : C. Dô, W. Kołodziejak, J. Chroboczek (IRIF, University of Paris-Diderot)
Chemin des normes
Réalisé dans le cadre du groupe de travail IETF babel
Première rédaction de cet article le 12 janvier 2021


Le protocole de routage Babel, normalisé dans le RFC 8966, avait à l'origine zéro sécurité. N'importe quelle machine pouvait se dire routeur et annoncer ce qu'elle voulait. Le RFC 7298 avait introduit un mécanisme d'authentification. Ce nouveau RFC le remplace avec un nouveau mécanisme. Chaque paquet est authentifié par HMAC, et les routeurs doivent donc partager une clé secrète.

Comme, par défaut, Babel croit aveuglément ce que ses voisins lui racontent, un méchant peut facilement, par exemple, détourner du trafic (en annonçant une route de plus forte préférence). Le problème est bien connu mais n'a pas de solution simple. Par exemple, Babel permet à la fois de l'unicast et du multicast, le second étant plus difficile à protéger. Une solution de sécurité, DTLS, spécifiée pour Babel dans le RFC 8968, résout le problème en ne faisant que de l'unicast. Notre RFC choisit une autre solution, qui marche pour l'unicast et le multicast. Chaque paquet est authentifié par un MAC attaché au paquet, calculé entre autres à partir d'une clé partagée.

La solution décrite dans ce RFC implique que tous les routeurs connectés au réseau partagent la clé secrète, et que tous ces routeurs soient eux-mêmes de confiance (l'authentification n'implique pas l'honnêteté du routeur authentifié, point très souvent oublié quand on parle d'authentification…) En pratique, c'est un objectif difficile à atteindre, il nécessite un réseau relativement bien géré. Pour un rassemblement temporaire où tout le monde partage sa connectivité, faire circuler ce mot de passe partagé sera difficile.

Ce mécanisme a par contre l'avantage de ne pas nécessiter que les horloges soient correctes ou synchronisées, et ne nécessite pas de stockage de données permanent (ce qui serait contraignant pour, par exemple, certains objets connectés).

Pour que tout marche bien et qu'on soit heureux et en sécurité, ce mécanisme d'authentification compte sur deux pré-requis :

  • Une fonction MAC sécurisée (évidemment), c'est-à-dire qu'on ne puisse pas fabriquer un MAC correct sans connaitre la clé secrète,
  • Que les nœuds Babel arrivent à ne pas répéter certaines valeurs qu'ils transmettent. Cela peut se faire avec un générateur aléatoire correct (RFC 4086) mais il existe aussi d'autres solutions pour générer des valeurs uniques, non réutilisables.

En échange de ces garanties, le mécanisme de ce RFC garantit l'intégrité des paquets et le rejet des rejeux (dans certaines conditions, voir le RFC).

La section 2 du RFC résume très bien le protocole : quand un routeur Babel envoie un paquet sur une des interfaces où la protection MAC a été activée, il calcule le MAC et l'ajoute à la fin du paquet. Quand il reçoit un paquet sur une des interfaces où la protection MAC a été activée, il calcule le MAC et, s'il ne correspond pas à ce qu'il trouve à la fin du paquet, le paquet est jeté. Simple, non ? Mais c'est en fait un peu plus compliqué. Pour protéger contre les attaques par rejeu, la machine qui émet doit maintenir un compteur des paquets envoyés, le PC (Packet Counter). Il est inclus dans les paquets envoyés et protégé par le MAC.

Ce PC ne protège pas dans tous les cas. Par exemple, si un routeur Babel vient de démarrer, et n'a pas de stockage permanent, il ne connait pas les PC de ses voisins et ne sait donc pas à quoi s'attendre. Dans ce cas, il doit ignorer le paquet et mettre l'émetteur au défi de répondre à un numnique qu'il envoie. Le voisin répond en incluant le numnique et son nouveau PC, prouvant ainsi qu'il ne s'agit pas d'un rejeu.

Petite difficulté, en l'absence de stockage permanent, le PC peut revenir en arrière et un PC être réutilisé. Outre le PC, il faut donc un autre nombre, l'index. Celui-ci n'est, comme le numnique utilisé dans les défis, jamais réutilisé. En pratique, un générateur aléatoire est une solution raisonnable pour fabriquer numniques et index.

La section 3 du RFC décrit les structures de données qu'il faut utiliser pour mettre en œuvre ce protocole. La table des interfaces (RFC 8966, section 3.2.3), doit être enrichie avec une indication de l'activation de la protection MAC sur l'interface, et la liste des clés à utiliser (Babel permet d'avoir plusieurs clés, notamment pour permettre le remplacement d'une clé, et le récepteur doit donc les tester toutes). Il faut également ajouter à la table des interfaces l'index et le PC décrits plus haut.

Quant à la table des (routeurs) voisins (RFC 8966, section 3.2.4), il faut y ajouter l'index et le PC de chaque voisin, et le numnique.

Enfin la section 4 détaille le fonctionnement. Comment calculer le MAC (avec un pseudo-en-tête), où mettre le TLV qui indique le PC, et celui qui contient la ou les MAC (dans la remorque, c'est-à-dire la partie du paquet après la longueur explicite), etc. La section 6 fournit quant à elle le format exact des TLV utilisés : le MAC TLV qui stocke le MAC, le PC TLV qui indique le compteur, et les deux TLV qui permettent de lancer un défi et d'obtenir une réponse, pour se resynchroniser en cas d'oubli du compteur. Ils ont été ajoutés au registre IANA.

Les gens de l'opérationnel aimeront la section 5, qui décrit comment déployer initialement cette option, et comment effectuer un changement de clé. Pour le déploiement initial, il faut configurer les machines dans un mode où elles signent mais ne vérifient pas les paquets entrants (il faut donc que les implémentations aient un tel mode). Cela permet de déployer progressivement. Une fois tous les routeurs ainsi configurés, on peut activer le mode normal, avec signature et vérification. Pour le remplacement de clés, on ajoute d'abord la nouvelle clé aux routeurs, qui signent donc avec les deux clés, l'ancienne et la nouvelle, puis, une fois que tous les routeurs ont la nouvelle clé, on retire l'ancienne.

Un petit bilan de la sécurité de ce mécanisme, en section 7 : d'abord un rappel qu'il est simple et qu'il ne fournit que le minimum, l'authentification et l'intégrité des paquets. Si on veut d'avantage, il faut passer à DTLS, avec le RFC 8968. Ensuite, certaines valeurs utilisées doivent être générées sans que l'attaquant puisse les deviner, ce qui nécessite, par exemple, un générateur de nombres aléatoires sérieux. D'autant plus que la taille limitée de certaines valeurs peut permettre des attaques à la force brute. Si, par exemple, les clés dérivent d'une phrase de passe, il faut une bonne phrase de passe, plus une fonction de dérivation qui gêne ces attaques, comme PBKDF2 (RFC 2898), bcrypt ou scrypt (RFC 7914).

Et, comme toujours, il y a la lancinante menace des attaques par déni de service, par exemple en envoyant des paquets délibérement conçus pour faire faire des calculs cryptographiques inutiles aux victimes. C'est pour limiter leurs conséquences que, par exemple, Babel impose de limiter le rythme d'envoi des défis, pour éviter de s'épuiser à défier un attaquant.

Je n'ai pas vu quelles mises en œuvre de Babel avaient ce mécanisme. il ne semble pas dans la version officielle de babeld, en tout cas, même si il y a du code dans une branche séparée. Et ce n'est pas encore dans une version officielle de BIRD mais le code est déjà écrit.

Le RFC ne décrit pas les changements depuis le RFC 7298 car il s'agit en fait d'un protocole différent, et incompatible (ce ne sont pas les mêmes TLV, par exemple).

Merci à Juliusz Chroboczek pour sa relecture.


Téléchargez le RFC 8967


L'article seul

RFC 8966: The Babel Routing Protocol

Date de publication du RFC : Janvier 2021
Auteur(s) du RFC : J. Chroboczek (IRIF, University of Paris-Diderot), D. Schinazi (Google LLC)
Chemin des normes
Réalisé dans le cadre du groupe de travail IETF babel
Première rédaction de cet article le 12 janvier 2021


Le travail sur les protocoles de routage ne désarme pas, motivé à la fois par les avancées de la science et par les nouvelles demandes (par exemple pour les réseaux ad hoc). Ainsi Babel est un protocole de routage, de la famille des protocoles à vecteur de distance, qui vise notammment à réduire drastiquement les probabilités de boucle. Il avait été décrit originellement dans le RFC 6126 ; ce nouveau RFC ne change pas fondamentalement le protocole Babel (quoique certains changements ne seront pas compris par les vieilles versions) mais il a désormais le statut de norme, et des solutions de sécurité plus riches. Ce manque de sécurité était la principale critique adressée à Babel.

Il y a deux familles de protocole de routage, ceux à vecteur de distance comme l'ancêtre RIP et ceux à états des liens comme OSPF (RFC 2328). Ce dernier est aujourd'hui bien plus utilisé que RIP, et à juste titre. Mais les problèmes de RIP n'ont pas forcément la même ampleur chez tous les membres de sa famille, et les protocoles à vecteurs de distance n'ont pas dit leur dernier mot.

Babel s'inspire de protocoles de routage plus récents comme DSDV. Il vise à être utilisable, à la fois sur les réseaux classiques, où le routage se fait sur la base du préfixe IP et sur les réseaux ad hoc, où il n'y a typiquement pas de regroupement par préfixe, où le routage se fait sur des adresses IP « à plat » (on peut dire que, dans un réseau ad hoc, chaque nœud est un routeur).

L'un des principaux inconvénients du bon vieux protocole RIP est sa capacité à former des boucles lorsque le réseau change de topologie. Ainsi, si un lien entre les routeurs A et B casse, A va envoyer les paquets à un autre routeur C, qui va probablement les renvoyer à A et ainsi de suite (le champ « TTL » pour IPv4 et « Hop limit » dans IPv6 a précisement pour but d'éviter qu'un paquet ne tourne sans fin). Babel, lui, évitera les boucles la plupart du temps mais, en revanche, il ne trouvera pas immédiatement la route optimale entre deux points. La section 1.1 du RFC spécifie plus rigoureusement les propriétés de Babel.

Babel peut fonctionner avec différentes métriques pour indiquer les coûts de telle ou telle route, le protocole lui-même étant indépendant de la métrique utilisée (cf. annexe A du RFC pour des conseils sur les choix). D'ailleurs, je vous recommande la lecture de l'Internet-Draft draft-chroboczek-babel-doesnt-care, pour mieux comprendre la philosophie de Babel.

Autre particularité de Babel, les associations entre deux machines pourront se faire même si elles utilisent des paramètres différents (par exemple pour la valeur de l'intervalle de temps entre deux « Hello » ; cf. l'annexe B pour une discussion du choix de ces paramètres, les compromis que ce choix implique entre intensité du trafic et détection rapide des changements, et les valeurs recommandées pour ces paramètres). Le RFC annonce ainsi que Babel est particulièrement adapté aux environnements « sans-fil » où certaines machines, devant économiser leur batterie, devront choisir des intervalles plus grands, ou bien aux environnements non gérés, où chaque machine est configurée indépendamment.

Je l'ai dit, rien n'est parfait en ce bas monde, et Babel a des limites, décrites en section 1.2. D'abord, Babel envoie périodiquement toutes les informations dont il dispose, ce qui, dans un réseau stable, mène à un trafic total plus important que, par exemple, OSPF (qui n'envoie que les changements). Ensuite, Babel a des mécanismes d'attente lorsqu'un préfixe disparait, qui s'appliquent aux préfixes plus généraux. Ainsi, lorsque deux préfixes deviennent agrégés, l'agrégat n'est pas joignable immédiatement.

Comment Babel atteint-il ses merveilleux objectifs ? La section 2 détaille les principes de base du protocole, la 3 l'échange de paquets et la 4 l'encodage d'iceux. Commençons par les principes. Babel est fondé sur le bon vieil algorithme de Bellman-Ford, tout comme RIP. Tout lien entre deux points A et B a un coût (qui n'est pas forcément un coût monétaire, c'est un nombre qui a la signification qu'on veut, cf. section 3.5.2). Le coût est additif (la somme des coûts d'un chemin complet faisant la métrique du chemin, section 2.1 et annexe A), ce qui veut dire que Métrique(A -> C) - pour une route passant par B >= Coût(A -> B) + Coût(B -> C). L'algorithme va essayer de calculer la route ayant la métrique le plus faible.

Un nœud Babel garde trace de ses voisins nœuds en envoyant périodiquement des messages Hello et en les prévenant qu'ils ont été entendus par des messages IHU (I Heard You). Le contenu des messages Hello et IHU permet de déterminer le coût.

Pour chaque source (d'un préfixe, pas d'un paquet), le nœud garde trace de la métrique vers cette source (lorsqu'un paquet tentera d'atteindre le préfixe annoncé) et du routeur suivant (next hop). Au début, évidemment la métrique est infinie et le routeur suivant indéterminé. Le nœud envoie à ses voisins les routes qu'il connait. Si celle-ci est meilleure que celle que connait le voisin, ce dernier l'adopte (si la distance était infinie - route inconnue, toute route sera meilleure).

L'algorithme « naïf » ci-dessus est ensuite amélioré de plusieurs façons : envoi immédiat de nouvelles routes (sans attendre l'émission périodique), mémorisation, non seulement de la meilleure route mais aussi de routes alternatives, pour pouvoir réagir plus vite en cas de coupure, etc.

La section 2.3 rappelle un problème archi-connu de l'algorithme de Bellman-Ford : la facilité avec laquelle des boucles se forment. Dans le cas d'un réseau simple comme celui-ci babel-simple-topo A annonce une route de métrique 1 vers S, B utilise donc A comme routeur suivant, avec une métrique de 2. Si le lien entre S (S = source de l'annonce) et A casse babel-simple-topo-cut comme B continue à publier une route de métrique 2 vers S, A se met à envoyer les paquets à B. Mais B les renvoie à A, créant ainsi une boucle. Les annonces ultérieures ne résolvent pas le problème : A annonce une route de métrique 3, passant par B, B l'enregistre et annonce une route de métrique 4 passant par A, etc. RIP résout le problème en ayant une limite arbitraire à la métrique, limite qui finit par être atteinte et stoppe la boucle (méthode dite du « comptage à l'infini »).

Cette méthode oblige à avoir une limite très basse pour la métrique. Babel a une autre approche : les mises à jour ne sont pas forcément acceptées, Babel teste pour voir si elles créent une boucle (section 2.4). Toute annonce est donc examinée au regard d'une condition, dite « de faisabilité ». Plusieurs conditions sont possibles. Par exemple, BGP utilise la condition « Mon propre numéro d'AS n'apparaît pas dans l'annonce. ». (Cela n'empêche pas les micro-boucles, boucles de courte durée en cas de coupure, cf. RFC 5715.) Une autre condition, utilisée par DSDV et AODV (RFC 3561), repose sur l'observation qu'une boucle ne se forme que lorsqu'une annonce a une métrique moins bonne que la métrique de la route qui a été retirée. En n'acceptant que les annonces qui améliorent la métrique, on peut donc éviter les boucles. Babel utilise une règle un peu plus complexe, empruntée à EIGRP, qui tient compte de l'histoire des annonces faites par le routeur.

Comme il n'y a pas de miracles en routage, cette idée de ne pas accepter n'importe quelle annonce de route a une contrepartie : la famine. Celle-ci peut se produire lorsqu'il existe une route mais qu'aucun routeur ne l'accepte (section 2.5). EIGRP résout le problème en « rédémarrant » tout le réseau (resynchronisation globale des routeurs). Babel, lui, emprunte à DSDV une solution moins radicale, en numérotant les annonces, de manière strictement croissante, lorsqu'un routeur détecte un changement dans ses liens. Une route pourra alors être acceptée si elle est plus récente (si elle a un numéro de séquence plus élevé), et un routeur Babel peut demander explicitement aux autres routeurs d'incrémenter ce nombre, pour accélérer la convergence. Ce numéro n'est par contre pas utilisé pour sélectionner la meilleure route (seule la métrique compte pour cela), uniquement pour voir si une annonce est récente.

À noter que tout se complique s'il existe plusieurs routeurs qui annoncent originellement la même route (section 2.7 ; un exemple typique est la route par défaut, annoncée par tous les routeurs ayant une connexion extérieure). Babel gère ce problème en associant à chaque préfixe l'identité du routeur qui s'est annoncé comme origine et considère par la suite ces annonces comme distinctes, même si le préfixe est le même. Conséquence : Babel ne peut plus garantir qu'il n'y aura pas de boucle (Babel essaie de construire un graphe acyclique mais l'union de plusieurs graphes acycliques n'est pas forcément acyclique). Par contre, il pourra détecter ces boucles a posteriori et les éliminer plus rapidement qu'avec du comptage vers l'infini.

Notez aussi que chaque routeur Babel est libre de rejeter les routes qui lui semblent déraisonnables, comme 127.0.0.1/32, sans affecter le fonctionnement du protocole (le détail de cette question du filtrage des routes est dans l'annexe C.)

Voilà pour les principes. Et le protocole ? La section 3 le décrit. Chaque routeur a une identité sur huit octets (le plus simple est de prendre l'adresse MAC d'une des interfaces). Les messages sont envoyés dans des paquets UDP et encodés en TLV. Le paquet peut être adressé à une destination unicast ou bien multicast. Les TLV peuvent contenir des sous-TLV dans leur partie Valeur.

Un routeur Babel doit se souvenir d'un certain nombre de choses (section 3.2), notamment :

  • Le numéro de séquence, qui croît strictement,
  • La liste des interfaces réseau où parler le protocole,
  • La liste des voisins qu'on a entendus,
  • La liste des sources (routeurs qui ont été à l'origine de l'annonce d'un préfixe). Elle sert pour calculer les critères d'acceptation (ou de rejet) d'une route. Babel consomme donc plus de mémoire que RIP, qui ne connait que son environnement immédiat, alors qu'un routeur Babel connaît tous les routeurs du réseau.
  • Et bien sûr la table des routes, celle qui, au bout du compte, sera utilisée pour la transmission des paquets.

Les préfixes annoncés sont sans rapport avec la version du protocole IP utilisée pour transporter l'annonce. Un préfixe IPv4 peut donc être envoyé en IPv6. Le RFC recommande de faire tourner Babel sur IPv6, même si le réseau est en partie en IPv4.

Les messages Babel ne bénéficient pas d'une garantie de délivrance (c'est de l'UDP, après tout), mais un routeur Babel peut demander à ses voisins d'accuser réception (section 3.3). La décision de le demander ou pas découle de la politique locale de chaque routeur. Si un routeur ne demande pas d'accusé de réception, l'envoi périodique des routes permettra de s'assurer que, au bout d'un certain temps, tous les routeurs auront toute l'information. Les accusés de réception peuvent toutefois être utiles en cas de mises à jour urgentes dont on veut être sûr qu'elles ont été reçues.

Comment un nœud Babel trouve t-il ses voisins ? La section 3.4 décrit ce mécanisme. Les voisins sont détectés par les messages Hello qu'ils émettent. Les messages IHU (I Heard You) envoyés en sens inverse permettent notamment de s'assurer que le lien est bien bidirectionnel.

Les détails de la maintenance de la table de routage figurent en section 3.5. Chaque mise à jour envoyée par un nœud Babel est un quintuplet {préfixe IP, longueur du préfixe, ID du routeur, numéro de séquence, métrique}. Chacune de ces mises à jour est évaluée en regard des conditions de faisabilité : une distance de faisabilité est un doublet {numéro de séquence, métrique} et ces distances sont ordonnées en comparant d'abord le numéro de séquence (numéro plus élevée => distance de faisabilité meilleure) et ensuite la métrique (où le critère est inverse). Une mise à jour n'est acceptée que si sa distance de faisabilité est meilleure.

Si la table des routes contient plusieurs routes vers un préfixe donné, laquelle choisir et donc réannoncer aux voisins (section 3.6) ? La politique de sélection n'est pas partie intégrante de Babel. Plusieurs mises en œuvre de ce protocole pourraient faire des choix différents. Les seules contraintes à cette politique sont qu'il ne faut jamais réannoncer les routes avec une métrique infinie (ce sont les retraits, lorsqu'une route n'est plus accessible), ou les routes infaisables (selon le critère de faisabilité cité plus haut). Si les différents routeurs ont des politiques différentes, cela peut mener à des oscillations (routes changeant en permanence) mais il n'existe pas à l'heure actuelle de critères scientifiques pour choisir une bonne politique. On pourrait imaginer que le routeur ne garde que la route avec la métrique le plus faible, ou bien qu'il privilégie la stabilité en gardant la première route sélectionnée, ou encore qu'il prenne en compte des critères comme la stabilité du routeur voisin dans le temps. En attendant les recherches sur ce point, la stratégie conseillée est de privilégier la route de plus faible métrique, en ajoutant un petit délai pour éviter de changer trop souvent. Notez que la méthode de calcul des métriques n'est pas imposée par Babel : tant que cette méthode obéit à certains critères, notamment de monotonie, elle peut être utilisée.

Une fois le routeur décidé, il doit envoyer les mises à jour à ses voisins (section 3.7). Ces mises à jour sont transportées dans des paquets multicast (mais peuvent l'être en unicast). Les changements récents sont transmis immédiatement, mais un nœud Babel transmet de toute façon la totalité de ses routes à intervalles réguliers. Petite optimisation : les mises à jour ne sont pas transmises sur l'interface réseau d'où la route venait, mais uniquement si on est sûr que ladite interface mène à un réseau symétrique (un Ethernet filaire est symétrique mais un lien WiFi ad hoc ne l'est pas forcément).

Un routeur Babel peut toujours demander explicitement des annonces de routes à un voisin (section 3.8). Il peut aussi demander une incrémentation du numéro de séquence, au cas où il n'existe plus aucune route pour un préfixe donné (problème de la famine, section 3.8.2.1).

La section 4 spécifie l'encodage des messages Babel sur le réseau. C'est un paquet UDP, envoyé à une adresse multicast (ff02::1:6 ou 224.0.0.111) ou bien unicast, avec un TTL de 1 (puisque les messages Babel n'ont jamais besoin d'être routés), et un port source et destination de 6696. En IPv6, les adresses IP de source et de destination unicast sont locales au lien et en IPv4 des adresses du réseau local.

Les données envoyées dans le message sont typées et la section 4.1 liste les types possibles, par exemple interval, un entier de 16 bits qui sert à représenter des durées en centisecondes (rappelez-vous que, dans Babel, un routeur informe ses voisins de ses paramètres temporels, par exemple de la fréquence à laquelle il envoie des Hello). Plus complexe est le type address, puisque Babel permet d'encoder les adresses par différents moyens (par exemple, pour une adresse IPv6 locale au lien, le préfixe fe80::/64 peut être omis). Quant à l'ID du routeur, cet identifiant est stocké sur huit octets.

Ensuite, ces données sont mises dans des TLV, eux-même placés derrière l'en-tête Babel, qui indique un nombre magique (42...) pour identifier un paquet Babel, un numéro de version (aujourd'hui 2) et la longueur du message. (La fonction babel_print_v2 dans le code de tcpdump est un bon moyen de découvrir les différents types et leur rôle.) Le message est suivi d'une remorque qui n'est pas comptée pour le calcul de la longueur du message, et qui sert notamment pour l'authentification (cf. RFC 8967). La remorque, une nouveauté qui n'existait pas explicitement dans le RFC 6126, est elle-même composée de TLV. Chaque TLV, comme son nom l'indique, comprend un type (entier sur huit bits), une longueur et une valeur, le corps, qui peut comprendre plusieurs champs (dépendant du type). Parmi les types existants :

  • 0 et 1, qui doivent être ignorés (ils servent si on a besoin d'aligner les TLV),
  • 2, qui indique une demande d'accusé de réception, comme le Echo Request d'ICMP (celui qui est utilisé par la commande ping). Le récepteur doit répondre par un message contenant un TLV de type 3.
  • 4, qui désigne un message Hello. Le corps contient notamment le numéro de séquence actuel du routeur. Le type 5 désigne une réponse au Hello, le IHU, et ajoute des informations comme le coût de la liaison entre les deux routeurs.
  • 6 sert pour transmettre l'ID du routeur.
  • 7 et 8 servent pour les routes elles-mêmes. 7 désigne le routeur suivant qui sera utilisé (next hop) pour les routes portées dans les TLV de type 8. Chaque TLV Update (type 8) contient notamment un préfixe (avec sa longueur), un numéro de séquence, et une métrique.
  • 9 est une demande explicite de route (lorsqu'un routeur n'a plus de route vers un préfixe donné ou simplement lorsqu'il est pressé et ne veut pas attendre le prochain message). 10 est la demande d'un nouveau numéro de séquence.

Les types de TLV sont stockés dans un registre IANA. On peut en ajouter à condition de fournir une spécification écrite (« Spécification nécessaire », cf. RFC 8126). Il y a aussi un registre des sous-TLV.

Quelle est la sécurité de Babel ? La section 6 dit franchement qu'elle est, par défaut, à peu près inexistante. Un méchant peut annoncer les préfixes qu'il veut, avec une bonne métrique pour être sûr d'être sélectionné, afin d'attirer tout le trafic.

En IPv6, une protection modérée est fournie par le fait que les adresses source et destination sont locales au lien. Comme les routeurs IPv6 ne sont pas censés faire suivre les paquets ayant ces adresses, cela garantit que le paquet vient bien du réseau local. Une raison de plus d'utiliser IPv6.

Ce manque de sécurité dans le Babel original du RFC 6126 avait suscité beaucoup de discussions à l'IETF lors de la mise de Babel sur le chemin des normes (voir par exemple cet examen de la direction de la sécurité). Normalement, l'IETF exige de tout protocole qu'il soit raisonnablement sécurisé (la règle figure dans le RFC 3365). Certaines personnes s'étaient donc vigoureusement opposées à la normalisation officielle de Babel tant qu'il n'avait pas de solution de sécurité disponible. D'autres faisaient remarquer que Babel était quand même déployable pour des réseaux fermés, « entre copains », même sans sécurité, mais les critiques pointaient le fait que tôt ou tard, tout réseau fermé risque de se retrouver ouvert. D'un autre côté, sécuriser des réseaux « ad hoc », par exemple un lot de machines mobiles qui se retrouvent ensemble pour un événement temporaire, est un problème non encore résolu.

Un grand changement de notre RFC est de permettre la signature des messages. Deux mécanismes existent, décrits dans les RFC 8967 (signature HMAC, pour authentifier, la solution la plus légère) et RFC 8968 (DTLS, qui fournit en plus la confidentialité). (Notons que, en matière de routage, la signature ne résout pas tout : c'est une chose d'authentifier un voisin, une autre de vérifier qu'il est autorisé à annoncer ce préfixe.)

J'ai parlé plus haut de la détermination des coûts des liens. L'annexe A du RFC contient des détails intéressants sur cette détermination. Ainsi, contrairement aux liens fixes, les liens radio ont en général une qualité variable, surtout en plein air. Déterminer cette qualité est indispensable pour router sur des liens radio, mais pas facile. L'algorithme ETX (décrit dans l'article de De Couto, D., Aguayo, D., Bicket, J., et R. Morris, « A high-throughput path metric for multi-hop wireless networks ») est recommandé pour cela.

L'annexe D est consacrée aux mécanismes d'extension du protocole, et reprend largement le RFC 7557, qu'elle remplace. Babel prévoyait des mécanismes d'extension en réservant certaines valeurs et en précisant le comportement d'un routeur Babel lors de la réception de valeurs inconnues. Ainsi :

  • Un paquet Babel avec un numéro de version différent de 2 doit être ignoré, ce qui permet de déployer une nouvelle future version de Babel sans que ses paquets ne cassent les implémentations existantes,
  • Un TLV de type inconnu doit être ignoré (section 4.3), ce qui permet d'introduire de nouveaux types de TLV en étant sûr qu'ils ne vont pas perturber les anciens routeurs,
  • Les données contenues dans un TLV au-delà de sa longueur indiquée, ou bien les données présentes après le dernier TLV, devaient, disait le RFC 7557 qui reprenait le RFC 6126, également être silencieusement ignorées (au lieu de déclencher une erreur). Ainsi, une autre voie d'extension était possible, pour glisser des données supplémentaires. Cette voie est désormais utilisée par les solutions de signature comme celle du RFC 8966.

Quelles sont donc les possibilités d'extensions propres ? Cela commence par une nouvelle version du protocole (l'actuelle version est la 2), qui utiliserait des numéros 3, puis 4... Cela ne doit être utilisé que si la nouvelle version est incompatible avec les précédentes et ne peut pas interopérer sur le même réseau.

Moins radicale, une extension de la version 2 peut introduire de nouveaux TLV (qui seront ignorés par les mises en œuvre anciennes de la version 2). Ces nouveaux TLV doivent suivre le format de la section 4.3. De la même façon, on peut introduire de nouveaux sous-TLV (des TLV contenus dans d'autres TLV, décrits en section 4.4).

Si on veut ajouter des données dans un TLV existant, en s'assurant qu'il restera correctement analysé par les anciennes mises en œuvre, il faut jouer sur la différence entre la taille explicite (explicit size) et la taille effective (natural size) du TLV. La taille explicite est celle qui est indiqué dans le champ Length spécifié dans la section 4.3. La taille effective est celle déduite d'une analyse des données (plus ou moins compliquée selon le type de TLV). Comme les implémentations de Babel doivent ignorer les données situées après la taille explicite, on peut s'en servir pour ajouter des données. Elles doivent être encodées sous forme de sous-TLV, chacun ayant type, longueur et valeur (leur format exact est décrit en section 4.4).

Enfin, après le dernier TLV (Babel est transporté sur UDP, qui indique une longueur explicite), on peut encore ajouter des données, une « remorque ». C'est ce que fait le RFC 8966.

Bon, mais alors quel mécanisme d'extension choisir ? La section 4 fournit des pistes aux développeurs. Le choix de faire une nouvelle version est un choix drastique. Il ne devrait être fait que si la nouvelle version est réellement incompatible avec la précédente.

Un nouveau TLV, ou bien un nouveau sous-TLV d'un TLV existant est la solution à la plupart des problèmes d'extension. Par exemple, si on veut mettre de l'information supplémentaire aux mises à jour de routes (TLV Update), on peut créer un nouveau TLV « Update enrichi » ou bien un sous-TLV de Update qui contiendra l'information supplémentaire. Attention, les conséquences de l'un ou l'autre choix ne seront pas les mêmes. Un TLV « Update enrichi » serait totalement ignoré par un Babel ancien, alors qu'un TLV Update avec un sous-TLV d'« enrichissement » verrait la mise à jour des routes acceptée, seule l'information supplémentaire serait perdue.

Il existe désormais, pour permettre le développement d'extensions, un registre IANA des types de TLV et un des sous-TLV (section 5 du RFC) et plusieurs extensions s'en servent déjà.

Enfin, l'annexe F du RFC résume les changements depuis le premier RFC, le RFC 6126, qui documentait la version 2 de Babel. On reste en version 2 car le protocole de notre RFC reste essentiellement compatible avec le précédent. Il y a toutefois trois fonctions de ce protocole qui pourraient créer des problèmes sur un réseau où certaines machines sont restées au RFC 6126 :

  • Les messages de type Hello en unicast sont une nouveauté. L'ancien RFC ne les mentionnait pas. Cela peut entrainer une mauvaise interprétation des numéros de séquence (qui sont distincts en unicast et multicast).
  • Les messages Hello peuvent désormais avoir un intervalle entre deux messages qui est nul, ce qui n'existait pas avant.
  • Les sous-TLV obligatoires (section 4.4) n'existaient pas avant et leur utilisation peut donc être mal interprétée par les vieilles mises en œuvre de Babel (le TLV englobant va être accepté alors qu'il devrait être rejeté).

Bref, si on veut déployer le Babel de ce RFC dans un réseau où il reste de vieilles mises en œuvre, il faut prendre garde à ne pas utiliser ces trois fonctions. Si on préfère mettre à jour les vieux programmes, sans toutefois y intégrer tout ce que contient notre RFC, il faut au minimum ignorer (ou bien gérer correctement) les Hello en unicast, ou bien avec un intervalle nul, et il faut ignorer un TLV qui contient un sous-TLV obligatoire mais inconnu.

Il y a d'autres changements depuis le RFC 6126 mais qui ne sont pas de nature à affecter l'interopérabilité ; voyez le RFC pour les détails.

Vous pourrez trouver plus d'informations sur Babel en lisant le RFC, ou bien sur la page Web officielle. Si vous voulez approfondir la question des protocoles de routage, une excellente comparaison a été faite par David Murray, Michael Dixon et Terry Koziniec dans « An Experimental Comparison of Routing Protocols in Multi Hop Ad Hoc Networks » où ils comparent Babel (qui l'emporte largement), OLSR (RFC 7181) et Batman (ce dernier est dans le noyau Linux officiel). Notez aussi que l'IETF a un protocole standard pour ce problème, RPL, décrit dans le RFC 6550. Si vous aimez les vidéos, l'auteur de Babel explique le protocole en anglais.

Qu'en est-il des mises en œuvre de ce protocole ? Il existe une implémentation d'exemple, babeld, assez éprouvée pour être disponible en paquetage dans plusieurs systèmes, comme babeld dans Debian ou dans OpenWrt, plateforme très souvent utilisée pour des routeurs libres (cf. https://openwrt.org/docs/guide-user/services/babeld). babeld ne met pas en œuvre les solutions de sécurité des RFC 8966 ou RFC 8967. Une autre implémentation se trouve dans Bird. Si vous voulez écrire votre implémentation, l'annexe E contient plusieurs conseils utiles, accompagnés de calculs, par exemple sur la consommation mémoire et réseau. Le RFC proclame que Babel est un protocole relativement simple et, par exemple, l'implémentation de référence contient environ 12 500 lignes de C.

Néanmoins, cela peut être trop, une fois compilé, pour des objets (le RFC cite les fours à micro-ondes...) et l'annexe E décrit donc des sous-ensembles raisonnables de Babel. Par exemple, une mise en œuvre passive pourrait apprendre des routes, sans rien annoncer. Plus utile, une mise en œuvre « parasite » n'annonce que la route vers elle-même et ne retransmet pas les routes apprises. Ne routant les paquets, elle ne risquerait pas de créer des boucles et pourrait donc omettre certaines données, comme la liste des sources. (Le RFC liste par contre ce que la mise en œuvre parasite doit faire.)

Toujours côté programmes, tcpdump et Wireshark savent afficher les paquets Babel.


Téléchargez le RFC 8966


L'article seul

RFC 8965: Applicability of the Babel Routing Protocol

Date de publication du RFC : Janvier 2021
Auteur(s) du RFC : J. Chroboczek (IRIF, University of Paris-Diderot)
Pour information
Réalisé dans le cadre du groupe de travail IETF babel
Première rédaction de cet article le 12 janvier 2021


Comme tout bon protocole, le protocole de routage Babel, normalisé dans le RFC 8966 ne fait pas de miracles et ne marche pas dans tous les cas. Il est donc utile de savoir quels sont les cas où Babel est efficace et utile. Ce RFC s'applique à identifier et documenter les créneaux pour Babel.

C'est qu'il en existe beaucoup de protocoles de routage internes à un AS. Le RFC cite OSPF (RFC 5340) et IS-IS (RFC 1195), qui ont la faveur des grands réseaux gérés par des professionnels, mais je trouve que Babel se situe plutôt du côté de protocoles prévus pour des réseaux moins organisés, comme Batman ou RPL (RFC 6550). Je vous laisse lire le RFC 8966 pour voir comment fonctionne Babel. Disons juste que c'est un protocole à vecteur de distances (comme RIPRFC 2453 - mais sans ses inconvénients) et qui considère la faisabilité de chaque route potentielle avant de l'ajouter, supprimant ainsi le risque de boucles. En refusant des routes qui pourraient peut-être créer une boucle, le risque est la famine, l'absence de route pour une destination, risque dont Babel se protège avec un mécanisme de numéro de séquence dans les annonces.

La section 2 de notre RFC explicite les caractéristiques de Babel, les bonnes et les mauvaises. D'abord, la simplicité. Babel est conceptuellement simple, ce qui facilite les analyses du protocole, et aussi sa mise en œuvre. Comme le note le RFC, Babel peut être expliqué en un micro-siècle. Et question programmation, le RFC cite une mise en œuvre de Babel réalisée en deux nuits (le RFC ne dit pas ce que le ou la programmeur·e faisait de jour…)

Ensuite, la résistance. Babel ne dépend que de quelques propriétés simples du réseau et peut donc fonctionner dans un large éventail de situations. Babel demande juste que les métriques soient strictement monotones croissantes (emprunter le chemin A puis le B, doit forcément coûter strictement plus cher que de juste prendre le chemin A, autrement des boucles pourraient se former) et distributives à gauche (cf. « Metarouting » de Griffin et Sobrinho). En revanche, Babel n'exige pas un transport fiable (les paquets ont le droit de se perdre, ou de doubler un autre paquet) et n'exige pas que la communication soit transitive (dans les liens radio, il peut arriver que A puisse joindre B et B puisse parler à C, sans que pour autant A puisse communiquer directement avec C). Des protocoles comme OSPF (RFC 5340) ou IS-IS sont bien plus exigeants de ce point de vue.

Babel est extensible : comme détaillé dans l'annexe C du RFC 8966, le protocole Babel peut être étendu. Cela a été fait, par exemple, pour permettre du routage en fonction de la source (draft-ietf-babel-source-specific) ou bien du routage en fonction du RTT (draft-ietf-babel-rtt-extension). Il y a aussi des extensions qui n'ont pas (encore ?) été déployées comme du routage en fonction des fréquences radio (draft-chroboczek-babel-diversity-routing) ou en fonction du ToS (draft-chouasne-babel-tos-specific).

Mais Babel a aussi des défauts, notamment :

  • Babel envoie périodiquement des messages, même quand il n'y a eu aucun changement. Cela permet de ne pas dépendre d'un transport fiable des paquets mais c'est du gaspillage lorsque le réseau est stable. Un grand réseau très stable et bien géré a donc plutôt intérêt à utiliser des protocoles comme OSPF, pour diminuer le trafic dû au routage. À l'autre extrémité, certains réseaux de machines contraintes (par exemple tournant uniquement sur batterie) n'auront pas intérêt à utiliser un protocole comme Babel, qui consomme de l'énergie même quand le réseau n'a pas changé.
  • Avec Babel, chaque routeur connait toute la table de routage. Si des routeurs sont limités en mémoire, cela peut être un problème, et des protocoles comme AODV (RFC 3561), RPL (RFC 6550) ou LOADng sont peut-être plus adaptés.
  • Babel peut être lent à récupérer lorsqu'il fait de l'agrégation de préfixes, et qu'une route plus spécifique est retirée.

Babel a connu plusieurs déploiements dans le monde réel (section 3). Certains de ces déploiements étaient dans des réseaux mixtes, mêlant des parties filaires, routant sur le préfixe, et des parties radio, avec du routage mesh sur les adresses IP. Babel a aussi été déployé dans des overlays, en utilisant l'extension tenant compte du RTT, citée plus haut. Il a aussi été utilisé dans des réseaux purement mesh. Voir à ce sujet les articles « An experimental comparison of routing protocols in multi hop ad hoc networks » et « Real-world performance of current proactive multi-hop mesh protocols ». Ces réseaux utilisent traditionnellement plutôt des protocoles comme OLSR (RFC 7181). Enfin, Babel a été déployé dans des petits réseaux non gérés (pas d'administrateur système).

Et la sécurité, pour finir (section 5). Comme tous les protocoles à vecteur de distance, Babel reçoit l'information de ses voisins. Il faut donc leur faire confiance, un voisin menteur peut annoncer ce qu'il veut. En prime, si le réseau sous-jacent n'est pas lui-même sécurisé (par exemple si c'est du WiFi sans WPA), il n'y a même pas besoin d'être routeur voisin, toute machine peut tripoter les paquets.

Pour empêcher cela, il y a deux solutions cryptographiques, dans les RFC 8967 (la solution la plus simple) et RFC 8968 (plus complexe mais plus sûre et qui fournit en prime de la confidentialité). La solution avec le MAC, normalisée dans le RFC 8967, est celle recommandée car elle convient mieux au caractère de la plupart des réseaux utilisant Babel. (Le RFC n'en parle apparamment pas, mais notez qu'authentifier le voisin ne résout pas complètement le problème. On peut être authentifié et mentir.)

Enfin, si on n'utilise pas de solution de confidentialité, un observateur peut déduire des messages Babel la position d'un routeur WiFi qui se déplacerait, ce qui peut être considéré comme un problème.

Sinon, pour creuser cette question de l'applicabilité de Babel, vous pouvez lire le texte, très vivant, « Babel does not care ».


Téléchargez le RFC 8965


L'article seul

RFC 8963: Evaluation of a Sample of RFCs Produced in 2018

Date de publication du RFC : Janvier 2021
Auteur(s) du RFC : C. Huitema (Private Octopus)
Pour information
Première rédaction de cet article le 23 janvier 2021


On raconte souvent que la publication d'un RFC prend longtemps, très longtemps, trop longtemps, et on dit parfois que cela est dû à une bureaucratie excessive dans le processus de publication. Peut-on objectiver cette opinion ? Dans ce RFC, l'auteur étudie le trajet de plusieurs RFC (pris au hasard) publiés en 2018 et regarde où étaient les goulets d'étranglement, et ce qui a pris du temps. Les RFC étudiés ont mis en moyenne trois ans et quatre mois à être publiés, depuis le premier document écrit jusqu'à la publication finale. (Mais il y a de grandes variations.) La majorité du temps est passée dans le groupe de travail, ce ne sont donc pas forcément les étapes « bureaucratiques » qui retardent.

Heureusement pour cette étude, le DataTracker enregistre les différentes étapes de la vie d'un document et permet donc au chercheur de travailler sur des données solides. Mais attention, si les données sont fiables, leur interprétation est toujours délicate. Et, si une métrique devient un objectif, elle cesse d'être une bonne métrique. Si l'IETF ne cherchait qu'à raccourci le délai de publication, ce serait simple, il suffirait d'approuver sans discussion et immédiatement toutes les propositions, même mauvaises. (Ce qu'on nomme le rubber stamping en anglais.)

La section 2 expose la méthodologie suivie. D'abord, déterminer les dates importantes dans la vie d'un RFC. Tous les RFC ne suivent pas le même parcours, loin de là. Le cas typique est celui d'un document individuel, qui est ensuite adopté par un groupe de travail, approuvé par l'IESG, puis publié par le RFC Editor. Dans cette analyse, trois périodes sont prises en compte :

  • Le temps de traitement dans le groupe de travail, qui part de la première publication et s'arrête au dernier appel à l'IETF,
  • Le temps de traitement par l'IETF, qui part du dernier appel à l'IETF et se termine par l'approbation par l'IESG,
  • Le temps de traitement par le RFC Editor, qui part de l'approbation par l'IESG et se termine par la publication en RFC.

Ainsi, pour le dernier RFC publié au moment où j'écris cet article (et qui n'est donc pas pris en compte dans l'analyse, qui couvre des RFC de 2018), pour ce RFC 9003, on voit sur le DataTracker qu'il a été documenté pour la première fois en décembre 2017, adopté par un groupe de travail en avril 2018, soumis à l'IESG en avril 2020, a eu un IETF Last Call en juin 2020, a été approuvé par l'IESG en novembre 2020 et enfin publié en RFC début janvier 2021. Mais beaucoup d'autres RFC suivent un autre parcours. Pour la voie indépendante (décrite dans le RFC 4846), l'analyse de ce RFC 8963 considère l'approbation par l'ISE (Independent Submission Editor) comme terminant la première période et l'envoi au RFC Editor comme terminant la deuxième.

Les vingt RFC qui sont utilisés dans l'étude ont été pris au hasard parmi ceux publiés en 2018. Le RFC reconnait que c'est un échantillon bien petit mais c'est parce que les données disponibles ne fournissaient pas toutes les informations nécessaires et qu'il fallait étudier « manuellement » chaque RFC, ce qui limitait forcément leur nombre. Je ne vais pas lister ici tous les RFC étudiés, juste en citer quelques uns que je trouve intéressant à un titre ou l'autre :

  • Le RFC 8446 est un très gros morceau. C'est la nouvelle version d'un protocole absolument critique, TLS. Non seulement le RFC est gros et complexe, mais le souci de sécurité a entrainé des études particulièrement poussées. Et, pour couronner le tout, ce RFC a été au cœur de polémiques politiques, certains reprochant à cette version de TLS d'être trop efficace, d'empêcher d'accéder aux données. La phase finale chez le RFC Editor, dite « AUTH48 » (car elle se fait normalement en 48 heures…) n'a pas pris moins de deux mois ! C'est en partie dû à une nouveauté de l'époque, utiliser GitHub pour suivre les changements à apporter. Pour chaque RFC étudié, l'auteur note également son succès ou insuccès en terme de déploiement et TLS 1.3 est un succès massif, avec des dizaines de mises en œuvre différentes et une présence très forte sur l'Internet.
  • Le RFC 8324 est un cas très différent. Consacré à une série d'opinions sur le DNS et ses évolutions récentes, il ne s'agit pas, contrairement au précédent, d'une norme IETF, mais d'une contribution individuelle. N'engageant que son auteur, elle ne nécessite pas le même niveau d'examen. C'est donc ce RFC qui est le titulaire du record dans l'étude : neuf mois seulement entre la première publication et le RFC.
  • Le RFC 8429 avait pour but de documenter l'abandon de plusieurs protocoles de cryptographie désormais dépassés. A priori très simple et très consensuel, il a été retardé par une discussion plus bureaucratique à propos de ses conséquences exactes sur le RFC 4757, puis par un délai très long d'un des auteurs à répondre lors de la phase finale (« AUTH48 » a pris trois mois).
  • Le RFC 8312 portait aussi sur un sujet consensuel, l'algorithme de lutte contre la congestion CUBIC étant aujourd'hui largement adopté. Mais il a quand même pris des années, en partie car, justement, CUBIC étant consensuel et largement déployé, il ne semblait pas crucial de le documenter. (Rappelez-vous que le travail à l'IETF est fait par des volontaires.)
  • Le RFC 8492 a également pris longtemps car c'était un projet individuel, dont l'auteur n'avait pas le temps de s'occuper, ce qui explique des longs délais à certains points. En outre, il a été retardé par la nécessité d'attendre un changement de politique d'enrgistrements dans certains registres à l'IANA, changements qui dépendaient de TLS 1.3, qui prenait du temps.
  • Un autre RFC qui a souffert du fait que même ses auteurs ne le considéraient pas comme urgent a été le RFC 8378, un projet très expérimental et dont la publication en RFC a pris plus de quatre ans.
  • Un des buts de l'étude était de voir s'il y avait une corrélation entre le délai total et certaines caractéristiques du RFC, par exemple sa longueur. Mais le RFC 8472, quoique simple, a pris plus de trois ans, en partie pour un problème bureaucratique sur le meilleur groupe de travail à utiliser.
  • En revanche, le RFC 8362 n'a pas de mal à expliquer le temps qu'il a pris : il modifie sérieusement le protocole OSPF et nécessite des mécanismes de transition depuis les anciennes versions, qui ont eux-même pris du temps.

Bien, après cette sélection d'un échantillon de RFC, qu'observe-t-on (section 4 de notre RFC) ? Le délai moyen entre le premier Internet Draft et la publication en RFC est de trois ans et trois mois. Mais les variations sont telles que cette moyenne n'a guère de sens (c'est souvent le cas avec la moyenne). Le plus malheureux est le RFC 8492, six ans et demi au total, mais ce cas n'est pas forcément généralisable. Pour les RFC de l'IETF, où le délai moyen est presque le même, le temps passé dans le groupe de travail est de deux ans et neuf mois, à l'IESG de trois à quatre mois et à peu près pareil chez le RFC Editor. Bref, 80 % du temps se passe en « vrai » travail, dans le groupe, les passages plus « bureaucratiques » n'occupent que 20 % du temps et ne sont donc pas les principaux responsables des délais.

L'étude regarde aussi des échantillons de RFC publiés en 2008 et en 1998 (en utilisant cette fois, à juste titre, des valeurs médianes) et note que le délai était à peu près le même en 2008 mais qu'il était trois à quatre fois plus court en 1998.

Et la partie purement éditoriale, chez le RFC Editor ? On a vu qu'elle prenait de trois à quatre mois. C'est quatre en moyenne mais si on exclut l'extrême RFC 8492, c'est plutôt trois mois, ce que je trouve plutôt court pour relire et préparer des documents longs et complexes, où une coquille peut être très gênante (ce ne sont pas des romans !) Quelles sont les caractéristiques des RFC que l'on pourrait corréler à ce délai chez le RFC Editor ? L'étude montre une corrélation entre le délai et la longueur du document, ce qui n'est pas surprenant. Même chose pour la corrélation entre le nombre de changements faits depuis l'approbation par l'IESG et la publication. (Une des tâches du RFC Editor est de faire en sorte que les RFC soient en bon anglais alors que beaucoup d'auteurs ne sont pas anglophones.) Plus étonnant, le nombre d'auteurs ne diminue pas le délai, au contraire, il l'augmente. Un nouveau cas de « quand on ajoute des gens au projet, le projet n'avance pas plus vite » ? Il ne faut pas oublier qu'il existe une zone grise entre « changement purement éditorial », où le RFC Editor devrait décider, et « changement qui a des conséquences techniques » et où l'approbation des auteurs est cruciale. Il n'est pas toujours facile de s'assurer de la nature d'un changement proposé par le RFC Editor. Et, si le changement a des conséquences techniques, il faut mettre les auteurs d'accord entre eux, ce qui est long (ils peuvent être dans des fuseaux horaires différents, avec des obligations professionnelles différentes) et peut soulever des désaccords de fond.

Et pour la voie indépendante ? Trois des RFC de l'échantillon étaient sur cette voie. Ils ont été publiés plus vite que sur la voie IETF, ce qui est logique puisqu'ils ne nécessitent pas le consensus de l'IETF (voir le RFC 8789). En revanche, le fait que la fonction d'ISE (Independant Stream Editor, cf. RFC 8730) soit une tâche assurée par un seul volontaire peut entrainer des retards ce qui a été le cas, ironiquement, de ce RFC 8963.

L'étude se penche aussi sur le nombre de citations dont bénéficie chaque RFC (section 5) car elle peut donner une idée sommaire de leur impact. Elle a utilisé l'API de Semantic Scholar. Par exemple, pour le RFC 8483, publié l'année de l'étude mais pas inclus dans l'échantillon :

% curl -s https://api.semanticscholar.org/v1/paper/10.17487/rfc8483\?include_unknown_references=true \
   jq .
  

On voit qu'il est cité une fois, précisément par ce RFC. En revanche, le RFC 8446, sur TLS 1.3 est cité pas moins de 773 fois :

% curl -s https://api.semanticscholar.org/v1/paper/10.17487/rfc8446\?include_unknown_references=true | \
   jq .citations\|length         
773
  

C'est de très loin le plus « populaire » dans le corpus (l'échantillon étudié). C'est bien sûr en partie en raison de l'importance critique de TLS, mais c'est aussi parce que TLS 1.3 a été étudié par des chercheurs et fait l'objet d'une validation formelle, un sujet qui génère beaucoup d'articles dans le monde académique. C'est aussi le cas du RFC 8312, sur Cubic, car la congestion est également un sujet de recherche reconnu. Notez que le passage du temps est impitoyable : les RFC des échantillons de 2008 et 1998 ne sont quasiment plus cités (sauf le RFC 2267, qui résiste). Est-ce que le nombre de citations est corrélé à l'ampleur du déploiement ? Dans le cas des RFC 8446 et RFC 8312, on pourrait le croire, mais le RFC 8441 a également un très vaste déploiement, et quasiment aucune citation. À l'inverse, un RFC qui a un nombre de citations record, le RFC 5326, n'a quasiment pas eu de déploiement, mais a excité beaucoup de chercheurs académiques (et même médiatique, vu son sujet cool), ce qui explique le nombre de citations. Bref, le monde de la recherche et celui des réseaux n'ont pas des jugements identiques.

On avait noté que le RFC 2267 était beaucoup cité. Or, il n'est plus d'actualité, ayant officiellement été remplacé par le RFC 2827. Mais les citations privilégient la première occurrence, et ne reflètent donc pas l'état actuel de la normalisation.

Bon, on a vu que les citations dans le monde de la recherche ne reflétaient pas réellement l'« importance » d'un RFC. Et le nombre d'occurrences dans un moteur de recherche, c'est mieux ? L'auteur a donc compté le nombre de références aux RFC dans Google et Bing. Il y a quelques pièges. Par exemple, en cherchant « RFC8441 », Bing trouve beaucoup de références à ce RFC 8441, qui parle de WebSockets, mais c'est parce qu'il compte des numéros de téléphone se terminant par 8441, ou des adresses postales « 8441 Main Street ». Google n'a pas cette amusante bogue. Ceci dit, il est noyé par le nombre très élevé de miroirs des RFC, puisque l'IETF permet et encourage ces copies multiples (contrairement aux organisations de normalisation fermées comme l'AFNOR). Ceci dit, les RFC 8441 et RFC 8471 sont plus souvent cités, notamment car ils apparaissent fréquemment dans les commentaires du code source d'un logiciel qui les met en œuvre. Le « test Google » est donc assez pertinent pour évaluer le déploiement effectif.

Quelles conclusions en tirer (section 6 du RFC) ? D'abord, si on trouve le délai de publication trop long, il faut se focaliser sur le temps passé dans le groupe de travail, car c'est clairement le principal contributeur au délai. [En même temps, c'est logique, c'est là où a lieu le « vrai » travail.] Malheureusement, c'est là où le DataTracker contient le moins d'information alors que les phases « bureaucratiques » comme le temps passé à l'IESG bénéficient d'une traçabilité plus détaillée. Ainsi, on ne voit pas les derniers appels (Last Calls) internes aux groupes de travail. Bien sûr, on pourrait rajouter ces informations mais attention, cela ferait une charge de travail supplémentaire pour les présidents des groupes de travail (des volontaires). Récolter des données pour faire des études, c'est bien, mais cela ne doit pas mener à encore plus de bureaucratie.

Du fait que l'étude ne porte que sur des RFC effectivement publiés, elle pourrait être accusée de sensibilité au biais du survivant. Peut-être que les RFC qui n'ont pas été publiés auraient aussi des choses à nous dire ? L'abandon d'un projet de normalisation est fréquent (pensons par exemple à DBOUND ou RPZ) et parfaitement normal et souhaitable ; toutes les idées ne sont pas bonnes et le travail de tri est une bonne chose. Mais, en n'étudiant que les RFC publiés, on ne peut pas savoir si le tri n'a pas été trop violent et si de bonnes idées n'en ont pas été victimes. L'IETF ne documente pas les abandons et une étude de drafts sans successeur pourrait être intéressante.

Pour terminer, je me suis amusé à regarder ce qu'il en était des trois RFC que j'ai écrit :

  • Le RFC 7626 a été publié moins de deux ans après le premier document ce qui, on l'a vu, est inférieur à la moyenne. Fortement poussé par l'engagement de l'IETF à agir après les révélations de Snowden, il n'a guère connu d'obstacles.
  • Le RFC 7816 a pris deux ans. Il a passé à peine plus d'un mois chez le RFC Editor, ce qui est très peu. Comme le précédent, il faisait partie d'un projet porteur, visant à améliorer le respect de la vie privée dans le DNS. Il a connu des objections techniques mais son statut « Expérimental » ne nécessite pas une solution parfaite. Une partie du temps a été passée dans un problème dont le RFC 8963 ne parle pas : une histoire de brevets et de négociations juridico-politiques.
  • Le RFC 8020 est le seul des trois à être sur le chemin des normes, en théorie bien plus exigeant. Mais il bat tous les records en n'ayant mis qu'un an à être publié. C'est en partie parce qu'il est simple, et relativement consensuel (il y a quand même eu des objections, par exemple sur le danger qu'il pouvait poser si la zone n'était pas signée avec DNSSEC).

Téléchargez le RFC 8963


L'article seul

RFC 8961: Requirements for Time-Based Loss Detection

Date de publication du RFC : Novembre 2020
Auteur(s) du RFC : M. Allman (ICSI)
Réalisé dans le cadre du groupe de travail IETF tcpm
Première rédaction de cet article le 24 novembre 2020


Vous savez certainement que l'Internet fait circuler les données sous forme de paquets indépendants, et qu'un paquet peut toujours être perdu, par exemple parce qu'un rayon cosmique agressif est passé à ce moment-là ou, moins spectaculaire, parce que les files d'un routeur étaient pleines et qu'il a dû se résigner à laisser tomber le paquet. Des protocoles existent donc pour gérer ces pertes de paquets, ce qui implique de les détecter. Et comment sait-on qu'un paquet a été perdu ? C'est plus complexe que ça n'en a l'air, et ce RFC tente d'établir un cadre générique pour la détection de pertes.

Ne tournons pas autour du pot : la seule façon fiable de savoir si un paquet a été perdu, c'est d'attendre qu'il arrive (ce que le RFC nomme, dans son titre, time-based loss detection) et, s'il n'arrive pas, de le déclarer perdu. Plus précisément, pour l'émetteur (car le récepteur ne sait pas forcément qu'on lui a envoyé un paquet), on émet un paquet et on attend une confirmation qu'il a été reçu (avec TCP, cette confirmation sera le ACK, avec le DNS sur UDP, ce sera la réponse DNS). Si la confirmation n'a pas été reçue, s'il y a timeout, c'est qu'un paquet (la demande, ou bien l'accusé de réception) n'est pas arrivé. Mais attendre combien de temps ? Si on attend peu de temps (mettons 100 millisecondes), on risque de considérer le paquet comme perdu, alors que le voyage était simplement un peu long (en 100 ms, vous ne pouvez même pas faire un aller-retour entre la France et les Philippines, ne serait-ce qu'à cause de la limite de la vitesse de la lumière). Et si on attend longtemps (mettons 5 secondes), on ne pourra pas réagir rapidement aux pertes, et la latence perçue par l'utilisateur sera insupportable (la sensation de « vitesse » pour l'utilisateur dépend davantage de la latence que de la capacité). Il faut donc faire un compromis entre réactivité et justesse.

Il existe d'autres méthodes que l'attente pour détecter des pertes, par exemple TCP et SCTP utilisent aussi les accusés de réception sélectifs (RFC 2018, RFC 4960 dans sa section 3.3.4 et RFC 6675) mais aucune de ces autres méthodes ne détecte toutes les pertes, et la détection par absence de réponse reste donc indispensable.

L'Internet, vous le savez, est un ensemble compliqué de réseaux, reliés par des câbles très variés (et quelques liens radio), avec des débits bien différents et changeant d'un moment à l'autre. Tout chemin d'une machine à l'autre va avoir un certain nombre de propriétés (qui varient dans le temps) telles que la latence ou la capacité. Et le taux de perte de paquets, qui nous intéresse ici. (Voir aussi le RFC 7680.)

Notre RFC suppose que la perte de paquets est une indication de congestion (RFC 5681). Ce n'est pas vrai à 100 %, surtout sur les liens radio, où des paquets peuvent être détruits par des perturbations électro-magnétiques sans qu'il y ait congestion, mais c'est quand même proche de la vérité.

Et, au fait, pourquoi détecter la perte de paquets ? Pourquoi ne pas tout simplement ignorer le problème ? Deux raisons :

  • Pour pouvoir envoyer des données de manière fiable, il faut détecter les paquets manquants, afin de pouvoir demander leur retransmission. C'est ce que fait TCP, par exemple, sans quoi on ne pourrait pas transmettre un fichier en étant sûr qu'il arrive complet.
  • Puisque la perte de paquets signale en général qu'il y a congestion, détecter cette perte permet de ralentir l'envoi de données et donc de lutter contre la congestion.

Résultat, beaucoup de protocoles ont un mécanisme de détection de pertes : TCP (RFC 6298), bien sûr, mais aussi SCTP (RFC 4960), SIP (RFC 3261), etc.

Le RFC cite souvent l'article de Allman, M. et Paxson V, « On Estimating End-to-End Network Path Properties » donc vous avez le droit d'interrompre votre lecture ici pour lire cet article avant de continuer.

Reprenons, avec la section 2 du RFC, qui explique les buts et non-buts de ce RFC :

  • Ce RFC ne change aucun protocole existant, les RFC restent les mêmes, vous n'avez pas à réapprendre TCP,
  • Ce RFC vise surtout les RFC futurs, qui auraient intérêt à se conformer aux principes énoncés ici (c'est par exemple le cas si vous développez un nouveau protocole au-dessus d'UDP et que vous devez donc mettre en œuvre la détection de pertes),
  • Ce RFC n'impose pas des règles absolues, il donne des principes qui marchent dans la plupart des cas, c'est tout.

Nous arrivons maintenant à la section 4 du RFC, qui liste les exigences auxquelles doivent obéir les mécanismes de détection de pertes. (En pratique, les mécanismes existants collent déjà à ces exigences mais elles n'avaient pas été formalisées.) Un petit retour sur la notion de latence, d'abord. On veut savoir combien de temps attendre avant de déclarer un paquet perdu. Cette durée se nomme RTO (Retransmission TimeOut). Les latences dans l'Internet étant extrêmement variables, il serait intéressant de faire dépendre le RTO de la latence. Mais quelle latence ? Le temps d'aller-retour entre deux machines est le temps qu'il faut à un paquet IP pour aller de la machine A à la machine B plus le temps qu'il faut à un paquet IP pour aller de B à A. On ne peut pas mesurer directement ce temps, car le temps de traitement dans la machine B n'est pas forcément connu. Dans le cas de l'ICMP Echo utilisé par ping, ce temps est considéré comme négligeable, ce qui est assez correct si l'amer est une machine Unix dont le noyau traite l'ICMP Echo. Cela l'est moins si l'amer est un routeur qui traite les réponses ICMP à sa plus basse priorité. Et cela l'est encore moins si un client DNS essaie d'estimer la latence vers un résolveur. Si le résolveur avait la réponse dans sa mémoire, le temps de traitement est faible. S'il devait demander aux serveurs faisant autorité, ce temps peut être bien supérieur à la latence. Le RFC distingue donc RTT (Round-Trip Time), la vraie latence, et FT (Feedback Time) qui est ce qu'affichent ping, dig et autres outils. La machine qui veut savoir combien de temps attendre une réaction de la machine en face, avant de déclarer qu'un paquet est perdu, a tout intérêt à avoir une idée du FT.

Certaines des exigences du RFC sont quantitatives. Ainsi, tant qu'on n'a pas mesuré le FT (au début de la session), le RFC requiert que le RTO soit d'au moins une seconde, pour ne pas surcharger le réseau avec des réémissions, et parce que la perte étant interprétée comme un signe de congestion, un RTO trop faible amènerait à réduire la quantité de données qu'on peut envoyer, diminuant ainsi la capacité effective. Sans compter le problème de l'ambiguïté. Si on a réémis un paquet, et qu'une réponse revient, était-elle pour le paquet initial ou pour la réémission ? Dans le doute, il ne faut pas utiliser le temps mesuré pour changer son estimation du RTO. Et c'est une des raisons pour lesquelles il faut prendre son temps pour les premières mesures. Ah, au fait, pourquoi une seconde et pas 0,75 ou 1,25 ? Cela vient d'une analyse quantitative des RTT typiques de l'Internet, exposée dans l'annexe A du RFC 6298.

Après plusieurs mesures, on connait mieux le FT et on peut abaisser le RTO. Il n'y a pas de durée minimale, donc on peut s'approcher de zéro tant qu'on veut.

Le RFC dit aussi que le RTO (Retransmission TimeOut, le délai d'attente) doit se mesurer à partir de plusieurs observations du FT, pour éviter une mesure faussée par un cas extrême. Et il faut refaire ces observations souvent car le réseau change ; les vieilles mesures n'ont pas d'intérêt. C'est ce que fait TCP avec son smoothed RTT, utilisant la moyenne mobile exponentielle (RFC 6298).

L'Internet étant ce qu'il est, il est recommandé de s'assurer qu'un méchant ne puisse pas facilement fausser cette mesure en injectant des faux paquets. Pour TCP, cette assurance est donnée par le caractère imprévisible du numéro de séquence initial, si l'attaquant n'est pas sur le chemin (RFC 5961). Si on veut une protection contre un attaquant situé sur le chemin, il faut de la cryptographie.

Le RFC recommande de refaire l'évaluation du RTO au moins une fois par RTT et, de préference, aussi souvent que des données sont échangées. TCP le fait une fois par RTT et, si on utilise le RFC 7323, à chaque accusé de réception.

Le RFC rappelle qu'en l'absence d'indication du contraire, une perte de paquets doit être considérée comme un indicateur de congestion, et qu'il faut donc ralentir (RFC 5681, pour le cas de TCP).

Et, dernière exigence, en cas de perte de paquets, le RTO doit croitre exponentiellement, pour s'ajuster rapidement à la charge du réseau. On pourra réduire le RTO lorsqu'on aura la preuve que les paquets passent et qu'on reçoit des accusés de réception et/ou des réponses. Dans tous les cas, le RFC limite le RTO à 60 secondes, ce qui est déjà énorme (je n'ai jamais vu une réponse revenir après une durée aussi longue).

Enfin, la section 5 discute des exigences posées et de leurs limites. La tension entre le désir de réactivité (un RTO faible) et celui de mesures correctes (un RTO plus important, pour être raisonnablement sûr de ne pas conclure à tort qu'il y a eu une perte) est une tension fondamentale : on n'aura jamais de solution parfaite, juste des compromis. Il existe des techniques qui permettent de mieux détecter les pertes (l'algorithme Eifel - RFC 3522, le F-RTO du RFC 5682, DSACK - RFC 2883 et RFC 3708…) mais elles ne sont pas complètes et l'attente bornée par le RTO reste donc nécessaire en dernier recours.

Le noyau Linux a mis en œuvre plusieurs techniques non normalisées (par exemple des modifications du RFC 6298) sans que cela ne crée apparemment de problèmes. D'une manière générale, les algorithmes de détection de pertes de TCP, SCTP ou QUIC sont largement compatibles avec les exigences de ce RFC.


Téléchargez le RFC 8961


L'article seul

RFC 8959: The "secret-token" URI Scheme

Date de publication du RFC : Janvier 2021
Auteur(s) du RFC : M. Nottingham
Pour information
Première rédaction de cet article le 30 janvier 2021


Enregistrer une clé privée (ou tout autre secret) dans un dépôt public (par exemple sur GitHub) est un gag courant. La nature des VCS fait qu'il est souvent difficile de retirer cette clé. Pour limiter un peu les dégâts, ce RFC enregistre un nouveau plan d'URI, secret-token:, qui permettra de marquer ces secrets, autorisant par exemple le VCS à rejeter leur enregistrement.

Ce RFC se focalise sur les secrets qui sont « au porteur » (bearer tokens) c'est-à-dire que leur seule connaissance suffit à les utiliser ; aucune autre vérification n'est faite. Ce peut être un mot de passe, une clé d'API, etc. Voici un exemple avec une clé GitLab (je vous rassure, je l'ai révoquée depuis) : gitlab-api-key.png

La révélation de ces secrets via un enregistrement accidentel, par exemple dans un dépôt logiciel public, est un grand classique. Lisez par exemple le témoignage « I Published My AWS Secret Key to GitHub », ou une aventure similaire, « Exposing your AWS access keys on Github can be extremely costly. A personal experience. », un avertissement de Github à ses utilisateurs, ou enfin une étude détaillée publiée à NDSS. Un git add de trop (ou bien un secret mis dans le code source) et, au prochain commit, le secret se retrouve dans le dépôt, puis publié au premier git push.

L'idée de ce RFC est de marquer clairement ces secrets pour que, par exemple, des audits du dépôt les repèrent plus facilement. Ou pour que les systèmes d'intégration continue puissent les rejeter automatiquement.

Le plan d'URI est simple (section 2 du RFC) : la chaîne secret-token: suivie du secret. Un exemple serait secret-token:E92FB7EB-D882-47A4-A265-A0B6135DC842%20foo (notez l'échappement du caractère d'espacement). Par exemple avec les secrets au porteur pour HTTP du RFC 6750, cela donnerait un envoi au serveur :

GET /authenticated/stuff HTTP/1.1
Host: www.example.com
Authorization: Bearer secret-token:E92FB7EB-D882-47A4-A265-A0B6135DC842%20foo
  

Le plan secret-token: est désormais dans le registre IANA.

Je n'ai pas encore trouvé d'organisation qui distribue ces secrets au porteur en utilisant ce plan d'URI mais il semble que GitHub soit tenté.


Téléchargez le RFC 8959


L'article seul

RFC 8958: Updated Registration Rules for URI.ARPA

Date de publication du RFC : Décembre 2020
Auteur(s) du RFC : T. Hardie
Première rédaction de cet article le 3 décembre 2020


Le domaine uri.arpa a été créé par le RFC 3405 pour stocker dans le DNS des règles gouvernant la résolution d'URI via des enregistrements NAPTR. Ce nouveau RFC introduit un très léger changement des procédures d'enregistrement dans ce domaine uri.arpa.

À titre d'exemple de ces règles, vous pouvez regarder la règle pour les URN.

En effet, le RFC 3405 parlait d'un IETF tree qui avait été créé par le RFC 2717 (dans sa section 2.2) mais supprimé par le RFC 4395. Notre RFC 8958 prend simplement acte de cette suppression et retire les mentions IETF tree du RFC 3405.


Téléchargez le RFC 8958


L'article seul

RFC 8956: Dissemination of Flow Specification Rules for IPv6

Date de publication du RFC : Décembre 2020
Auteur(s) du RFC : C. Loibl (next layer Telekom GmbH), R. Raszuk (NTT Network Innovations), S. Hares (Huawei)
Chemin des normes
Réalisé dans le cadre du groupe de travail IETF idr
Première rédaction de cet article le 1 janvier 2021


Il existe un mécanisme de diffusion des règles de filtrage IP, FlowSpec, normalisé dans le RFC 8955. Ce mécanisme permet d'utiliser BGP pour envoyer à tous les routeurs d'un AS les mêmes règles, ce qui est très pratique, par exemple pour bloquer une attaque sur tous les routeurs d'entrée du domaine. Le FlowSpec original ne gérait qu'IPv4, voici son adaptation à IPv6.

En fait, il n'y avait pas grand'chose à faire. Après tout, IPv6 n'est pas un nouveau protocole, juste une nouvelle version du même protocole IP. Des concepts cruciaux, comme la définition d'un préfixe en préfixe/longueur, ou comme la règle du préfixe le plus spécifique restent les mêmes. Ce RFC est donc assez simple. Il ne reprend pas l'essentiel du RFC 8955, il ajoute juste ce qui est spécifique à IPv6. (À l'IETF, il avait été proposé de fusionner les deux documents mais, finalement, il y a un RFC générique + IPv4, le RFC 8955, et un spécifique à IPv6, notre RFC 8956.)

Petit rappel sur FlowSpec : les règles de filtrage sont transportées en BGP, les deux routeurs doivent annoncer la capacité « multi-protocole » (les capacités sont définies dans le RFC 5492) définie dans le RFC 4760, la famille de l'adresse (AFI pour Address Family Identifier) est 2 (qui indique IPv6) et le SAFI (Subsequent Address Family Identifier) est le même qu'en IPv4, 133.

Le gros de notre RFC (section 3) est l'énumération des différents types d'identifiant d'un trafic à filtrer. Ce sont les mêmes qu'en IPv4 mais avec quelques adaptations. Ainsi, le type 1, « préfixe de destination » permet par exemple de dire à ses routeurs « jette à la poubelle tout ce qui est destiné à 2001:db:96b:1::/64 ». Son encodage ressemble beaucoup à celui en IPv4 mais avec un truc en plus, le décalage (offset) depuis le début de l'adresse. Avec un décalage de 0, on est dans le classique préfixe/longueur mais on peut aussi ignorer des bits au début de l'adresse avec un décalage non-nul.

Le type 3, « protocole de niveau supérieur » est l'un de ceux dont la définition IPv6 est la plus différente de celle d'IPv4. En effet, IPv6 a les en-têtes d'extension (RFC 8200, section 4), une spécificité de cette version. Le champ « Next header » d'IPv6 (RFC 8200, section 3) n'a pas la même sémantique que le champ « Protocole » d'IPv4 puisqu'il peut y avoir plusieurs en-têtes avant celui qui indique le protocole de couche 4 (qui sera typiquement TCP ou UDP). Le choix de FlowSpec est que le type 3 désigne le dernier champ « Next header », celui qui identifie le protocole de transport, ce qui est logique, c'est bien là-dessus qu'on veut filtrer, en général. Rappelez-vous au passage que d'analyser les en-têtes d'extension IPv6 pour arriver au protocole de transport n'est hélas pas trivial. Et il y a d'autres pièges, expliqués dans les RFC 7112 et RFC 8883.

Autre type d'identifiant qui est différent en IPv6, le type 7, ICMP. Si le protocole est très proche, les types et les codes ne sont en effet pas les mêmes. Ainsi, l'un des types les plus célèbres, Echo Request, utilisé par ping, vaut 8 en IPv4 et 138 en IPv6.

Les autres types se comportent en IPv6 exactement comme en IPv4. Par exemple, si vous voulez dire à vos routeurs de filtrer sur le port, les types 4, 5 et 6, dédiés à cet usage, ont le même encodage et la même sémantique qu'en IPv4 (ce qui est logique, puisque le port est un concept de couche 4).

FlowSpec est aujourd'hui largement mis en œuvre dans les logiciels de routage, y compris en IPv6. Vous pouvez consulter à ce sujet le rapport de mise en œuvre. Si vous voulez jouer avec FlowSpec, le code Python d'exemple de l'annexe A est disponible en ligne.


Téléchargez le RFC 8956


L'article seul

RFC 8955: Dissemination of Flow Specification Rules

Date de publication du RFC : Décembre 2020
Auteur(s) du RFC : C. Loibl (next layer Telekom GmbH), S. Hares (Huawei), R. Raszuk (Bloomberg LP), D. McPherson (Verisign), M. Bacher (T-Mobile Austria)
Chemin des normes
Réalisé dans le cadre du groupe de travail IETF idr
Première rédaction de cet article le 1 janvier 2021


Lorsqu'on a un grand réseau compliqué, diffuser à tous les routeurs des règles de filtrage, par exemple pour faire face à une attaque par déni de service, peut être complexe. Il existe des logiciels permettant de gérer ses routeurs collectivement mais ne serait-il pas plus simple de réutiliser pour cette tâche les protocoles existants et notamment BGP ? Après tout, des règles de filtrage sont une forme de route. On profiterait ainsi des configurations existantes et de l'expérience disponible. C'est ce que se sont dit les auteurs de ce RFC. « FlowSpec » (nom officieux de cette technique) consiste à diffuser des règles de traitement du trafic en BGP, notamment à des fins de filtrage. Ce RFC remplace le RFC FlowSpec original, le RFC 5575, le texte ayant sérieusement changé (mais le protocole est presque le même).

Les routeurs modernes disposent en effet de nombreuses capacités de traitement du trafic. Outre leur tâche de base de faire suivre les paquets, ils peuvent les classifier, limiter leur débit, jeter certains paquets, etc. La décision peut être prise en fonction de critères tels que les adresses IP source et destination ou les ports source et destination. Un flot (flow) est donc défini comme un tuple rassemblant les critères d'acceptation d'un paquet IP. Notre RFC 8955 encode ces critères dans un attribut NLRI (Network Layer Reachability Information est décrit dans la section 4.3 du RFC 4271) BGP, de manière à ce qu'ils puissent être transportés par BGP jusqu'à tous les routeurs concernés. Sans FlowSpec, il aurait fallu qu'un humain ou un programme se connecte sur tous les routeurs et y rentre l'ACL concernée.

Pour reconnaitre les paquets FlowSpec, ils sont marqués avec le SAFI (concept introduit dans le RFC 4760) 133 pour les règles IPv4 (et IPv6 grâce au RFC 8956) et 134 pour celles des VPN.

La section 4 du RFC donne l'encodage du NLRI. Un message UPDATE de BGP est utilisé, avec les attributs MP_REACH_NLRI et MP_UNREACH_NLRI du RFC 4760 et un NLRI FlowSpec. Celui-ci compte plusieurs couples {type, valeur} où l'interprétation de la valeur dépend du type (le type est codé sur un octet). Voici les principaux types :

  • Type 1 : préfixe de la destination, représenté par un octet pour la longueur, puis le préfixe, encodé comme traditionnellement en BGP (section 4.3 du RFC 4271). Ainsi, le préfixe 10.0.1.0/24 sera encodé (noté en hexadécimal) 01 18 0a 00 01 (type 1, longueur 24 - 18 en hexa - puis le préfixe dont vous noterez que seuls les trois premiers octets sont indiqués, le dernier n'étant pas pertinent ici).
  • Type 2 : préfixe de la source (lors d'une attaque DoS, il est essentiel de pouvoir filtrer aussi sur la source).
  • Type 3 : protocole (TCP, UDP, etc).
  • Types 4, 5 et 6 : port (source ou destination ou les deux). Là encore, c'est un critère très important lors du filtrage d'une attaque DoS. Attention, cela suppose que le routeur soit capable de sauter les en-têtes IP (y compris des choses comme ESP) pour aller décoder TCP ou UDP. Tous les routeurs ne savent pas faire cela (en IPv6, c'est difficile).
  • Type 9 : options activées de TCP, par exemple uniquement les paquets RST.

Tous les types de composants sont enregistrés à l'IANA.

Une fois qu'on a cet arsenal, à quoi peut-on l'utiliser ? La section 5 détaille le cas du filtrage. Autrefois, les règles de filtrage étaient assez statiques (je me souviens de l'époque où tous les réseaux en France avaient une ACL, installée manuellement, pour filtrer le réseau de l'EPITA). Aujourd'hui, avec les nombreuses DoS qui vont et viennent, il faut un mécanisme bien plus dynamique. La première solution apparue a été de publier via le protocole de routage des préfixes de destination à refuser. Cela permet même à un opérateur de laisser un de ses clients contrôler le filtrage, en envoyant en BGP à l'opérateur les préfixes, marqués d'une communauté qui va déclencher le filtrage (à ma connaissance, aucun opérateur n'a utilisé cette possibilité, en raison du risque qu'une erreur du client ne se propage). De toute façon, c'est très limité en cas de DoS (par exemple, on souhaite plus souvent filtrer sur la source que sur la destination). Au contraire, le mécanisme FlowSpec de ce RFC donne bien plus de critères de filtrage.

Cela peut d'ailleurs s'avérer dangereux : une annonce FlowSpec trop générale et on bloque du trafic légitime. C'est particulièrement vrai si un opérateur accepte du FlowSpec de ses clients : il ne faut pas permettre à un client de filtrer les autres. D'où la procédure suggérée par la section 6, qui demande de n'accepter les NLRI FlowSpec que s'ils contiennent un préfixe de destination, et que ce préfixe de destination est routé vers le même client qui envoie le NLRI (notez que cette règle a été assouplie par le RFC 9117). Ainsi, un client ne peut pas déclencher le filtrage d'un autre puisqu'il ne peut influencer que le filtrage des paquets qui lui sont destinés.

Au fait, en section 5, on a juste vu comment indiquer les critères de classification du trafic qu'on voulait filtrer. Mais comment indiquer le traitement qu'on veut voir appliqué aux paquets ainsi classés ? (Ce n'est pas forcément les jeter : on peut vouloir être plus subtil.) FlowSpec utilise les communautés étendues du RFC 4360. La valeur sans doute la plus importante est 0x8006, traffic-rate, qui permet de spécifier un débit maximal pour les paquets qui correspondent aux critères mis dans le NLRI. Le débit est en octets/seconde. En mettant zéro, on demande à ce que tous les paquets classés soient jetés. (Mais on peut aussi indiquer des paquets/seconde, c'est une nouveauté de ce RFC.) Les autres valeurs possibles permettent des actions comme de modifier les bits DSCP du trafic classé.

Comme toutes les armes, celle-ci peut être dangereuse pour celui qui la manipule. La section 12 est donc la bienvenue, pour avertir de ces risques. Par exemple, comme indiqué plus haut, si on permet aux messages FlowSpec de franchir les frontières entre AS, un AS maladroit ou méchant risque de déclencher un filtrage chez son voisin. D'où l'importance de la validation, n'accepter des règles FlowSpec que pour les préfixes de l'AS qui annonce ces règles.

Ensuite, comme tous les systèmes de commande des routeurs à distance, FlowSpec permet de déclencher un filtrage sur tous les routeurs qui l'accepteront. Si ce filtrage est subtil (par exemple, filtrer tous les paquets plus grands que 900 octets), les problèmes qui en résultent seront difficiles à diagnostiquer.

FlowSpec a joué un rôle important dans la panne Level 3 / CenturyLink d'août 2020. Et, avant cela, dans la panne CloudFlare du 3 mars 2013, où des critères incorrects (une taille de paquet supérieure au maximum permis par IP) avaient été envoyés à tous les routeurs. Ce n'est pas une bogue de FlowSpec : tout mécanisme de diffusion automatique de l'information à N machines différentes a le même problème potentiel. Si l'information était fausse, le mécanisme de diffusion transmet l'erreur à tous... (Dans le monde des serveurs Unix, le même problème peut se produire avec des logiciels comme Chef ou Puppet. Lisez un cas rigolo avec Ansible.) Comme le prévient notre RFC : « When automated systems are used, care should be taken to ensure the correctness of the automated system. » Toutefois, contrairement à ce que laisse entendre le RFC, il n'y a pas que les processus automatiques qui injectent des erreurs : les humains le font aussi.

Si vous voulez en apprendre plus sur FlowSpec :

Si vous vous intéressez à l'utilisation de BGP lors d'attaques par déni de service, vous pouvez aussi consulter les RFC 3882 et RFC 5635.

Les changements depuis la norme originale, le RFC 5575, sont résumés dans l'annexe B. Parmi les principaux :

  • Le RFC 7674 a été intégré ici, et n'est donc plus d'actualité,
  • Les comparaisons entre les valeurs indiquées dans les règles FlowSpec et les paquets qu'on voit passer sont désormais spécifiées plus strictement, avec du code d'exemple (en Python) dans l'annexe A,
  • Les actions fondées sur le trafic peuvent désormais s'exprimer en nombre de paquets et plus seulement en nombre d'octets (certaines attaques par déni de service sont dangereuses par leur nombre de paquets, même si ces paquets sont petits).

FlowSpec est utilisé depuis dix ans et de nombreuses mises en œuvre existent (cf. la liste). Sur les Juniper, on peut consulter leur documentation en ligne.


Téléchargez le RFC 8955


L'article seul

RFC 8953: Coordinating Attack Response at Internet Scale 2 (CARIS2) Workshop Report

Date de publication du RFC : Décembre 2020
Auteur(s) du RFC : K. Moriarty (Dell Technologies)
Pour information
Première rédaction de cet article le 17 décembre 2020


Voici le compte-rendu de la deuxième édition de l'atelier CARIS (Coordinating Attack Response at Internet Scale), un atelier de l'ISOC consacré à la défense de l'Internet contre les différentes attaques possibles, par exemple les dDoS. Cet atelier s'est tenu à Cambridge en mars 2019. Par rapport au premier CARIS, documenté dans le RFC 8073, on note l'accent mis sur les conséquences du chiffrement, désormais largement répandu.

Les problèmes de sécurité sur l'Internet sont bien connus. C'est tous les jours qu'on entend parler d'une attaque plus ou moins réussie contre des infrastructures du réseau. Ainsi, Google a été victime d'une grosse attaque en 2017 (mais qui n'a été révélée que des années après). Mais, pour l'instant, nous n'avons pas de solution miracle. L'idée de base des ateliers CARIS est de rassembler aussi bien des opérateurs de réseau, qui sont « sur le front » tous les jours, que des chercheurs, des fournisseurs de solutions de défense, et des CSIRT, pour voir ensemble ce qu'on pouvait améliorer. Pour participer, il fallait avoir soumis un article, et seules les personnes dont un article était accepté pouvait venir, garantissant un bon niveau de qualité aux débats, et permettant de limiter le nombre de participants, afin d'éviter que l'atelier ne soit juste une juxtaposition de discours.

La section 2 du RFC présente les quatorze papiers qui ont été acceptés. On les trouve en ligne.

Le but de l'atelier était d'identifier les points sur lesquels des progrès pourraient être faits. Par exemple, tout le monde est d'accord pour dire qu'on manque de professionnel·le·s compétent·e·s en cybersécurité mais il ne faut pas espérer de miracles : selon une étude, il manque trois millions de personnes dans ce domaine et il n'y a simplement aucune chance qu'on puisse les trouver à court terme. Plus réaliste, l'atelier s'est focalisé sur le déploiement du chiffrement (TLS 1.3, normalisé dans le RFC 8446, le futur - à l'époque - QUIC, et pourquoi pas le TCPcrypt du RFC 8548), déploiement qui peut parfois gêner la détection de problèmes, et sur les mécanismes de détection et de prévention. Une importance particulière était donnée au passage à l'échelle (on ne peut plus traiter chaque attaque individuellement et manuellement, il y en a trop).

Bon, maintenant, les conclusions de l'atelier (section 4). Première session, sur l'adoption des normes. C'est une banalité à l'IETF que de constater que ce n'est pas parce qu'on a normalisé une technique de sécurité qu'elle va être déployée. Beaucoup de gens aiment râler contre l'insécurité de l'Internet mais, dès qu'il s'agit de dépenser de l'argent ou du temps pour déployer les solutions de sécurité, il y a moins d'enthousiasme. (J'écris cet article au moment de la publication de la faille de sécurité SadDNS. Cela fait plus de dix ans qu'on a une solution opérationnelle contre la famille d'attaques dont SadDNS fait partie, DNSSEC et, pourtant, DNSSEC n'est toujours pas déployé sur la plupart des domaines.) Commençons par un point optimiste : certaines des technologies de sécurité de l'IETF ont été largement déployées, comme SSL (RFC 6101), remplacé il y a quinze ans par TLS (RFC 8446). L'impulsion initiale venait clairement du secteur du commerce électronique, qui voulait protéger les numéros des cartes de crédit. Lié à TLS, X.509 (RFC 5280) est aussi un succès. Cette fois, l'impulsion initiale est plutôt venue des États. (X.509 doit être une des très rares normes UIT survivantes sur l'Internet.)

Moins directement lié à la sécurité, SNMP (RFC 3410) est aussi un succès, même s'il est en cours de remplacement par les techniques autour de YANG comme RESTCONF. Toujours pour la gestion de réseaux, IPfix (RFC 7011) est également un succès, largement mis en œuvre sur beaucoup d'équipements réseau.

Par contre, il y a des semi-échecs et des échecs. Le format de description d'incidents de sécurité IODEF (RFC 7970) ne semble pas très répandu. (Il a un concurrent en dehors de l'IETF, STIX - Structured Threat Information eXpression, qui ne semble pas mieux réussir.) IODEF est utilisé par des CSIRT mais souffre de son niveau de détail (beaucoup d'opérationnels veulent des synthèses, pas des données brutes) et, comme toutes les techniques d'échange d'information sur les questions de sécurité, souffre également des problèmes de confiance qui grippent la circulation de l'information. Autre technique de sécurité excellente mais peu adoptée, DANE (RFC 7671). Malgré de nombreux efforts de promotion (comme https://internet.nl, le blog que vous lisez a une note de 93 %, car la configuration TLS est tolérante) et même avec une reconnaissance légale partielle en Allemagne, DANE reste très minoritaire.

Un autre cas fameux de non-succès, même s'il n'est pas directement lié à la sécurité, est IPv6 (RFC 8200).

Deuxième session, les protocoles nouveaux. L'atelier s'est penché sur le format MUD (Manufacturer Usage Description, RFC 8520) qui pourrait aider à boucher une petite partie des trous de sécurité de l'Internet des objets. Il a également travaillé l'échange de données et les problèmes de confiance qu'il pose. Comme à CARIS 1, plusieurs participants ont noté que cet échange de données reste gouverné par des relations personnelles. La confiance ne passe pas facilement à l'échelle. L'échange porte souvent sur des IOC et un standard possible a émergé, MISP.

Une fois le problème détecté, il reste à coordonner la réaction, puisque l'attaque peut toucher plusieurs parties. C'est encore un domaine qui ne passe guère à l'échelle. L'Internet n'a pas de mécanisme (technique mais surtout humain) pour coordonner des centaines de victimes différentes. Des tas d'obstacles à la coordination ont été mentionnés, des outils trop difficiles à utiliser en passant par les obstacles frontaliers à l'échange, les obligations légales qui peuvent interdire l'échange de données, et bien sûr le problème récurrent de la confiance. Vous vous en doutez, pas plus qu'au premier atelier, il n'y aura eu de solution parfaite découverte pendant les sessions.

La session sur la surveillance a vu plusieurs discussions intéressantes. Ce fut le cas par exemple du problème de la réputation des adresses IP. Ces adresses sont souvent des IOC et on se les échange souvent, ce qui soulève des questions liées à la vie privée. (Un des papiers de l'atelier est « Measured Approaches to IPv6 Address Anonymization and Identity Association », de David Plonka et Arthur Berger , qui explique la difficulté de l'« anonymisation » des adresses IP si on veut qu'elles restent utiles pour les opérationnels.) L'exploitation correcte de ces adresses IP nécessite de connaitre les plans d'adressage utilisés (si une adresse IPv6 se comporte mal, faut-il bloquer tout le préfixe /64 ? Tout le /48 ?). Il n'y a pas de ressources publiquement disponibles à ce sujet, qui permettrait de connaitre, pour une adresse IP donnée, l'étendue du préfixe englobant. (Je ne parle évidemment pas du routage, pour lequel ces bases existents, mais de la responsabilité.) Une des suggestions était d'étendre les bases des RIR. Une autre était de créer une nouvelle base. Le problème est toujours le même : comment obtenir que ces bases soient peuplées, et correctement peuplées ?

Une des questions amusantes lorsqu'on essaie de déboguer un problème de communication entre deux applications est de savoir quoi faire si la communication est chiffrée. Il n'est évidemment pas question de réclamer une porte dérobée pour court-circuiter le chiffrement, cela créerait une énorme faille de sécurité. Mais alors comment faire pour savoir ce qui se dit ? On a besoin de la coopération de l'application. Mais toutes les applications ne permettent pas facilement de journaliser les informations importantes et, quand elles le font, ce n'est pas dans un format cohérent. D'où une suggestion lors de l'atelier de voir s'il ne serait pas envisageable de mettre cette fonction dans les compilateurs, pour avoir un mécanisme de journalisation partout disponibles.

Pendant qu'on parle de chiffrement, une autre question est celle de l'identification d'une machine ou d'un protocole par le fingerprinting, c'est-à-dire en observant des informations non chiffrées (taille des paquets, temps de réponse, variations permises par le protocole, etc). Le fingerprinting pose évidemment des gros risques pour la vie privée et beaucoup de travaux sur des protocoles récents (comme QUIC) visaient à limiter son efficacité.

Pour résumer l'atelier (section 6 du RFC), plusieurs projets ont été lancés pour travailler sur des points soulevés dans l'atelier. À suivre, donc.


Téléchargez le RFC 8953


L'article seul

RFC 8952: Captive Portal Architecture

Date de publication du RFC : Novembre 2020
Auteur(s) du RFC : K. Larose (Agilicus), D. Dolson, H. Liu (Google)
Pour information
Réalisé dans le cadre du groupe de travail IETF capport
Première rédaction de cet article le 1 décembre 2020


Les portails captifs sont ces insupportables pages Web vers lesquels certains réseaux d'accès en WiFi vous détournent pour vous faire laisser des données personnelles avant d'avoir accès à l'Internet. Très répandus dans les hôtels et aéroports, ils utilisent pour ce détournement des techniques qui sont souvent celles des attaquants, et ils posent donc de graves problèmes de sécurité, sans parler de l'utilisabilité. Le groupe de travail CAPPORT de l'IETF travaille à définir des solutions techniques qui rendent ces portails captifs un peu moins pénibles. Ce RFC décrit l'architecture générale du système, d'autres RFC décrivant les solutions partielles.

Vu de l'utilisateurice, la connexion à l'Internet sur pas mal de hotspots se fait ainsi : on accroche le réseau WiFi, on se rend à une page Web quelconque et, au lieu de la page demandée, on est détourné vers une page des gérants du hotspot, qui vous montre de la publicité, exige des données personnelles (et parfois une authentification) et l'acceptation de conditions d'utilisation léonines, avant de continuer. Si la page que vous vouliez visiter était en HTTPS, ce qui est le cas le plus courant aujourd'hui, vous aurez probablement en outre un avertissement de sécurité. Certains systèmes d'exploitation, comme Android, ou certains navigateurs, comme Firefox, détectent automatiquement la captivité, suspendent leurs autres activités, et vous proposent d'aller directement à une page du portail. Mais ça reste pénible, par exemple parce que c'est difficile à automatiser. (Un exemple d'automatisation en shell avec curl que j'ai récupéré en ligne est hotel-wifi.sh. Un autre en Go et utilisant Chrome est captive-browser.)

Techniquement, la mise en œuvre du concept de portail captif se fait en général actuellement en détournant le trafic HTTP (via un transparent proxy) ou avec un résolveur DNS menteur, qui donne l'adresse IP du portail en réponse à toute requête. L'annexe A du RFC résume les mises en œuvre actuelles de portails captifs, et les méthodes utilisées pour pouvoir se connecter le moins mal possible. La machine qui veut savoir si elle est derrière un portail captif va typiquement faire une requête HTTP vers un URL connu et contrôlé par le fournisseur de l'application ou du système d'explication. Cet URL est appelé le canari. (Au lieu ou en plus de HTTP, on peut faire un test DNS sur un nom de domaine où on connait le résultat, au cas où le portail captif fonctionne avec un résolveur menteur. Attention, DNS et HTTP peuvent donner des résultats différents, détournement DNS mais pas HTTP, ou bien le contraire.) Notez que toutes les techniques avec un canari peuvent mener à des faux négatifs si le canari est connu et que le gardien derrière le portail captif le laisse passer, via une règle spécifique.

Notre RFC choisit d'être positif et donne une liste des exigences auxquelles essaie de répondre les solutions développées dans le groupe de travail. Ces exigences peuvent aussi être lues comme une liste de tous les inconvénients des portails captifs actuels. Un échantillon de ces exigences :

  • Ne pas détourner les protocoles standards de l'Internet comme HTTP, et, d'une manière générale, ne pas se comporter comme un attaquant (alors que les portails actuels violent systématiquement la sécurité en faisant des attaques de l'intermédiaire).
  • Ne pas détourner non plus le DNS, puisque la solution doit être compatible avec DNSSEC, outil indispensable à la sécurité du DNS.
  • Travailler au niveau IP (couche 3) et ne pas être dépendant d'une technologie d'accès particulière (cela ne doit pas marcher uniquement pour le WiFi).
  • Les machines terminales doivent avoir un moyen simple et sûr d'interroger leur réseau d'accès pour savoir si elles sont captives, sans compter sur le détournement de protocoles Internet, sans avoir besoin de « canaris », d'URI spéciaux qu'on tester, comme le detectportal.firefox.com de Firefox.
  • Les solutions déployées ne doivent pas exiger un navigateur Web, afin d'être utilisable, par exemple, par des machines sans utilisateur humain (les portails actuels plantent toutes les applications non-Web, comme la mise à jour automatique des logiciels). Ceci dit, le RFC se concentre surtout sur le cas où il y a un utilisateur humain.

L'architecture décrite ici repose sur :

  • L'annonce aux machines clients d'un URI qui sera l'adresse de l'API à laquelle parler pour en savoir plus sur le portail captif. Cette annonce peut se faire en DHCP (RFC 8910) ou en RA (même RFC). Mais elle pourra aussi se faire avec RADIUS ou par une configuration statique.
  • L'information donnée par le portail captif à la machine cliente, lui disant notamment si elle est en captivité ou pas.

Plus concrètement, le RFC liste les composants du problème et donc des solutions. D'abord, la machine qui se connecte (user equipment, dans le RFC). Pour l'instant, la description se limite aux machines qui ont un navigateur Web, même si elles sont loin d'être les seules, ou même les plus nombreuses. Pour que tout marche comme il faut, cette machine doit pouvoir récupérer l'URI de l'API du portail captif (par exemple, elle doit avoir un client DHCP), doit pouvoir gérer le fait que certaines de ses interfaces réseaux sont derrière un portail captif et pas les autres (RFC 7556), doit pouvoir notifier l'utilisateur qu'il y a un tel portail (et attention au hameçonnage !), et ce serait bien qu'il y a ait un moyen de dire aux applications « on est encore en captivité, n'essayez pas de vous connecter tout de suite ». Un exemple de cette dernière demande est fournie par Android où l'utilisation du réseau par défaut ne passe de la 4G au WiFi qu'une fois qu'Android a pu tester que l'accès WiFi n'était pas en captivité. Enfin, si la machine gère l'API des portails captifs, elle doit évidemment suivre toutes les règles de sécurité, notamment la validation du certificat.

Ensuite, deuxième composant, le système de distribution d'informations dans le réseau (provisioning service), DHCP ou RA. C'est lui qui va devoir envoyer l'URI de l'API à la machine qui se connecte, comme normalisé dans le RFC 8910.

Troisième composant, le serveur derrière l'API. Il permet de se passer des « canaris », des tests du genre « est-ce que j'arrive à me connecter à https://detectportal.example ? », il y a juste à interroger l'API. Cette API (RFC 8908) doit permettre au minimum de connaitre l'état de la connexion (en captivité ou pas), et d'apprendre l'URI que l'humain devra visiter pour sortir de captivité (« j'ai lu les 200 pages de textes juridiques des conditions d'utilisation, les 100 pages du code de conduite et je les accepte inconditionnellement »). Elle doit utiliser HTTPS.

Enfin, il y a le composant « gardien » (enforcement) qui s'assure qu'une machine en captivité ne puisse effectivement pas sortir ou en tout cas, ne puisse pas aller en dehors des services autorisés (qui doivent évidemment inclure le portail où on accepte les conditions). Il est typiquement placé dans le premier routeur. L'architecture présentée dans ce RFC ne change pas ce composant (contrairement aux trois premiers, qui devront être créés ou modifiés).

Parmi ces différents composants qui interagissent, l'un d'eux mérite une section entière, la section 3, dédiée à la machine qui se connecte (user equipment) et notamment à la question de son identité. Car il faut pouvoir identifier cette machine, et que les autres composants soient d'accord sur cette identification. Par exemple, une fois que le portail captif aura accepté la connexion, il faudra que le système « gardien » laisse désormais passer les paquets. Pour cette identification, plusieurs identificateurs sont possibles, soit présents explicitement dans les paquets, soit déduits d'autres informations. On peut imaginer, par exemple, utiliser l'adresse MAC ou l'adresse IP. Ou bien, pour les identificateurs qui ne sont pas dans le paquet, l'interface physique de connexion. Le RFC liste leurs propriétés souhaitables :

  • Unicité,
  • Difficulté à être usurpé par un attaquant (sur ce point, les adresses MAC ou IP ne sont pas satisfaisantes, alors que l'interface physique est parfaite ; diverses techniques existent pour limiter la triche sur l'adresse IP comme le test de réversibilité),
  • Visibilité pour le serveur derrière l'API (selon la technique de développement utilisée, l'adresse MAC peut être difficile ou impossible à récupérer par ce serveur ; idem pour l'interface physique),
  • Visibilité pour le gardien.

Notons que l'adresse IP a l'avantage d'être un identificateur qui n'est pas local au premier segment de réseau, ce qui peut permettre, par exemple, d'avoir un gardien qui soit situé un peu plus loin.

Armé de tous ces éléments, il est possible de présenter le traitement complet (section 4). Donc, dans l'ordre des évènements, dans le monde futur où ces différents RFC auront été déployés :

  • La machine de l'utilisateur se connecte au réseau, et obtient des informations en DHCP ou RA,
  • Parmi ces informations, l'URI de l'API du portail captif (options du RFC 8910),
  • La machine parle à cette API en suivant le RFC 8908, ce qui lui permet d'apprendre l'URI de la page du portail captif prévue pour les humains,
  • L'utilisateur humain lit les CGU (ah, ah) et les accepte,
  • Le gardien est prévenu de cette acceptation et laisse désormais passer les paquets,
  • L'utilisateur peut enfin regarder Joséphine, ange gardien sur Salto.

Si tout le monde joue le jeu, et que les techniques conformes à l'architecture décrite dans ce RFC sont déployées, les portails captifs, quoique toujours pénibles, devraient devenir plus gérables. Hélas, il est probable que beaucoup de déploiements ne soient jamais changé, notamment dans les environnements où le client est… captif (hôtels, aéroports) et où il n'y a donc aucune motivation commerciale pour améliorer les choses. Les logiciels vont donc devoir continuer à utiliser des heuristiques de type canari pendant une très longue période, car ils ne pourront pas compter sur des portails captifs propres.

Tout cela laisse ouvert quelques problèmes de sécurité, que la section 6 étudie. D'abord, fondamentalement, il faut faire confiance au réseau d'accès. Via DHCP et RA, il peut vous conduire n'importe où. (Le RFC 7556 discute de cette difficulté à authentifier le réseau d'accès.) Des protections comme l'authentification du serveur en TLS limitent un peu les dégâts mais ne résolvent pas tout. Ceci dit, c'est pour cela que notre RFC impose l'utilisation de HTTPS pour accéder à l'API. Mais, d'une manière générale, le réseau d'accès, et le portail captif qu'il utilise, a tous les pouvoirs. Il peut par exemple vous bloquer l'accès à tout ou partie de l'Internet, même après que vous ayez accepté les conditions d'utilisation.

Il y a aussi des questions de vie privée. L'identificateur de la machine qui se connecte peut être détourné de son usage pour servir à surveiller un utilisateur, par exemple. Les bases de données utilisées par les différents composants de cette architecture de portail captif doivent donc être considérées comme contenant des données personnelles. L'utilisateur peut changer ces identificateurs (pour l'adresse MAC, avec un logiciel comme macchanger) mais cela peut casser son accès Internet, si le gardien utilise justement cet identificateur.


Téléchargez le RFC 8952


L'article seul

RFC 8950: Advertising IPv4 Network Layer Reachability Information (NLRI) with an IPv6 Next Hop

Date de publication du RFC : Novembre 2020
Auteur(s) du RFC : S. Litkowski, S. Agrawal, K. Ananthamurthy (Cisco), K. Patel (Arrcus)
Chemin des normes
Réalisé dans le cadre du groupe de travail IETF bess
Première rédaction de cet article le 20 novembre 2020


Le protocole de routage BGP annonce des préfixes qu'on sait joindre, avec l'adresse IP du premier routeur à qui envoyer les paquets pour ce préfixe. (Ce routeur est appelé le next hop.) BGP a une extension, BGP multi-protocoles (RFC 4760) où les préfixes annoncés (NLRI pour Network Layer Reachability Information) ne sont plus forcément de la même famille que les adresses utilisées dans la session BGP. On peut donc annoncer des préfixes IPv6 sur une session BGP établie en IPv4 et réciproquement. Notre RFC, qui succède au RFC 5549 avec quelques petits changements, étend encore cette possibilité en permettant que le next hop ait une adresse de version différente de celle du préfixe.

Normalement, BGP multi-protocoles (RFC 4760) impose la version (IPv4 ou IPv6) du next hop via l'AFI (Address Family Identifier) et le SAFI (Subsequent Address Family Identifier) indiqués dans l'annonce (cf. la liste actuelle des AFI possible et celle des SAFI). Ainsi, un AFI de 1 (IPv4) couplé avec un SAFI valant 1 (unicast), lors de l'annonce d'un préfixe IPv4, impose que l'adresse du routeur suivant soit en IPv4. Désormais, cette règle est plus libérale, le routeur suivant peut avoir une adresse IPv6. Cela peut faciliter, par exemple, la vie des opérateurs qui, en interne, connectent des ilots IPv4 au-dessus d'un cœur de réseau IPv6 (cf. RFC 4925). Notez que cela ne règle que la question de l'annonce BGP. Il reste encore à router un préfixe IPv4 via une adresse IPv6 mais ce n'est plus l'affaire de BGP.

Il y avait déjà des exceptions à la règle comme quoi le préfixe et l'adresse du routeur suivant étaient de la même famille. Ainsi, le RFC 6074 permettait cela pour le couple AFI 25 (L2VPN) / SAFI 65 (VPLS). Mais le couple AFI 2 / SAFI 1 (IPv6 / unicast) ne permet pas de telles exceptions (RFC 2545). Une astuce (RFC 4798 et RFC 4659) permet de s'en tirer en encodant l'adresse IPv4 du next hop dans une adresse IPv6. (Oui, ::192.0.2.66 est une adresse IPv4 encodée dans les seize octets d'IPv6, cf. RFC 4291, section 2.5.5.2.) Quant au cas inverse (AFI IPv4, routeur suivant en IPv6), elle fait l'objet de notre RFC.

Lorsque l'adresse du next hop n'est pas de la famille du préfixe, il faut trouver la famille, ce qui peut se faire par la taille de l'adresse du next hop (quatre octets, c'est de l'IPv4, seize octets, de l'IPv6). C'est ce que propose le RFC 4684.

L'extension permettant la liberté d'avoir des next hop dans une famille différente du préfixe est spécifiée complètement en section 4. Elle liste les couples AFI / SAFI pour lesquels on est autorisé à avoir un next hop IPv6 alors que le préfixe est en IPv4. Le routeur BGP qui reçoit ces annonces doit utiliser la longueur de l'adresse pour trouver tout seul si le next hop est IPv4 ou IPv6 (la méthode des RFC 4684 et RFC 6074).

L'utilisation de cette liberté nécessite de l'annoncer à son pair BGP, pour ne pas surprendre des routeurs BGP anciens. Cela se fait avec les capacités du RFC 5492. La capacité se nomme Extended Next Hop Encoding et a le code 5. Cette capacité est restreinte à certains couples AFI / SAFI, listés dans l'annonce de la capacité. Par exemple, le routeur qui veut annoncer une adresse IPv6 comme next hop pour de l'unicast IPv4 va indiquer dans le champ Valeur de la capacité 1 / 1 (le couple AFI /SAFI) et le next hop AFI 2.

La section 2 du RFC résume les changements depuis le RFC 5549, que notre RFC remplace. L'encodage de l'adresse du next hop change dans le cas des VPN, et, pour les VPN MPLS, extension de l'exception au multicast. Bref, rien de bien crucial.


Téléchargez le RFC 8950


L'article seul

RFC 8949: Concise Binary Object Representation (CBOR)

Date de publication du RFC : Décembre 2020
Auteur(s) du RFC : C. Bormann (Universität Bremen TZI), P. Hoffman (ICANN)
Chemin des normes
Réalisé dans le cadre du groupe de travail IETF cbor
Première rédaction de cet article le 5 décembre 2020


Il existait un zillion de formats binaires d'échange de données ? Et bien ça en fera un zillion plus un. CBOR (Concise Binary Object Representation) est un format qui utilise un modèle de données très proche de celui de JSON, mais est encodé en binaire, avec comme but principal d'être simple à encoder et décoder, même par des machines ayant peu de ressources matérielles. Normalisé à l'origine dans le RFC 7049, il est désormais spécifié dans ce nouveau RFC. Si le texte de la norme a changé, le format reste le même.

Parmi les autres formats binaires courants, on connait ASN.1 (plus exactement BER ou DER, utilisés dans plusieurs protocoles IETF), EBML ou MessagePack mais ils avaient des cahiers des charges assez différents (l'annexe E du RFC contient une comparaison). CBOR se distingue d'abord par sa référence à JSON (RFC 8259), dont le modèle de données sert de point de départ à CBOR, puis par le choix de faciliter le travail des logiciels qui devront créer ou lire du CBOR. CBOR doit pouvoir tourner sur des machines très limitées (« classe 1 », en suivant la terminologie du RFC 7228). Par contre, la taille des données encodées n'est qu'une considération secondaire (section 1.1 du RFC pour une liste prioritisée des objectifs de CBOR). Quant au lien avec JSON, l'idée est d'avoir des modèles de données suffisamment proches pour qu'écrire des convertisseurs CBOR->JSON et JSON->CBOR soit assez facile, et pour que les protocoles qui utilisent actuellement JSON puissent être adaptés à CBOR sans douleur excessive. CBOR se veut sans schéma, ou, plus exactement, sans schéma obligatoire. Et le but est que les fichiers CBOR restent utilisables pendant des dizaines d'années, ce qui impose d'être simple et bien documenté.

La spécification complète de CBOR est en section 3 de ce RFC. Chaque élément contenu dans le flot de données commence par un octet dont les trois bits de plus fort poids indiquent le type majeur. Les cinq bits suivants donnent des détails. Ce mécanisme permet de programmeur un décodeur CBOR avec une table de seulement 256 entrées (l'annexe B fournit cette table et l'annexe C un décodeur en pseudo-code très proche de C). Pour un entier, si la valeur que codent ces cinq bits suivants est inférieure à 24, elle est utilisée telle quelle. Sinon, cela veut dire que les détails sont sur plusieurs octets et qu'il faut lire les suivants (la valeur des cinq bits codant la longueur à lire). Selon le type majeur, les données qui suivent le premier octet sont une valeur (c'est le cas des entiers, par exemple) ou bien un doublet {longueur, valeur} (les chaînes de caractères, par exemple). L'annexe A de notre RFC contient de nombreux exemples de valeurs CBOR avec leur encodage.

Quels sont les types majeurs possibles ? Si les trois premiers bits sont à zéro, le type majeur, 0, est un entier non signé. Si les cinq bits suivants sont inférieurs à 24, c'est la valeur de cet entier. S'ils sont égaux à 24, c'est que l'entier se trouve dans l'octet suivant l'octet initial, s'ils sont égaux à 25, que l'entier se trouve dans les deux octets suivants, et ainsi de suite (31 est réservé pour les tailles indéterminées, décrites plus loin). L'entier 10 se représentera donc 00001010, l'entier 42 sera 00011000 00101010, etc. Presque pareil pour un type majeur de 1, sauf que l'entier sera alors signé, et négatif. La valeur sera -1 moins la valeur encodée. Ainsi, -3 sera 00100010. Vous voulez vérifier ? L'excellent terrain de jeu http://cbor.me vous le permet, essayez par exemple http://cbor.me?diag=42.

Le type majeur 2 sera une chaîne d'octets (principal ajout par rapport au modèle de données de JSON). La longueur est codée d'abord, en suivant la même règle que pour les entiers. Puis viennent les données. Le type 3 indique une chaîne de caractères et non plus d'octets. Ce sont forcément des caractères Unicode, encodés en UTF-8 (RFC 3629). Le champ longueur (codé comme un entier) indique le nombre d'octets de l'encodage UTF-8, pas le nombre de caractères (pour connaître ce dernier, il faut un décodeur UTF-8). Vous voulez des exemples ? Connectez-vous à http://www.cbor.me/?diag=%22lait%22 et vous voyez que la chaîne « lait » est représentée par 646c616974 : 64 = 01100100, type majeur 3 puis une longueur de 4. Les codes ASCII suivent (rappelez-vous qu'ASCII est un sous-ensemble d'UTF-8). Avec des caractères non-ASCII comme http://www.cbor.me/?diag=%22caf%C3%A9%22, on aurait 65636166c3a9 (même type majeur, longueur 5 octets, puis les caractères, avec c3a9 qui code le é en UTF-8).

Le type majeur 4 indique un tableau. Rappelez-vous que CBOR utilise un modèle de données qui est très proche de celui de JSON. Les structures de données possibles sont donc les tableaux et les objets (que CBOR appelle les maps). Un tableau est encodé comme une chaîne d'octets, longueur (suivant les règles des entiers) puis les éléments du tableau, à la queue leu leu. La longueur est cette fois le nombre d'éléments, pas le nombre d'octets. Les éléments d'un tableau ne sont pas forcément tous du même type. Les tableaux (et d'autres types, cf. section 3.2) peuvent aussi être représentés sans indiquer explicitement la longueur ; le tableau est alors terminé par un élément spécial, le break code. Par défaut, un encodeur CBOR est libre de choisir la forme à longueur définie ou celle à longueur indéfinie, et le décodeur doit donc s'attendre à rencontrer les deux.

Le type majeur 5 indique une map (ce qu'on appelle objet en JSON et dictionnaire ou hash dans d'autres langages). Chaque élément d'une map est un doublet {clé, valeur}. L'encodage est le même que pour les tableaux, la longueur étant le nombre de doublets. Chaque doublet est encodé en mettant la clé, puis la valeur. Donc, le premier scalaire est la clé de la première entrée de la map, le deuxième la valeur de la première entrée, le troisième la clé de la deuxième entrée, etc.

Les clés doivent être uniques (une question problématique en JSON où les descriptions existantes de ce format ne sont ni claires ni cohérentes sur ce point).

Je passe sur le type majeur 6, voyez plus loin le paragraphe sur les étiquettes. Le type majeur 7 sert à coder les flottants (encodés ensuite en IEEE 754) et aussi d'autres types scalaires et le break code utilisé dans le paragraphe suivant. Les autres types scalaires, nommés « valeurs simples » (simple values) sont des valeurs spéciales comme 20 pour le booléen Faux, 21 pour le Vrai, et 22 pour le néant. Elles sont stockées dans un registre IANA.

Dans la description ci-dessus, les types vectoriels (tableaux, chaînes, maps) commencent par la longueur du vecteur. Pour un encodeur CBOR, cela veut dire qu'il faut connaître cette longueur avant même d'écrire le premier élément. Cela peut être contraignant, par exemple si on encode au fil de l'eau (streaming) des données en cours de production. CBOR permet donc d'avoir des longueurs indéterminées. Pour cela, on met 31 comme « longueur » et cette valeur spéciale indique que la longueur n'est pas encore connue. Le flot des éléments devra donc avoir une fin explicite cette fois, le break code. Celui-ci est représenté par un élément de type majeur 7 et de détails 31, donc tous les bits de l'octet à 1. Par exemple, http://cbor.me/?diag=%28_%20%22lait%22%29 nous montre que la chaîne « lait » ainsi codée (le _ indique qu'on veut un codage en longueur indéterminée) sera 7f646c616974ff. 7f est le type majeur 3, chaîne de caractères, avec la longueur 31, indiquant qu'elle est indéterminée. Puis suit la chaîne elle-même (les chaînes indéterminées en CBOR sont faites par concaténation de châines de longueur déterminée), puis le break code ff.

La même technique peut être utilisée pour les chaînes d'octets et de caractères, afin de ne pas avoir à spécifier leur longueur au début. Cette possibilité de listes de longueur indéterminée a été ajoutée pour faciliter la vie du streaming.

Revenons au type majeur 6. Il indique une étiquette (tag), qui sert à préciser la sémantique de l'élément qui suit (cf. section 3.4). Un exemple typique est pour indiquer qu'une chaîne de caractères est un fait une donnée structurée, par exemple une date ou un numéro de téléphone. Un décodeur n'a pas besoin de comprendre les étiquettes, il peut parfaitement les ignorer. Les valeurs possibles pour les étiquettes sont stockées dans un registre IANA, et on voit que beaucoup ont déjà été enregistrées, en plus de celles de ce RFC (par exemple par le RFC 8746 ou par le RFC 8782).

Quelques valeurs d'étiquette intéressantes ? La valeur 0 indique une date au format du RFC 3339 (une chaîne de caractères). La valeur 1 étiquette au contraire un entier, et indique une date comme un nombre de secondes depuis le 1er janvier 1970. Les valeurs négatives sont autorisées mais leur sémantique n'est pas normalisée (UTC n'est pas défini pour les dates avant l'epoch, surtout quand le calendrier a changé).

Les valeurs 2 et 3 étiquettent une chaîne d'octets et indiquent qu'on recommande de l'interpréter comme un grand entier (dont la valeur n'aurait pas tenu dans les types majeurs 0 ou 1). Les décodeurs qui ne gèrent pas les étiquettes se contenteront de passer à l'application cette chaîne d'octets, les autres passeront un grand entier.

Autre cas rigolos, les nombres décimaux non entiers. Certains ne peuvent pas être représentés de manière exacte sous forme d'un flottant. On peut alors les représenter par un couple [exposant, mantisse]. Par exemple, 273,15 est le couple [-2, 27315] (l'exposant est en base 10). On peut donc l'encoder en CBOR sous forme d'un tableau de deux éléments, et ajouter l'étiquette de valeur 4 pour préciser qu'on voulait un nombre unique.

D'autres étiquettes précisent le contenu d'une chaîne de caractères : l'étiquette 32 indique que la chaîne est un URI, la 34 que la chaîne est du Base64 (RFC 4648) et la 36 que cela va être un message MIME (RFC 2045). Comme l'interprétation des étiquettes est optionnelle, un décodeur CBOR qui n'a pas envie de s'embêter peut juste renvoyer à l'application cette chaîne.

Une astuce amusante pour finir les étiquettes, et la spécification du format : l'étiquette 55799 signifie juste que ce qui suit est du CBOR, sans modifier sa sémantique. Encodée, elle sera représentée par 0xd9d9f7 (type majeur 6 sur trois bits, puis détails 25 qui indiquent que le nombre est sur deux octets puis le nombre lui-même, d9f7 en hexa). Ce nombre 0xd9d9f7 peut donc servir de nombre magique. Si on le trouve au début d'un fichier, c'est probablement du CBOR (il ne peut jamais apparaître au début d'un fichier JSON, donc ce nombre est particulièrement utile quand on veut distinguer tout de suite si on a affaire à du CBOR ou à du JSON).

Maintenant que le format est défini rigoureusement, passons à son utilisation. CBOR est conçu pour des environnements où il ne sera souvent pas possible de négocier les détails du format entre les deux parties. Un décodeur CBOR générique peut décoder sans connaître le schéma utilisé en face. Mais, en pratique, lorsqu'un protocole utilise CBOR pour la communication, il est autorisé (section 5 du RFC) à mettre des restrictions, ou des informations supplémentaires, afin de faciliter la mise en œuvre de CBOR dans des environnements très contraints en ressources. Ainsi, on a parfaitement le droit de faire un décodeur CBOR qui ne gérera pas les nombres flottants, si un protocole donné n'en a pas besoin.

Un cas délicat est celui des maps (section 5.6). CBOR ne place guère de restrictions sur le type des clés et un protocole ou format qui utilise CBOR voudra souvent être plus restrictif. Par exemple, si on veut absolument être compatible avec JSON, restreindre les clés à des chaînes en UTF-8 est souhaitable. Si on tient à utiliser d'autres types pour les clés (voire des types différents pour les clés d'une même map !), il faut se demander comment on les traduira lorsqu'on enverra ces maps à une application. Par exemple, en JavaScript, la clé formée de l'entier 1 est indistinguable de celle formée de la chaîne de caractères "1". Une application en JavaScript ne pourra donc pas se servir d'une map qui aurait de telles clés, de types variés.

On a vu que certains éléments CBOR pouvaient être encodés de différentes manières, par exemple un tableau peut être représenté par {longueur, valeurs} ou bien par {valeurs, break code}. Cela facilite la tâche des encodeurs mais peut compliquer celle des décodeurs, surtout sur les machines contraintes en ressources, et cela peut rendre certaines opérations, comme la comparaison de deux fichiers, délicates. Existe-t-il une forme canonique de CBOR ? Pas à proprement parler mais la section 4.1 décrit la notion de sérialisation favorite, des décodeurs étant autorisés à ne connaitre qu'une sérialisation possible. Notamment, cela implique pour l'encodeur de :

  • Mettre les entiers sous la forme la plus compacte possible. L'entier 2 peut être représenté par un octet (type majeur 0 puis détails égaux à 2) ou deux (type majeur 0, détails à 24 puis deux octets contenant la valeur 2), voire davantage. La forme recommandée est la première (un seul octet). Même règle pour les longueurs (qui, en CBOR, sont encodées comme les entiers).
  • Autant que possible, mettre les tableaux et les chaînes sous la forme {longueur, valeurs}, et pas sous la forme où la longueur est indéfinie.

Tous les encodeurs CBOR qui suivent ces règles produiront, pour un même jeu de données, le même encodage.

Plus stricte est la notion de sérialisation déterministe de la section 4.2. Là encore, chacun est libre de la définir comme il veut (il n'y a pas de forme canonique officielle de CBOR, rappelez-vous) mais elle ajoute des règles minimales à la sérialisation favorite :

  • Trier les clés d'une map de la plus petite à la plus grande. (Selon leur représentation en octets, pas selon l'ordre alphabétique.)
  • Ne jamais utiliser la forme à longueur indéfinie des tableaux et chaînes.
  • Ne pas utiliser les étiquettes si elles ne sont pas nécessaires.

Autre question pratique importante, le comportement en cas d'erreurs. Que doit faire un décodeur CBOR si deux clés sont identiques dans une map, ce qui est normalement interdit en CBOR ? Ou si un champ longueur indique qu'on va avoir un tableau de 5 éléments mais qu'on n'en rencontre que 4 avant la fin du fichier ? Ou si une chaîne de caractères, derrière son type majeur 3, n'est pas de l'UTF-8 correct ? D'abord, un point de terminologie important : un fichier CBOR est bien formé si sa syntaxe est bien celle de CBOR, il est valide s'il est bien formé et que les différents éléments sont conformes à leur sémantique (par exemple, la date après une étiquette de valeur 0 doit être au format du RFC 3339). Si le document n'est pas bien formé, ce n'est même pas du CBOR et doit être rejeté par un décodeur. S'il n'est pas valide, il peut quand même être utile, par exemple si l'application fait ses propres contrôles. Les sections 5.2 et 5.3 décrivent la question. CBOR n'est pas pédant : un décodeur a le droit d'ignorer certaines erreurs, de remplacer les valeurs par ce qui lui semble approprié. CBOR penche nettement du côté « être indulgent avec les données reçues » ; il faut dire qu'une application qui utilise CBOR peut toujours le renforcer en ajoutant l'obligation de rejeter ces données erronées. Un décodeur strict peut donc s'arrêter à la première erreur. Ainsi, un pare-feu qui analyse du CBOR à la recherche de contenu malveillant a tout intérêt à rejeter les données CBOR incorrectes (puisqu'il ne sait pas trop comment elles seront interprétées par la vraie application). Bref, la norme CBOR ne spécifie pas de traitement d'erreur unique. Je vous recommande la lecture de l'annexe C, qui donne en pseudo-code un décodeur CBOR minimum qui ne vérifie pas la validité, uniquement le fait que le fichier est bien formé, et l'annexe F, qui revient sur cette notion de « bien formé » et donne des exemples.

Comme CBOR a un modèle de données proche de celui de JSON, on aura souvent envie d'utiliser CBOR comme encodage efficace de JSON. Comment convertir du CBOR en JSON et vice-versa sans trop de surprises ? La section 6 du RFC se penche sur ce problème. Depuis CBOR vers JSON, il est recommandé de produire du I-JSON (RFC 7493). Les traductions suivantes sont suggérées :

  • Les entiers deviennent évidemment des nombres JSON.
  • Les chaînes d'octets sont encodées en Base64 et deviennent des chaînes de caractères JSON (JSON n'a pas d'autre moyen de transporter du binaire).
  • Les chaînes de caractères deviennent des chaînes de caractères JSON (ce qui nécessite d'en échapper certains, RFC 8259, section 7).
  • Les tableaux deviennent des tableaux JSON et les maps des objets JSON (ce qui impose de convertir les clés en chaînes UTF-8, si elles ne l'étaient pas déjà).
  • Etc.

En sens inverse, de JSON vers CBOR, c'est plus simple, puisque JSON n'a pas de constructions qui seraient absentes de CBOR.

Pour les amateurs de futurisme, la section 7 discute des éventuelles évolutions de CBOR. Pour les faciliter, CBOR a réservé de la place dans certains espaces. Ainsi, le type majeur 7 permettra d'encoder encore quelques valeurs simples (cela nécessitera un RFC sur le chemin des normes, cf. RFC 8126 et la section 9.1 de notre RFC). Et on peut ajouter d'autres valeurs d'étiquettes (selon des règles qui dépendent de la valeur numérique : les valeurs les plus faibles nécessiteront une procédure plus complexe, cf. section 9.2).

CBOR est un format binaire. Cela veut dire, entre autres, qu'il n'est pas évident de montrer des valeurs CBOR dans, mettons, une documentation, contrairement à JSON. La section 8 décrit donc un format texte (volontairement non spécifié en détail) qui permettra de mettre des valeurs CBOR dans du texte. Nulle grammaire formelle pour ce format de diagnostic : il est prévu pour l'utilisation par un humain, pas par un analyseur syntaxique. Ce format ressemble à JSON avec quelques extensions pour les nouveautés de CBOR. Par exemple, les étiquettes sont représentées par un nombre suivi d'une valeur entre parenthèses. Ainsi, la date (une chaîne de caractères étiquetée par la valeur 0) sera notée :

0("2013-10-12T11:34:00Z")

Une map de deux éléments sera notée comme en JSON :

{"Fun": true, "Amt": -2}  

Même chose pour les tableaux. Ici, avec étiquette sur deux chaînes de caractères :

[32("http://cbor.io/"), 34("SW5zw6lyZXogaWNpIHVuIMWTdWYgZGUgUMOicXVlcw==")]

L'annexe G du RFC 8610 ajoute quelques extensions utiles à ce format de diagnostic.

Lors de l'envoi de données encodées en CBOR, le type MIME à utiliser sera application/cbor. Comme l'idée est d'avoir des formats définis en utilisant la syntaxe CBOR et des règles sémantiques spécifiques, on verra aussi sans doute des types MIME utilisant la notation plus du RFC 6839, par exemple application/monformat+cbor.

Voici par exemple un petit service Web qui envoie la date courante en CBOR (avec deux étiquettes différentes, celle pour les dates au format lisible et celle pour les dates en nombre de secondes, et en prime celles du RFC 8943). Il a été réalisé avec la bibliothèque flunn. Il utilise le type MIME application/cbor :

% curl -s https://www.bortzmeyer.org/apps/date-in-cbor  | read-cbor -
...
Tag 0
	String of length 20: 2020-11-16T15:02:24Z
Tag 1
	Unsigned integer 1605538944
	...
  

(Le programme read-cbor est présenté plus loin.)

Un petit mot sur la sécurité (section 10) : il est bien connu qu'un analyseur mal écrit est un gros risque de sécurité et d'innombrables attaques ont déjà été réalisées en envoyant à la victime un fichier délibérement incorrect, conçu pour déclencher une faille de l'analyseur. Ainsi, en CBOR, un décodeur qui lirait une longueur, puis chercherait le nombre d'éléments indiqué, sans vérifier qu'il est arrivé au bout du fichier, pourrait déclencher un débordement de tampon. Les auteurs de décodeurs CBOR sont donc priés de programmer de manière défensive, voire paranoïaque : ne faites pas confiance au contenu venu de l'extérieur.

Autre problème de sécurité, le risque d'une attaque par déni de service. Un attaquant taquin peut envoyer un fichier CBOR où la longueur d'un tableau est un très grand nombre, dans l'espoir qu'un analyseur naïf va juste faire malloc(length) sans se demander si cela ne consommera pas toute la mémoire.

Enfin, comme indiqué plus haut à propos du traitement d'erreur, comme CBOR ne spécifie pas de règles standard pour la gestion des données erronées, un attaquant peut exploiter cette propriété pour faire passer des données « dangereuses » en les encodant de telle façon que l'IDS n'y voit que du feu. Prenons par exemple cette map :

{"CodeToExecute": "OK",
 "CodeToExecute": "DANGER"}

Imaginons qu'une application lise ensuite la donnée indexée par CodeToExecute. Si, en cas de clés dupliquées, elle lit la dernière valeur, elle exécutera le code dangereux. Si un IDS lit la première valeur, il ne se sera pas inquiété. Voilà une bonne raison de rejeter du CBOR invalide (les clés dupliquées sont interdites) : il peut être interprété de plusieurs façons. Notez quand même que ce problème des clés dupliquées, déjà présent en JSON, a suscité des discussions passionnées à l'IETF, entre ceux qui réclamaient une interdiction stricte et absolue et ceux qui voulaient laisser davantage de latitude aux décodeurs. (La section 5.6 est une bonne lecture ici.)

Pour les amateurs d'alternatives, l'annexe E du RFC compare CBOR à des formats analogues. Attention, la comparaison se fait à la lumière du cahier des charges de CBOR, qui n'était pas forcément le cahier des charges de ces formats. Ainsi, ASN.1 (ou plutôt ses sérialisations comme BER ou DER, PER étant nettement moins courant puisqu'il nécessite de connaître le schéma des données) est utilisé par plusieurs protocoles IETF (comme LDAP) mais le décoder est une entreprise compliquée.

MessagePack est beaucoup plus proche de CBOR, dans ses objectifs et ses résultats, et a même été le point de départ du projet CBOR. Mais il souffre de l'absence d'extensibilité propre. Plusieurs propositions d'extensions sont restées bloquées à cause de cela.

BSON (connu surtout via son utilisation dans MongoDB) a le même problème. En outre, il est conçu pour le stockage d'objets JSON dans une base de données, pas pour la transmission sur le réseau (ce qui explique certains de ses choix). Enfin, MSDTP, spécifié dans le RFC 713, n'a jamais été réellement utilisé.

Rappelez-vous que CBOR prioritise la simplicité de l'encodeur et du décodeur plutôt que la taille des données encodées. Néanmoins, un tableau en annexe E.5 compare les tailles d'un même objet encodé avec tous ces protocoles : BSON est de loin le plus bavard (BER est le second), MessagePack et CBOR les plus compacts.

Une liste des implémentations est publiée en https://cbor.io/. Au moins quatre existent, en Python, Ruby, JavaScript et Java. J'avais moi-même écrit un décodeur CBOR très limité (pour un besoin ponctuel) en Go. Il est disponible ici et son seul rôle est d'afficher le CBOR sous forme arborescente, pour aider à déboguer un producteur de CBOR. Cela donne quelque chose du genre :

% ./read-cbor test.cbor
Array of 3 items
	String of length 5: C-DNS
	Map of 4 items
		Unsigned integer 0
 => 		Unsigned integer 0
		Unsigned integer 1
 => 		Unsigned integer 5
		Unsigned integer 4
 => 		String of length 70: Experimental dnstap client, IETF 99 hackathon, data from unbound 1.6.4
		Unsigned integer 5
 => 		String of length 5: godin
	Array of indefinite number of items
		Map of 3 items
			Unsigned integer 0
 => 			Map of 1 items
				Unsigned integer 1
 => 				Array of 2 items
					Unsigned integer 1500204267
					Unsigned integer 0
			Unsigned integer 2
 => 			Map of indefinite number of items
				Unsigned integer 0
 => 				Array of 2 items
					Byte string of length 16
					Byte string of length 16
...

L'annexe G de notre RFC résume les changements depuis le RFC 7049. Le format reste le même, les fichiers CBOR d'avant sont toujours du CBOR. Il y a eu dans ce nouveau RFC des corrections d'erreurs (comme un premier exemple erroné, un autre, et encore un), un durcissement des conditions d'enregistrement des nouvelles étiquettes pour les valeurs les plus basses, une description plus détaillée du modèle de données (la section 2 est une nouveauté de notre RFC), un approfondissement des questions de représentation des nombres, etc. Notre RFC 8949 est également plus rigoureux sur les questions de sérialisation préférée et déterministe. L'étiquette 35, qui annonçait qu'on allait rencontrer une expression rationnelle a été retirée (le RFC note qu'il existe plusieurs normes pour ces expressions et que l'étiquette n'est pas définie de manière assez rigoureuse pour trancher).


Téléchargez le RFC 8949


L'article seul

RFC 8948: Structured Local Address Plan (SLAP) Quadrant Selection Option for DHCPv6

Date de publication du RFC : Décembre 2020
Auteur(s) du RFC : CJ. Bernardos (UC3M), A. Mourad (InterDigital)
Chemin des normes
Réalisé dans le cadre du groupe de travail IETF dhc
Première rédaction de cet article le 2 décembre 2020


Les adresses MAC sur 48 bits, normalisées par l'IEEE, ont un bit qui indique si l'adresse découle d'un plan mondial et est donc globalement unique, ou bien si elle a été allouée via une méthode locale. Plus récemment, l'IEEE a découpé l'espace local en quatre quadrants, pouvant avoir des poltiques différentes. Depuis qu'on peut allouer ces adresses MAC locales par DHCP (RFC 8947), il serait intéressant de choisir son quadrant. C'est ce que permet la nouvelle option DHCP QUAD normalisée dans ce nouveau RFC.

Tout·e étudiant·e en réseaux informatiques a appris le découpage des adresses MAC de 48 bits via le bit U/L (Universal/Local). Les adresses avec ce bit à 1 sont gérées localement (et donc pas forcément uniques au niveau mondial). En 2017, l'IEEE a ajouté un nouveau concept, le SLAP (Structured Local Address Plan). L'espace des adresses locales est découpé en quadrants, identifiés par deux bits (troisième et quatrième position dans l'adresse) :

  • 01 Extended Local Identifier où l'adresse commence par le CID (Company ID), qui identifie l'organisation,
  • 11 Standard Assigned Identifier où l'adresse est allouée en suivant un protocole normalisé par l'IEEE (qui, à ma connaissance, n'est pas encore publié, il se nommera IEEE P802.1CQ: Multicast and Local Address Assignment),
  • 00 Administratively Assigned Identifier où l'adresse est entièrement gérée localement, sans préfixe ou protocole standard (avant SLAP, toutes les adresses locales étaient gérées ainsi),
  • 10 est réservé pour des idées futures.

De son côté, l'IETF a, dans le RFC 8947, normalisé une option de DHCPv6 qui permet d'obtenir des adresses MAC par DHCP.

Or, dans certains cas, une machine pourrait vouloir choisir le quadrant dans lequel son adresse MAC se situe. Le RFC cite l'exemple d'objets connectés qui voudraient une adresse dans le quadrant ELI (Extended Local Identifier) pour avoir l'identifiant du fabricant au début de l'adresse, sans pour autant que le fabricant ne soit obligé d'allouer à la fabrication une adresse unique à chaque objet. Par contre, des systèmes qui changeraient leur adresse MAC pour éviter la traçabilité préféreraient sans doute un adresse dans le quadrant AAI (Administratively Assigned Identifier). L'annexe A du RFC est très intéressante de ce point de vue, décrivant plusieurs scénarios et les raisons du choix de tel ou tel quadrant.

Notre nouveau RFC ajoute donc une option au protocole DHCP que normalisait le RFC 8415. Elle se nomme QUAD et repose sur l'option LLADDR du RFC 8947. Le client met cette option QUAD dans la requête DHCP, pour indiquer son quadrant favori. Le serveur ne l'utilise pas dans la réponse. Soit il est d'accord pour allouer une adresse MAC dans ce quadrant et il le fait, soit il propose une autre adresse. (Rappelez-vous qu'en DHCP, le client propose, et le serveur décide.) Le client regarde l'adresse renvoyée et sait ainsi si sa préférence pour un quadrant particulier a été satisfaite ou pas.

La section 4 décrit les détails de l'option QUAD. Elle permet d'exprimer une liste de quadrants, avec des préférences associées. L'option est désormais enregistrée à l'IANA (code 140).

Pour l'instant, je ne connais pas de mise en œuvre de ce RFC que ce soit côté client ou serveur.


Téléchargez le RFC 8948


L'article seul

RFC 8947: Link-Layer Addresses Assignment Mechanism for DHCPv6

Date de publication du RFC : Décembre 2020
Auteur(s) du RFC : B. Volz (Cisco), T. Mrugalski (ISC), CJ. Bernardos (UC3M)
Chemin des normes
Réalisé dans le cadre du groupe de travail IETF dhc
Première rédaction de cet article le 2 décembre 2020


L'utilisation de DHCP pour attribuer des adresses IP est bien connue. Ce nouveau RFC spécifie comment utiliser DHCP pour attribuer des adresses MAC.

Dans quels cas est-ce utile ? La principale motivation vient des environnements virtualisés où on crée des quantités industrielles de machines virtuelles. C'est le cas chez les gros hébergeurs, par exemple, ou bien dans les organisations qui ont d'immenses fermes de machines. Si les adresses MAC de ces machines sont attribuées au hasard, le risque de collision est important, d'autant plus qu'il n'y a pas de protocole de détection de ces collisions. (Sur le risque de collision, notamment en raison du paradoxe de l'anniversaire, voir le RFC 4429, annexe A.1.) Bien sûr, les adresses MAC n'ont pas besoin d'être uniques au niveau mondial, mais la taille de certains réseaux L2 fait que la collision est un réel problème. Un autre scénario est celui des objets connectés (RFC 7228), où leur nombre pourrait menacer l'espace d'adressage OUI. L'idée est donc de récupérer ces adresses MAC auprès d'un serveur DHCP, ce qui évitera les collisions.

En effet, le protocole DHCP, normalisé pour sa version IPv6 dans le RFC 8415, ne sert pas qu'à attribuer des adresses IP. On peut s'en servir pour n'importe quel type de ressources. Et il offre toutes les fonctions nécessaires pour gérer ces ressources, et de nombreuses mises en œuvre déjà bien testées. Notre RFC va donc s'appuyer dessus pour les adresses MAC de 48 bits (EUI-48) d'IEEE 802 (et peut-être dans le futur pour d'autres types d'adresses).

Dans ces adresses de 48 bits, un bit nous intéresse particulièrement, le U/L. Comme son nom l'indique (U = universal, L = local), il indique si l'adresse suit le mécanisme d'allocation de l'IEEE (et est donc a priori unique au niveau mondial) ou bien si l'adresse est gérée selon des règles locales. Les adresses allouées via DHCP seront en général des adresses à gestion locale. Étant purement locales (aucune garantie quant à leur unicité), elles ne doivent pas être utilisées pour fabriquer le DUID - DHCP Unique Identifier - de DHCP (cf. RFC 8415, section 11). Notez aussi que la norme IEEE 802c découpe l'espace local en quatre parties (détails dans l'annexe A du RFC ou, pour les courageuses et les courageux, dans la norme 802c). Pendant qu'on parle de l'IEEE, leur norme 802 1CQ prévoit un mécanisme d'affectation des adresses MAC, qui peut être un concurrent de celui de ce RFC. Le RFC 8948 fournit un moyen de choisir l'un de ces quadrants.

Un peu de terminologie est nécessaire pour suivre ce RFC (section 3). Un bloc d'adresses est une suite consécutive d'adresses MAC. IA_LL désigne une Identity Association for Link-Layer Address et c'est une option DHCP (code 138) qui va beaucoup servir. Autre option DHCP, LLADDR (code 139) qui est l'option qui va servir à transporter les adresses MAC.

La section 4 du RFC présente les principes de déploiement de la solution. Il y a notamment deux scénarios envisagés :

  • Le mode relais, où le client DHCP ne sera pas le destinataire final des adresses MAC. Par exemple, dans le cas de la virtualisation, le client DHCP sera l'hyperviseur qui demandera un bloc d'adresses qu'il attribuera ensuite aux machines virtuelles qu'il crée. Le serveur DHCP verra donc un client très gourmand, demandant de plus en plus d'adresses MAC.
  • Le mode direct, où le client DHCP sera la machine utilisatrice de l'adresse MAC. C'est notamment le mode envisagé pour l'IoT, chaque objet demandant son adresse MAC. Comme il faudra bien une adresse MAC pour faire du DHCP afin de demander une adresse MAC, les objets utiliseront les adresses temporaires normalisées dans IEEE 802.11. Comme l'adresse MAC changera une fois allouée, il faut veiller à ne pas utiliser des commutateurs méchants qui bloquent le port en cas de changement d'adresse.

Bon, sinon, le fonctionnement de l'allocation d'adresses MAC marche à peu près comme celui de l'allocation d'adresses IP. Le client DHCP envoie un message Solicit incluant une option IA_LL qui contient elle-même une option LLADDR qui indique le type d'adresse souhaitée et peut aussi inclure une suggestion, si le client a une idée de l'adresse qu'il voudrait. Le serveur répond avec Advertise contenant l'adresse ou le bloc d'adresses alloué (qui ne sont pas forcément ceux suggérés par le client, comme toujours avec DHCP). Si nécessaire, il y aura ensuite l'échange habituel Request puis Reply. Bref, du DHCPv6 classique. Le client devra renouveler l'allocation au bout du temps indiqué dans le bail (le serveur peut toujours donner l'adresse sans limite de temps, cf. RFC 8415, section 7.7). Le client peut explicitement abandonner l'adresse, avec un message Release. On l'a dit, ça se passe comme pour les adresses IP.

Les fanas du placement exact des bits liront la section 10, où est décrit l'encodage de l'option IA_LL et de la « sous-option » LLADDR. C'est là qu'on trouvera l'indication des blocs d'adresses MAC, encodés par la première adresse puis la taille du bloc (-1). Ainsi, 02:04:06:08:0a / 3 indique un bloc qui va de 02:04:06:08:0a à 02:04:06:08:0d. Pendant qu'on parle de bits, notez que les bits indiquant diverses caractéristiques de l'adresse MAC figurent dans le premier octet, et que la transmission se fait en commençant par le bit le moins significatif (IEEE 802 est petit-boutien pour les bits). Ainsi, l'adresse citée plus haut, 02:04:06:08:0a a un premier octet qui vaut 2, soit 00000010 en binaire, ce qui sera transmis comme 01000000. Le premier bit est le M, qui indique ici qu'il s'agit d'une adresse unicast, le second est U/L, indiquant ici que c'est bien une adresse locale, les deux bits suivants sont une nouveauté de IEEE 802c et indiquent le quadrant des adresses (cf. annexe A du RFC, puis le RFC 8948).

Quelques conseils pour les administrateurs des serveurs DHCP qui feront cette allocation d'adresses MAC figurent en section 11 du RFC. Par exemple, il ne faut allouer que des adresses locales (bit U/L à 1).

Les deux nouvelles options, IA_LL et LLADDR ont été mises dans le registre IANA.

Pour finir, l'annexe A du RFC résume la norme IEEE 802c. Dans la norme IEEE 802 originale, il y avait, comme indiqué plus haut, un bit U/L qui disait si l'adresse était gérée selon des règles locales (et n'était donc pas forcément unique au niveau mondial). 802c ajoute à ces adresses locales la notion de quadrant, découpant l'espace local en quatre. Après le bit M (unicast ou groupe) et le bit U/L (local ou pas). deux bits indiquent dans quel quadrant se trouve l'adresse :

  • 01 est Extended Local, des adresses qui comportent un identifiant de l'organisation, le CID (Company ID), et qui sont donc apparemment garanties uniques à l'échelle de la planète,
  • 11 est Standard Assigned, elles sont affectées par le protocole IEEE concurrent de celui du RFC,
  • 00 est Administratively Assigned, ce sont les « vraies » adresses locales, attribuées comme on veut (comme l'était la totalité de l'espace des adresses locales autrefois, les adresses d'exemple plus haut (02:04:06:08:0a et suivantes sont dans ce quadrant),
  • 10 est réservé pour un usage futur.

Un mécanisme de sélection du quadrant est normalisé dans le RFC 8948.

Pour l'instant, je ne connais pas de mise en œuvre de ce RFC que ce soit côté client ou serveur.


Téléchargez le RFC 8947


L'article seul

RFC 8945: Secret Key Transaction Authentication for DNS (TSIG)

Date de publication du RFC : Novembre 2020
Auteur(s) du RFC : F. Dupont (ISC), S. Morris (ISC), P. Vixie (Farsight), D. Eastlake 3rd (Futurewei), O. Gudmundsson (Cloudflare), B. Wellington (Akamai)
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 2020


Le DNS a des vulnérabilités à plusieurs endroits, notamment des risques d'usurpation, qui permettent de glisser une réponse mensongère à la place de la bonne. Il existe plusieurs solutions pour ces problèmes, se différenciant notamment par leur degré de complexité et leur facilité à être déployées. TSIG (Transaction SIGnature), normalisé dans ce RFC (qui remplace le RFC 2845), est une solution de vérification de l'intégrité du canal, permettant à deux machines parlant DNS de s'assurer de l'identité de l'interlocuteur. TSIG est surtout utilisé entre serveurs DNS maîtres et esclaves, pour sécuriser les transferts de zone (aujourd'hui, presque tous les transferts entre serveurs faisant autorité sont protégés par TSIG).

TSIG repose sur l'existence d'une clé secrète, partagée entre les deux serveurs, qui sert à générer un HMAC permettant de s'assurer que le dialogue DNS a bien lieu avec la machine attendue, et que les données n'ont pas été modifiées en route. L'obligation de partager une clé secrète le rend difficilement utilisable, en pratique, pour communiquer avec un grand nombre de clients. Mais, entre deux serveurs faisant autorité pour la même zone, ce n'est pas un problème (section 1.1 du RFC). On peut gérer ces secrets partagés manuellement. Même chose entre un serveur maître et un client qui met à jour les données par dynamic update (RFC 2136).

Bien sûr, si tout le monde utilisait DNSSEC partout, le problème de sécuriser le transfert de zones entre serveurs faisant autorité serait moins grave (mais attention, DNSSEC ne signe pas les délégations et les colles, cf. la section 10.2 de notre RFC). En attendant un futur lointain « tout-DNSSEC », TSIG fournit une solution simple et légère pour éviter qu'un méchant ne se glisse dans la conversation entre maître et esclave (par exemple grâce à une attaque BGP) et ne donne à l'esclave de fausses informations. (Il existe aussi des solutions non-DNS comme de transférer la zone avec rsync au dessus de SSH. Notez d'autre part que TSIG peut servir à d'autres choses que le transfert de zones, comme l'exemple des mises à jour dynamiques cité plus haut. Par contre, pour sécuriser la communication entre le client final et le résolveur, il vaut mieux utiliser DoTRFC 7858 - ou DoHRFC 8484.)

Comment fonctionne TSIG ? Les sections 4 et 5 décrivent le protocole. Le principe est de calculer, avec la clé secrète, un HMAC des données transmises, et de mettre ce HMAC (cette « signature ») dans un pseudo-enregistrement TSIG qui sera joint aux données, dans la section additionnelle. À la réception, l'enregistrement TSIG (obligatoirement le dernier de la section additionnelle) sera extrait, le HMAC calculé et vérifié.

Pour éviter des attaques par rejeu, les données sur lesquelles portent le HMAC incluent l'heure. C'est une des causes les plus fréquentes de problèmes avec TSIG : les deux machines doivent avoir des horloges très proches (section 10, qui recommande une tolérance - champ Fudge - de cinq minutes).

Le format exact des enregistrements TSIG est décrit en section 4. L'enregistrement est donc le dernier, et est mis dans la section additionnelle (voir des exemples plus loin avec dig et tshark). Le type de TSIG est 250 et, évidemment, ces pseudo-enregistrements ne doivent pas être gardés dans les caches. Plusieurs algorithmes de HMAC sont possibles. Un est obligatoire et recommandé, celui fondé sur SHA-256 (que j'utilise dans les exemples par la suite). On peut toujours utiliser SHA-1 et MD5 (avant que vous ne râliez : oui, SHA-1 et MD5 ont de gros problèmes mais cela n'affecte pas forcément leur utilisation en HMAC, cf. RFC 6151). Un registre IANA contient les algorithmes actuellement possibles (cf. aussi section 6 du RFC).

Le nom dans l'enregistrement TSIG est le nom de la clé (une raison pour bien le choisir, il inclut typiquement le nom des deux machines qui communiquent), la classe est ANY, le TTL nul et les données contiennent le nom de l'algorithme utilisé, le moment de la signature, et bien sûr la signature elle-même.

Le fait que la clé soit secrète implique des pratiques de sécurité sérieuses, qui font l'objet de la section 8. Par exemple, l'outil de génération de clés de BIND crée des fichiers en mode 0600, ce qui veut dire lisibles uniquement par leur créateur, ce qui est la moindre des choses. Encore faut-il assurer la sécurité de la machine qui stocke ces fichiers. (Voir aussi le RFC 2104).

Voyons maintenant des exemples concrets où polly (192.168.2.27) est un serveur DNS maître pour la zone example.test et jadis (192.168.2.29) un serveur esclave. Pour utiliser TSIG afin d'authentifier un transfert de zone entre deux machines, commençons avec BIND. Il faut d'abord générer une clé. Le programme tsig-keygen, livré avec BIND, génère de nombreux types de clés (d'où ses nombreuses options). Pour TSIG, on veut du SHA-256 (mais tsig-keygen connait d'autres algorithmes) :

% tsig-keygen -a HMAC-SHA256  polly-jadis 
key "polly-jadis" {
	algorithm hmac-sha256;
	secret "LnDomTT+IRf0daLYqxkstFpTpmFfcOvyxtRaq2VhHSI=";
};

(Avant BIND 9.13, le programme à utiliser était dnssec-keygen, qui ne fait désormais plus que du DNSSEC.) On a donné à la clé le nom des deux machines entre lesquelles se fera la communication (le nom est affiché dans le journal lors d'un transfert réussi, et à plusieurs autres endroits, donc il vaut mieux le choisir long et descriptif), plus un chiffre pour distinguer d'éventuelles autres clés. La clé est partagée entre ces deux machines. On met alors la clé dans la configuration de BIND, sur les deux machines (tsig-keygen produit directement de la configuration BIND). Naturellement, il faut transférer la clé de manière sécurisée entre les deux machines, pas par courrier électronique ordinaire, par exemple. Sur le serveur maître, on précise que seuls ceux qui connaissent la clé peuvent transférer la zone :

zone "example.test" { 
         type master;  
         file "/etc/bind/example.test";
         allow-transfer {  key polly-jadis; };
};

Les autres clients seront rejetés :

Apr  6 17:45:15 polly daemon.err named[27960]: client @0x159c250 192.168.2.29#51962 (example.test): zone transfer 'example.test/AXFR/IN' denied

Mais, si on connait la clé, le transfert est possible (ici, un test avec dig) :


% dig -y hmac-sha256:polly-jadis:LnDomTT+IRf0daLYqxkstFpTpmFfcOvyxtRaq2VhHSI= @polly.sources.org AXFR example.test 

; <<>> DiG 9.16.1-Debian <<>> -y hmac-sha256 @polly.sources.org AXFR example.test
; (1 server found)
;; global options: +cmd
example.test.		86400	IN	SOA	polly.sources.org. hostmaster.sources.org. 2020040600 7200 3600 604800 43200
example.test.		86400	IN	NS	polly.sources.org.
example.test.		86400	IN	SOA	polly.sources.org. hostmaster.sources.org. 2020040600 7200 3600 604800 43200
polly-jadis.		0	ANY	TSIG	hmac-sha256. 1586195193 300 32 pJciUWLOkg1lc6J+msC+dzYotVSOr8PSXgE6fFU3UwE= 25875 NOERROR 0 
;; Query time: 10 msec
;; SERVER: 192.168.2.27#53(192.168.2.27)
;; WHEN: Mon Apr 06 17:46:33 UTC 2020
;; XFR size: 3 records (messages 1, bytes 267)


On voit que dig affiche fidèlement tous les enregistrements, y compris le pseudo-enregistrement de type TSIG qui contient la signature. Un point de sécurité, toutefois (merci à Jan-Piet Mens pour sa vigilance) : avec ce -y, quiconque a un compte sur la même machine Unix peut voir la clé avec ps. Il peut être préférable de la mettre dans un fichier et de dire à dig d'utiliser ce fichier (dig -k polly-jadis.key …).

Maintenant que le test marche, configurons le serveur BIND esclave :

// To avoid copy-and-paste errors, you can also redirect tsig-keygen's
// output to a file and then include it in named.conf with 'include
// polly-jadis.key'.
key "polly-jadis" {
    algorithm hmac-sha256;
              secret "LnDomTT+IRf0daLYqxkstFpTpmFfcOvyxtRaq2VhHSI=";
              };

zone "example.test" {
       type slave;
       masters {192.168.2.27;};
       file "example.test";
};

server 192.168.2.27 {
        keys {
          polly-jadis;
        };
};

et c'est tout : l'esclave va désormais utiliser TSIG pour les transferts de zone. Le programme tshark va d'ailleurs nous afficher les enregistrements TSIG :

    Queries
        example.test: type AXFR, class IN
            Name: example.test
            [Name Length: 12]
            [Label Count: 2]
            Type: AXFR (transfer of an entire zone) (252)
            Class: IN (0x0001)
    Additional records
        polly-jadis: type TSIG, class ANY
            Name: polly-jadis
            Type: TSIG (Transaction Signature) (250)
            Class: ANY (0x00ff)
            Time to live: 0
            Data length: 61
            Algorithm Name: hmac-sha256
            Time Signed: Apr  6, 2020 19:51:36.000000000 CEST
            Fudge: 300
            MAC Size: 32
            MAC
                [Expert Info (Warning/Undecoded): No dissector for algorithm:hmac-sha256]
                    [No dissector for algorithm:hmac-sha256]
                    [Severity level: Warning]
                    [Group: Undecoded]
            Original Id: 37862
            Error: No error (0)
            Other Len: 0

Et si l'esclave est un nsd et pas un BIND ? C'est le même principe. On configure l'esclave :

key:
    name: polly-jadis
    algorithm: hmac-sha256
    secret: "LnDomTT+IRf0daLYqxkstFpTpmFfcOvyxtRaq2VhHSI="

zone:
    name: "example.test"
    zonefile: "example.test"
    allow-notify: 192.168.2.27 NOKEY
    request-xfr: 192.168.2.27 polly-jadis

Et le prochain nsd-control transfer example.test (ou bien la réception de la notification) déclenchera un transfert, qui sera ainsi enregistré :

Apr 07 06:46:48 jadis nsd[30577]: notify for example.test. from 192.168.2.27 serial 2020040700
Apr 07 06:46:48 jadis nsd[30577]: [2020-04-07 06:46:48.831] nsd[30577]: info: notify for example.test. from 192.168.2.27 serial 2020040700
Apr 07 06:46:48 jadis nsd[30565]: xfrd: zone example.test written received XFR packet from 192.168.2.27 with serial 2020040700 to disk
Apr 07 06:46:48 jadis nsd[30565]: [2020-04-07 06:46:48.837] nsd[30565]: info: xfrd: zone example.test written received XFR packet from 192.168.2.27 with serial 2020040700 to disk

Pour générer des clés sans utiliser BIND, on peut consulter mon autre article sur TSIG.

On l'a vu, TSIG nécessite des horloges synchronisées. Une des erreurs les plus fréquentes est d'oublier ce point. Si une machine n'est pas à l'heure, on va trouver dans le journal des échecs TSIG comme (section 5.2.3 du RFC), renvoyés avec le code de retour DNS NOTAUTH (code 9) et un code BADTIME dans l'enregistrement TSIG joint :

Mar 24 22:14:53 ludwig named[30611]: client 192.168.2.7#65495: \
         request has invalid signature: TSIG golgoth-ludwig-1: tsig verify failure (BADTIME)

Ce nouveau RFC ne change pas le protocole du RFC 2845. Un ancien client peut interopérer avec un nouveau serveur, et réciproquement. Seule change vraiment la procédure de vérification, qui est unilatérale. Une des motivations pour la production de ce nouveau RFC venait de failles de sécurité de 2016-2017 (CVE-2017-3142, CVE-2017-3143 et CVE-2017-11104). Certes, elle concernaient des mises en œuvre, pas vraiment le protocole, mais elles ont poussé à produire un RFC plus complet et plus clair.

Le débat à l'IETF au début était « est-ce qu'on en profite pour réparer tout ce qui ne va pas dans le RFC 2845, ou bien est-ce qu'on se limite à réparer ce qui est clairement cassé ? » Il a été tranché en faveur d'une solution conservatrice : le protocole reste le même, il ne s'agit pas d'un « TSIG v2 ».


Téléchargez le RFC 8945


L'article seul

RFC 8943: Concise Binary Object Representation (CBOR) Tags for Date

Date de publication du RFC : Novembre 2020
Auteur(s) du RFC : M. Jones (Microsoft), A. Nadalin (Independent), J. Richter (pdv Financial Software GmbH)
Chemin des normes
Réalisé dans le cadre du groupe de travail IETF cbor
Première rédaction de cet article le 21 novembre 2020


Le format de données CBOR, normalisé dans le RFC 8949, possède un certain nombre de types de données de base, et un mécanisme d'extensions, les étiquettes (tags). Ce RFC spécifie deux nouvelles étiquettes, pour indiquer des dates.

Le RFC 8949 déclarait déjà deux types pour les estampilles temporelles, l'étiquette 0 pour une chaîne de caractères dont le contenu est une estampille au format du RFC 3339 (avec la date et l'heure), et l'étiquette 1 pour une estampille sous la forme d'un nombre de secondes depuis l'epoch. Dans ce nouveau RFC sont ajoutées deux étiquettes, 100 pour un entier qui va stocker le nombre de jours depuis l'epoch, et 1004 pour une date seule (sans heure) au format du RFC 3339. L'epoch est celle de la norme Posix 1 / IEEE Standard 1003.1, le 1 janvier 1970. Dans les deux cas, comme on ne stocke pas l'heure, des considérations comme le fuseau horaire ou les secondes intercalaires sont inutiles. Quant au calendrier utilisé, c'est le grégorien.

Dans ce calendrier, John Lennon (je reprends l'exemple du RFC…) est né le 9 octobre 1940 et mort le 8 décembre 1980. (Les dates utilisées dans ce RFC n'incluent pas l'heure.) Pour la première étiquette, 100, qui indique le nombre de jours depuis l'epoch, l'auteur d'I Am the Walrus est né le -10676. C'est un nombre négatif puisque l'epoch utilisée est le 1 janvier 1970, après sa naissance. Lennon est mort le 3994. Pour le cas de la deuxième étiquette, 1004, il est né le 1940-10-09 et mort le 1980-12-08, suivant le format du RFC 3339. Le jour (lundi, mardi, mercredi…) est explicitement non mentionné, si on en a besoin, il faut le recalculer.

Les deux formats, en nombre de jours depuis l'epoch, et en RFC 3339 ont l'avantage que les comparaisons de date sont triviales, une simple comparaison d'entiers dans le premier cas, de chaînes de caractères dans le suivant, suffit.

Petit piège des dates indiquées sans l'heure, un même événement peut survenir à deux dates différentes selon le fuseau horaire. Ainsi, une vidéoconférence qui a lieu, à Tokyo, le 12 octobre à 10h00 sera considérée par les habitants d'Honolulu comme se tenant le 11 octobre à 15h00.

Les deux étiquettes ont été enregistrées à l'IANA. La valeur 100 pour la première a été choisie car 100 est le code ASCII de 'd' (pour date).

Si vous voulez un fichier CBOR utilisant ces deux étiquettes, vous pouvez appeler le service https://www.bortzmeyer.org/apps/date-in-cbor qui vous renvoie un tableau avec les quatre façons de servir une date en CBOR, les deux standards du RFC 8949, et les deux de notre RFC. Ici, on utilise le programmme read-cbor pour afficher plus joliment :

% wget -q -O - https://www.bortzmeyer.org/apps/date-in-cbor | ./read-cbor -
Array of 5 items
...
        Tag 0
                String of length 20: 2020-11-21T06:44:33Z
        Tag 1
                Unsigned integer 1605941073
        Tag 100
                Unsigned integer 18587
        Tag 1004
                String of length 10: 2020-11-21
  

Téléchargez le RFC 8943


L'article seul

RFC 8942: HTTP Client Hints

Date de publication du RFC : Février 2021
Auteur(s) du RFC : I. Grigorik, Y. Weiss (Google)
Expérimental
Réalisé dans le cadre du groupe de travail IETF httpbis
Première rédaction de cet article le 9 février 2021


Aux débuts du Web, il allait de soi que le contenu renvoyé à un client était identique quel que soit le client. On demandait un article scientifique, le même article était donné à tout le monde. La séparation du contenu et de la présentation que permet HTML faisait en sorte que ce contenu s'adaptait automatiquement aux différents clients et notamment à leur taille. Mais petit à petit l'habitude s'est prise d'envoyer un contenu différent selon le client. C'était parfois pour de bonnes raisons (s'adapter à la langue de l'utilisateur) et parfois pour des mauvaises (incompréhension de la séparation du contenu et de la présentation, ignorance du Web par des commerciaux qui voulaient contrôler l'apparence exacte de la page). Cette adaptation au client peut se faire en tenant compte des en-têtes envoyés par le navigateur dans sa requête, notamment l'affreux User-Agent:, très indiscret et en général mensonger. Mais la tendance actuelle est de ne pas utiliser systématiquement ces en-têtes, très dangereux pour la vie privée et parfois inutiles : si le serveur HTTP n'adapte pas le contenu en fonction des en-têtes, pourquoi les envoyer ? Ce nouveau RFC propose une solution : un en-tête Accept-CH: envoyé par le serveur qui indique ce que le serveur va faire des en-têtes d'indication envoyés par le client (client hints) dans ses futures requêtes. Il s'agit d'un projet Google, déjà mis en œuvre dans Chrome mais, pour l'instant, seulement avec le statut « Expérimental ».

Le Web a toujours été prévu pour être accédé par des machines très différentes, en terme de taille d'écran, de logiciels utilisés, de capacités de traitement. Sans compter les préférences propres à l'utilisateur, par exemple sa langue. C'est ainsi que, par exemple, les lignes de texte ne sont pas définies dans le source en HTML, elles seront calculées dynamiquement par le navigateur qui, lui, connait la largeur de la fenêtre. Mais il a été difficile de faire comprendre cela à des marketeux habitués de la page imprimée et qui insistaient pour contrôler tout au pixel près.

Bref, il y a longtemps que des gens qui ne connaissent pas le Web font n'importe quoi, par exemple en regardant l'en-tête User-Agent: (RFC 7231, section 5.5.3) et en ajustant le contenu Web à cet en-tête, ce qui nécessite une base de données de tous les User-Agent: possibles puisque chaque nouvelle version d'un navigateur peut changer les choses. (D'où ces User-Agent: ridicules et mensongers qu'on voit aujourd'hui, où chaque navigateur met le nom de tous les autres, par exemple Edge annonce Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.2704.79 Safari/537.36 Edge/18.014.) Ces techniques sont évidemment très mauvaises : elles compliquent les serveurs, elles ne permettent pas de gérer le cas d'un nouveau navigateur (sauf à ce qu'il annonce les noms de ces prédécesseurs dans User-Agent:, ce qui se fait couramment), elles ne permettent pas facilement à l'utilisateur ou à son navigateur de savoir quels critères ont été utilisés par le serveur pour adapter le contenu. Et elles sont très indiscrètes, exposant par défaut bien plus d'informations que ce qui est nécessaire au serveur pour adapter son contenu. C'est ainsi que des services comme Panopticlick ou Am I unique? peuvent fonctionner, démontrant la possibilité d'identifier un utilisateur sans biscuits, avec juste du fingerprinting passif.

Un site Web peut aussi ajuster son contenu en fonction d'un utilisateur particulier, suivi, par exemple, par les biscuits du RFC 6265, ou via des paramètres dans l'URL mais cela complique les choses, par exemple en obligeant à mettre des informations dans l'URL de chaque ressource du site Web.

Une meilleure solution, au moins en théorie, est la négociation de contenu HTTP (RFC 7231, section 3.4.1) : le client utilise alors des en-têtes bien définis (comme Accept-Language: pour la langue) auxquels le serveur peut répondre par un contenu spécifique. La négociation de contenu pose toutefois un problème, note le RFC, c'est que les en-têtes sont systématiquement envoyés par le client, qui ne sait pas si le serveur en fera quelque chose. Cela peut notamment avoir des conséquences en terme de vie privée, un client pouvant être identifié par le jeu complet des en-têtes qu'il envoie, même en l'absence de biscuits de traçage. (En outre, pour le cas particulier de la langue, la négociation de contenu n'aide guère car elle ne prend pas en compte des questions comme la notion de version originale.)

Au contraire, estime le RFC (avec pas mal d'optimisme), le mécanisme décrit ici peut potentiellement être davantage protecteur, en obligeant le serveur à annoncer quelles informations il va utiliser et en n'envoyant les informations permettant l'ajustement du contenu que lorsqu'elles sont explicitement demandées.

En quoi consiste ce mécanisme, justement ? La section 2 du RFC le décrit. Un en-tête CH (Client Hint, indication par le client) est un en-tête HTTP qui va envoyer des données permettant au serveur de modifier le contenu. Les clients HTTP (les navigateurs Web, par exemple) envoient les en-tête CH en fonction de leur propre configuration, et des demandes du serveur, via l'en-tête Accept-CH:. Le projet de spécification « Client Hints Infrastructure » discute de telles politiques et donne un jeu minimal d'indications qui n'identifient pas trop le client. Notre RFC ajoute que, par défaut, le client HTTP ne devrait pas envoyer de données autres que celles contenues dans ce jeu minimal. On verra bien si les navigateurs Web suivront cette recommandation. En tout cas, le RFC insiste bien sur le risque d'identification (fingerprinting), avec toute technique où le client exprime des préférences. Notez qu'à l'heure actuelle, les indications client ne sont pas encore spécifiées, mais il existe des propositions.

Si le client envoie des en-têtes CH (Client Hint), le serveur peut alors ajuster son contenu. Comme celui-ci dépend des en-têtes CH, le serveur doit penser à ajouter un en-tête Vary: (RFC 7231, section 7.1.4). Le serveur doit ignorer les indications qu'il ne comprend pas (ce qui permettra d'en déployer des nouvelles sans tout casser).

Comment est-ce que le serveur HTTP indique qu'il accepte le système des indications client (client hints) ? La section 3 de notre RFC décrit l'en-tête Accept-CH: (Accept Client Hints) qui sert à indiquer que le serveur connait bien ce système. Il figure désormais dans le registre des en-têtes. Accept-CH: est un en-tête structuré (RFC 8941) et sa valeur est une liste des indications qu'il accepte. Ainsi :

Accept-CH: CH-Example, CH-Example-again
  

indique que le serveur HTTP connait le système décrit dans notre RFC et qu'il utilise deux indications client, CH-Example et CH-Example-again. (À l'heure actuelle, il n'y a pas encore d'indications client normalisées, donc le RFC et cet article utilisent des exemples bidons.)

Voici un exemple plus complet. Le serveur veut savoir quel logiciel utilise le client et sur quel système d'exploitation. Il envoie :

Accept-CH: Sec-CH-UA-Full-Version, Sec-CH-UA-Platform
  

(UA = User Agent, le client HTTP, il s'agit ici de propositions d'indications client ; Sec- et CH- sont expliqués plus loin.) Le navigateur va alors, dans ses requêtes suivantes, et s'il est d'accord, envoyer :

Sec-CH-UA: "Examplary Browser"; v="73"
Sec-CH-UA-Full-Version: "73.3R8.2H.1"
Sec-CH-UA-Platform: "Windows"
  

(Notez que cet exemple particulier suppose que Sec-CH-UA: est envoyé systématiquement, même non demandé, point qui est encore en discussion.)

Ce système des indications client pose évidemment des problèmes de sécurité, analysés dans la section 4. D'abord, si les indications client ont été conçues pour mieux respecter la vie privée, elles ne sont pas non plus une solution magique. Un serveur malveillant peut indiquer une liste très longue dans son Accept-CH: espérant que les clients naïfs lui enverront plein d'informations. (Notons que les chercheurs en sécurité pourront analyser les réponses des serveurs HTTP et compiler des listes de ceux qui abusent, contrairement au système actuel où le serveur peut capter l'information de manière purement passive.) Le client prudent ne doit envoyer au serveur que ce qui est déjà accessible audit serveur par d'autres moyens (API JavaScript par exemple). Et il doit tenir compte des critères suivants :

  • Entropie : il ne faut pas envoyer d'information rare, qui pourrait aider à identifier une machine. Indiquer qu'on tourne sur Windows n'est pas bien grave, cela n'est pas rare. Mais un client tournant sur Plan 9 ne devrait pas s'en vanter auprès du serveur.
  • Sensibilité : les informations sensibles ne devraient pas être envoyées (le RFC ne donne pas d'exemple mais cela inclut certainement la localisation).
  • Stabilité : les informations qui changent rapidement ne devraient pas être envoyées.

À noter que des indications apparemment utiles, comme la langue, peuvent être dangereuses ; s'il s'agit d'une langue rare, elle peut aider à l'identification d'un utilisateur, s'il s'agit de la langue d'une minorité persécutée, elle peut être une information sensible.

Le RFC recommande (reste à savoir si cela sera suivi) que les clients HTTP devraient lier le Accept-CH: à une origine (RFC 6454) et n'envoyer donc les informations qu'à l'origine qui les demande. Ils devraient également être configurables, permettant à l'utilisateur d'envoyer plus ou moins d'informations selon son degré de méfiance. (Le RFC n'en parle pas, mais j'ajoute que la valeur par défaut - très bavarde ou au contraire très discrète - est un choix crucial, car peu d'utilisateurs changeront ce réglage.) Et le RFC demande que tout logiciel client permette également la discrétion totale, en n'envoyant aucune indication client. (André Sintzoff me fait remarquer à juste titre que « l'absence d'information est déjà une information ». Signaler qu'on ne veut pas être suivi à la trace peut vous signaler comme élement suspect, à surveiller.)

La liste des indications à envoyer pour chaque origine doit évidemment être remise à zéro lorsqu'on effectue des opérations de nettoyage, comme de supprimer les biscuits.

La même section 4 donne des conseils aux auteurs de futures indications client. On a vu qu'à l'heure actuelle aucune n'était encore normalisée mais des projets existent déjà, notamment pour remplacer User-Agent:. Un des choix cruciaux est de décider si les indications peuvent être générées par une application (typiquement du code JavaScript exécuté par le navigateur Web) ou bien seulement par le client. Les secondes sont évidemment plus sûres et c'est pour cela que leur nom est préfixée par Sec- (pour Secure). C'est une idée qui vient de la spécification Fetch.

Toujours côté nommage des indications client, le RFC recommande que le nom soit préfixé de CH- pour Client Hints, ce qui peut permettre de les distinguer facilement.

À l'heure actuelle, Chromium met déjà en œuvre ce système des indications client.


Téléchargez le RFC 8942


L'article seul

RFC 8941: Structured Field Values for HTTP

Date de publication du RFC : Février 2021
Auteur(s) du RFC : M. Nottingham (Fastly), P-H. Kamp (The Varnish Cache Project)
Chemin des normes
Réalisé dans le cadre du groupe de travail IETF httpbis
Première rédaction de cet article le 9 février 2021


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 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 existants ne seront pas changés, pour préserver la compatibilité.

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 7231, section 8.3.1, 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 spécifiée en ABNF (RFC 5234) mais le RFC fournit aussi des algorithmes d'analyse et, malheureusement, exige que, en cas de désaccord entre les algorithmes et la grammaire, ce soit les algorithmes qui l'emportent.

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 :

  • Inclure une référence à ce RFC 8941.
  • J'ai parlé jusqu'à présent d'en-tête structuré mais en fait ce RFC 8941 s'applique aussi aux pieds (trailers) (RFC 7230, sections 4.1.2 et 4.4 ; en dépit de leur nom, les pieds sont des en-têtes eux aussi). La norme devra préciser si elle s'applique seulement aux en-têtes classiques ou bien aussi aux pieds.
  • Spécifier si la valeur sera une liste, un dictionnaire ou juste un élément (ces termes sont définis en section 3). Dans l'exemple ci-dessus Foo-Example:, la valeur était un élément, de type entier.
  • Penser à définir la sémantique.
  • Et ajouter les règles supplémentaires sur la valeur du champ, s'il y en a. Ainsi, l'exemple avec 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 8941. 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) :

ListOfListsOfStrings-Example: ("foo" "bar"), ("baz"), ("bat" "one"), ()    
  

Le cas des listes vides avait occupé une bonne partie des discussions à l'IETF (le vide, ça remplit…) Ainsi, un en-tête structuré dans 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, 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 doivent hélas être en ASCII (RFC 20). Si on veut envoyer de l'Unicode, il faudra utiliser le type « contenu binaire » en précisant l'encodage (sans doute UTF-8). 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 programmeu·r·se·s 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 programmeu·r·se·s, l'annexe B du RFC donne quelques conseils pour les auteur·e·s 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 n'y a dans le registre des en-têtes qu'un seul en-tête structuré, le Accept-CH: du RFC 8942. Sa valeur est une liste d'identificateurs. Mais plusieurs autres en-têtes structurés sont en préparation.

Si vous voulez un exposé synthétique sur ces en-têtes structuré, 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.6/site-packages/http_sfv-0.9.1-py3.6.egg/http_sfv/util.py", line 57, in parse
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.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.parse(b"en=\"Applepie\", fr=\"Tarte aux pommes\"")
>>> print(my_dict)
en="Applepie", fr="Tarte aux pommes"
>>> print(my_dict["fr"])
"Tarte aux pommes"


Téléchargez le RFC 8941


L'article seul

RFC 8937: Randomness Improvements for Security Protocols

Date de publication du RFC : Octobre 2020
Auteur(s) du RFC : C. Cremers (CISPA Helmholtz Center for Information Security), L. Garratt (Cisco Meraki), S. Smyshlyaev (CryptoPro), N. Sullivan, C. Wood (Cloudflare)
Pour information
Réalisé dans le cadre du groupe de recherche IRTF cfrg
Première rédaction de cet article le 17 octobre 2020


Tout le monde sait désormais que la génération de nombres aléatoires (ou, plus rigoureusement, de nombres imprévisibles, pseudo-aléatoires) est un composant crucial des solutions de sécurité à base de cryptographie, comme TLS. Les failles des générateurs de nombres pseudo-aléatoires, ou bien les attaques exploitant une faiblesse de ces générateurs sont les problèmes les plus fréquents en cryptographie. Idéalement, il faut utiliser un générateur sans défauts. Mais, parfois, on n'a pas le choix et on doit se contenter d'un générateur moins satisfaisant. Ce nouveau RFC décrit un mécanisme qui permet d'améliorer la sécurité de ces générateurs imparfaits, en les nourrissant avec des clés privées.

Un générateur de nombres pseudo-aléatoires cryptographique (CSPRNG, pour Cryptographically-Strong PseudoRandom Number Generator) produit une séquence de bits qui est indistinguable d'une séquence aléatoire. Un attaquant qui observe la séquence ne peut pas prédire les bits suivants.

On les nomme générateur pseudo-aléatoires car un générateur réellement aléatoire devrait être fondé sur un phénomène physique aléatoire. Un exemple classique d'un tel phénomène, dont la théorie nous dit qu'il est réellement aléatoire, est la désintégration radioactive. Des exemples plus pratiques sont par exemple des diodes forcées de produire un bruit blanc (comme dans le Cryptech). (Ou alors un chat marchant sur le clavier ?) Mais, en pratique, les ordinateurs se servent de générateurs pseudo-aléatoires, ce qui suffit s'ils sont imprévisibles à un observateur extérieur.

Beaucoup de solutions de sécurité, même en l'absence de cryptographie, dépendent de cette propriété d'imprévisibilité. Ainsi, TCP, pour protéger contre un attaquant aveugle (situé en dehors du chemin du paquet), a besoin de numéros de séquence initiaux qui soient imprévisibles (RFC 5961). Et la cryptographie consomme énormément de ces nombres pseudo-aléatoires. Ainsi, TLS (RFC 8446) se sert de tels nombres à de nombreux endroits, par exemple les numniques utilisés dans le ClientHello, sans compter les nombres pseudo-aléatoires utilisés lors de la génération des clés de session. La plupart des autres protocoles de chiffrement dépendent de tels nombres que l'attaquant ne peut pas prévoir.

Réaliser un générateur pseudo-aléatoire sur une machine aussi déterministe qu'un ordinateur n'est pas facile, en l'absence de source quantique comme un composant radioactif. (Cf. RFC 4086.) La solution adoptée la plus simple est d'utiliser une fonction qui calcule une séquence de nombres pseudo-aléatoires d'une manière que, même en observant la séquence, on ne puisse pas deviner le nombre suivant (la mathématique fournit plusieurs fonctions pour cela). Comme l'algorithme est en général connu, et que l'ordinateur est déterministe, s'il connait le premier nombre, la graine, un observateur pourrait calculer toute la séquence. La graine ne doit donc pas être connue des observateurs extérieurs. Elle est par exemple calculée à partir d'éléments qu'un tel observateur ne connait pas, comme des mouvements physiques à l'intérieur de l'ordinateur, ou bien provoqués par l'utilisateur.

De tels générateurs pseudo-aléatoires sont très difficiles à réaliser correctement et, en cryptographie, il y a eu bien plus de failles de sécurité dues à un générateur pseudo-aléatoire que dues aux failles des algorithmes de cryptographie eux-mêmes. L'exemple le plus fameux était la bogue Debian, où une erreur de programmation avait réduit drastiquement le nombre de graines possibles (l'entropie). Un autre exemple amusant est le cas du générateur cubain qui oubliait le chiffre 9.

Mais le générateur pseudo-aléatoire peut aussi être affaibli délibérement, pour faciliter la surveillance, comme l'avait fait le NIST, sur ordre de la NSA, en normalisant le générateur Dual-EC avec une faille connue.

Obtenir une graine de qualité est très difficile, surtout sur des appareils ayant peu de pièces mobiles, donc peu de mouvements physiques qui pourraient fournir des données aléatoires. Un truc avait déjà été proposé : combiner la graine avec la clé privée utilisée pour la communication (par exemple en prenant un condensat de la concaténation de la graine avec la clé privée, pour faire une graine de meilleure qualité), puisque la clé privée est secrète. L'idée vient du projet Naxos. (Cela suppose bien sûr que la clé privée n'ait pas été créée par la source qu'on essaie d'améliorer. Elle peut par exemple avoir été produite sur un autre appareil, ayant davantage d'entropie.) Le problème de cette approche avec la clé privée, est que les clés privées sont parfois enfermées dans un HSM, qui ne les laisse pas sortir.

La solution proposée par notre RFC est de ne pas utiliser directement la clé secrète, mais une signature de la graine. C'est une valeur qui est imprévisible par un observateur, puisqu'elle dépend de la clé privée, que l'attaquant ne connait pas. Ainsi, on peut obtenir une séquence de nombres pseudo-aléatoires que l'observateur ne pourra pas distinguer d'une séquence réellement aléatoire, même si la graine initiale n'était pas terrible.

L'algorithme exact est formulé dans la section 3 du RFC. Le Nième nombre pseudo-aléatoire est l'expansion (RFC 5869, section 2.3) de l'extraction (RFC 5869, section 2.2) d'un condensat de la signature d'une valeur qui ne change pas à chaque itération de la séquence, ce qui fait que cet algorithme reste rapide. (J'ai simplifié, il y a d'autres paramètres, notamment la valeur « normale » de la séquence, initiée à partir de la graine.) La signature doit évidemment être secrète (sinon, on retombe dans le générateur pseudo-aléatoire d'avant ce RFC). La fonction de signature doit être déterministe (exemple dans le RFC 6979). La « valeur qui ne change pas à chaque itération » peut, par exemple (section 4 du RFC) être l'adresse MAC de la machine mais attention aux cas de collisions (entre VM, par exemple). Une autre valeur constante est utilisée par l'algorithme et peut, par exemple, être un compteur, ou bien l'heure de démarrage. Ces deux valeurs n'ont pas besoin d'être secrètes.

Une analyse de sécurité de ce mécanisme a été faite dans l'article « Limiting the impact of unreliable randomness in deployed security protocols ». Parmi les points à surveiller (section 9), la nécessité de bien garder secrète la signature, ce qui n'est pas toujours évident notamment en cas d'attaque par canal auxiliaire.

À propos de générateurs pseudo-aléatoires, je vous recommande cet article très détaillé en français sur la mise en œuvre de ces générateurs sur Linux.

Merci à Kim Minh Kaplan pour une bonne relecture.


Téléchargez le RFC 8937


L'article seul

RFC 8932: Recommendations for DNS Privacy Service Operators

Date de publication du RFC : Octobre 2020
Auteur(s) du RFC : S. Dickinson (Sinodun IT), B. Overeinder (NLnet Labs), R. van Rijswijk-Deij (NLnet Labs), A. Mankin (Salesforce)
Réalisé dans le cadre du groupe de travail IETF dprive
Première rédaction de cet article le 25 octobre 2020


Vous gérez un résolveur DNS qui promet de protéger la vie privée des utilisateurs et utilisatrices ? Alors, vous serez certainement intéressé par ce nouveau RFC qui rassemble les bonnes pratiques en matière de gestion d'un tel résolveur, et explique comment documenter la politique du résolveur, les choix effectués. On y trouve une bonne analyse des questions de sécurité, une discussion de ce qui doit être dans une politique, une analyse comparée des politiques, et même un exemple de politique.

Depuis la publication du RFC 7626, les risques pour la vie privée posés par l'utilisation du DNS sont bien connus. Par exemple, un de ces risques est que le trafic DNS entre une machine terminale et le résolveur est en clair et peut donc trivialement être écouté, ce qui est d'autant plus gênant que ce trafic inclut toutes les informations (il n'y a pas de minimisation possible de la question, par exemple). Un autre risque est que le gérant du résolveur voit forcément tout le trafic, même si on chiffre le trafic entre la machine terminale et lui. Il peut abuser de cette confiance qu'on lui fait. Une des solutions possibles face à ces risques est d'utiliser, non pas le résolveur annoncé par le réseau d'accès à l'Internet mais un résolveur extérieur, à qui vous faites confiance, et avec qui vous communiquez de manière chiffrée. De tels résolveurs existent. Certains sont publics (accessibles à tous), gérés par des GAFA comme Cloudflare (avec son résolveur 1.1.1.1) ou gérés par des associations ou bien des individus (vous trouverez une liste partielle à la fin du README de ce logiciel). D'autres de ces résolveurs sont réservés à tel ou tel groupe ou organisation.

Le problème pour l'utilisateur est celui du choix : lequel prendre ? Pour cela, il faut déjà que ceux et celles qui gèrent le résolveur aient documenté leurs pratiques et qu'on leur fasse confiance pour respecter leurs promesses. C'est l'un des buts de ce RFC : fournir un cadre général de description des pratiques d'un résolveur « vie privée », pour faciliter la tâche des rédacteurs et rédactrices de ces documents, dans l'esprit du RFC 6841, qui faisait la même chose pour DNSSEC. Notre RFC n'impose pas une politique particulière, il décrit juste les choix possibles, et ce qu'il ne faut pas oublier de mettre dans son RPS, son Recursive operator Privacy Statement.

Optimiste, notre RFC estime que des promesses formelles de strict préservation de la vie privée des utilisateurs seraient même un avantage pour les résolveurs ayant de tels engagements, leur apportant davantage d'utilisateurs. L'expérience du Web avec le succès des GAFA, entreprises capitalistes captatrices de données personnelles, même lorsqu'une alternative libre et respectueuse de la vie privée existe, me fait douter de ce pronostic.

Notez que la question du choix d'un résolveur est une question complexe. Le RFC cite par exemple le fait qu'avoir un résolveur stable, utilisé pour toutes les connexions d'une machine mobile, peut permettre à ce résolveur de vous suivre, lorsque vous changez de réseau d'accès.

La section 2 décrit le domaine d'applicabilité de ce document : il vise les gérants de résolveurs DNS, pas les utilisateurs finaux, ni les gérants de serveurs faisant autorité. Suivant les principes des RFC 6973 et RFC 7626, il va se pencher sur ce qui arrive aux données en transit sur l'Internet, aux données au repos sur un des serveurs (journaux, par exemple) et aux données transmises pour effectuer le travail (requêtes envoyées par un résolveur aux serveurs faisant autorité, lorsque la réponse n'est pas déjà dans la mémoire du résolveur).

La section 5 est le cœur de notre RFC, elle décrit les recommandations concrètes. Pour illustrer ces recommandations, je dirai à chaque fois comment elles ont été mises en œuvre sur le résolveur sécurisé que je gère, dot.bortzmeyer.fr & https://doh.bortzmeyer.fr/. Non pas qu'il soit le meilleur, le plus rapide, le plus sûr ou quoi que ce soit d'autre. Mais parce qu'il met en œuvre les recommandations de ce RFC, et que je sais qu'il respecte sa politique affichée. Ces notes sur mon résolveur apparaitront entre crochets.

D'abord, en transit, les communications faites en clair peuvent être écoutées par un surveillant passif et, pire, modifiées par un attaquant actif (section 5.1 du RFC). La première recommandation va de soi, il faut chiffrer. Un résolveur DNS public qui n'aurait pas de chiffrement (comme ceux actuellement proposés par certaines associations) n'a guère d'intérêt du point de vue de la vie privée. Pour ce chiffrement, deux techniques normalisées, DoT (DNS sur TLS, RFC 7858 et RFC 8310) et DoH (DNS sur HTTPS, RFC 8484). [Mon résolveur les déploie toutes les deux.] Il existe aussi un DNS sur DTLS (RFC 8094) mais qui n'a eu aucun succès, et des techniques non normalisées comme DNSCrypt, ou une forme ou l'autre de VPN vers le résolveur. Le RFC note que le chiffrement protège le canal, pas les données, et qu'il ne dispense donc pas de DNSSEC (cf. section 5.1.4). [Évidemment mon résolveur valide avec DNSSEC.] Un avantage des canaux sécurisés créés avec DoT ou DoH est qu'il y a beaucoup moins de risque que DNSSEC soit bloqué (cf. RFC 8027).

[Beaucoup de techniciens ont tendance à limiter la protection de la vie privée au chiffrement. C'est un exemple de fascination pour une technique complexe, au détriment d'autres mesures moins geek, qui sont présentées plus loin. Le chiffrement est nécessaire mais certainement pas suffisant.]

Chiffrer n'est pas très utile si on n'a pas authentifié celui avec qui on communique. Un attaquant actif peut toujours tenter de se faire passer pour le serveur qu'on essaie de joindre, par exemple par des attaques BGP ou tout simplement en injectant dans son réseau les routes nécessaires (comme le cas turc). Il faut donc authentifier le résolveur. DoT ne présentait guère de solutions satisfaisantes à l'origine, mais ça s'est amélioré avec le RFC 8310. Un résolveur DoT doit donc présenter un certificat qui permette son authentification ou bien publier sa clé publique (SPKI Subject Public Key Info, mais son utilisation est aujourd'hui déconseillée, car elle rend difficile le changement de clé). Le certificat peut contenir un nom ou une adresse IP. S'il contient un nom, il faut que le client connaisse ce nom, pour l'authentifier (un résolveur DNS traditionnel n'était connu que par son adresse IP). Ainsi, le certificat du résolveur Quad9 contient nom(s) et adresse(s) IP :

% openssl s_client -showcerts -connect 9.9.9.9:853 | openssl x509 -text
...
        Subject: C = US, ST = California, L = Berkeley, O = Quad9, CN = *.quad9.net
...
            X509v3 Subject Alternative Name: 
                DNS:*.quad9.net, DNS:quad9.net, IP Address:9.9.9.9, IP Address:9.9.9.10, IP Address:9.9.9.11, IP Address:9.9.9.12, IP Address:9.9.9.13, IP Address:9.9.9.14, IP Address:9.9.9.15, IP Address:149.112.112.9, IP Address:149.112.112.10, IP Address:149.112.112.11, IP Address:149.112.112.12, IP Address:149.112.112.13, IP Address:149.112.112.14, IP Address:149.112.112.15, IP Address:149.112.112.112, IP Address:2620:FE:0:0:0:0:0:9, IP Address:2620:FE:0:0:0:0:0:10, IP Address:2620:FE:0:0:0:0:0:11, IP Address:2620:FE:0:0:0:0:0:12, IP Address:2620:FE:0:0:0:0:0:13, IP Address:2620:FE:0:0:0:0:0:14, IP Address:2620:FE:0:0:0:0:0:15, IP Address:2620:FE:0:0:0:0:0:FE, IP Address:2620:FE:0:0:0:0:FE:9, IP Address:2620:FE:0:0:0:0:FE:10, IP Address:2620:FE:0:0:0:0:FE:11, IP Address:2620:FE:0:0:0:0:FE:12, IP Address:2620:FE:0:0:0:0:FE:13, IP Address:2620:FE:0:0:0:0:FE:14, IP Address:2620:FE:0:0:0:0:FE:15
...
  

[Mon résolveur, lui, utilise Let's Encrypt, qui ne permet pas encore (cf. RFC 8738) de mettre une adresse IP dans le certificat. Il n'y a donc que le nom. On peut aussi l'authentifier avec sa clé publique (SPKI), qui vaut eHAFsxc9HJW8QlJB6kDlR0tkTwD97X/TXYc1AzFkTFY=.] Puisqu'on parle de certificats : le RFC note à juste titre que les opérateurs de serveurs DNS ne sont pas forcément experts en la matière, sauf à demander de l'aide à leurs collègues HTTP qui gèrent ces certificats depuis longtemps. Le RFC recommande donc d'automatiser (par exemple avec ACME, cf. RFC 8555), et de superviser les certificats (c'est le B A BA, mais combien d'administrateurs système ne le font toujours pas ?).

Le RFC a également des recommandations techniques sur les protocoles utilisés. En effet :

Il est donc conseillé de suivre les recommandations TLS du RFC 7525, de n'utiliser que des versions récentes de TLS. Un rappel, d'ailleurs : les services comme SSLabs.com peuvent parfaitement tester votre résolveur DoH. [J'ai une bonne note.] ssllabs-doh.png

Il est également conseillé de faire du remplissage (RFC 7830 et RFC 8467 ou bien, pour DoH, avec le remplissage HTTP/2), et de ne pas imposer des fonctions qui sont dangereuses pour la vie privée comme la reprise de session TLS (RFC 5077), ou comme les cookies (DNS, RFC 7873 ou HTTP, RFC 6265). [Le logiciel que j'utilise pour mon résolveur, dnsdist, semble faire du remplissage DNS, mais je n'ai pas testé.]

Question non plus de sécurité mais de performance, le RFC recommande de gérer les requêtes en parallèle, y compris de pouvoir donner des réponses dans le désordre (RFC 7766) et de maintenir les sessions TLS ouvertes (RFC 7766 et RFC 7828, voire RFC 8490). [Le logiciel utilisé sur mon résolveur, dnsdist, ne sait pas encore générer des réponses dans un ordre différent de celui d'arrivée.]

Le résolveur DNS est un composant crucial de l'utilisation de l'Internet. S'il est en panne, c'est comme si tout l'Internet est en panne. La disponibilité du résolveur est donc une question cruciale. Si les pannes sont trop fréquentes, les utilisateurs risquent de se rabattre sur des solutions qui ne respectent pas leur vie privée, voire sur des solutions non sécurisées. Le risque est d'autant plus élevé qu'il y a des attaques par déni de service visant les résolveurs DNS (cf. cet article de l'AFNIC). Le RFC recommande donc de tout faire pour assurer cette disponibilité. [Mon propre résolveur, étant un projet personnel, et installé sur un VPS ordinaire, n'est certainement pas bien placé de ce point de vue.]

Bien sûr, il faut fournir aux utilisateurs des services qui protègent la vie privée les mêmes options qu'aux autres visiteurs : résolveur non menteur, validation DNSSEC, etc. (Il existe des services où les choix sont exclusifs et où, par exemple, choisir un résolveur non menteur prive de DNSSEC.)

La protection de la vie privée ne plait pas à tout le monde. Les partisans de la surveillance ont défendu leur point de vue dans le RFC 8404 en réclamant de la visibilité sur le trafic, justement ce que le chiffrement veut empêcher. Ce RFC 8932 essaie de ménager la chèvre et le chou en signalant aux opérateurs de résolveurs chiffrants qu'ils ont toujours accès au trafic en clair, en prenant les données après leur déchiffrement, sur le résolveur. C'est ce que permet, par exemple, la technologie dnstap. Après tout, TLS ne protège qu'en transit, une fois arrivé sur le résolveur, les données sont en clair. [Mon résolveur ne copie pas le trafic en clair, qui n'est jamais examiné. Le logiciel utilisé est capable de faire du dnstap, mais a été compilé sans cette option.] Notez que ce RFC 8932 reprend hélas sans nuances les affirmations du RFC 8404 comme quoi il est légitime d'avoir accès aux données de trafic.

Une technique courante pour mettre en œuvre un résolveur avec TLS est de prendre un résolveur ordinaire, comme BIND, et de le placer derrière un relais qui terminera la session TLS avant de faire suivre au vrai résolveur, typiquement situé sur la même machine pour des raisons de sécurité. Des logiciels comme stunnel, haproxy ou nginx permettent de faire cela facilement, et fournissent des fonctions utiles, par exemple de limitation du trafic. Mais cela ne va pas sans limites. Notamment, avec cette méthode, le vrai résolveur ne voit pas l'adresse IP du client, mais celle du relais. Cela interdit d'utiliser des solutions de sécurité comme les ACL ou comme RRL (Response Rate Limiting). [Mon résolveur utilise un relais, mais avec un logiciel spécialisé dans le DNS, dnsdist. dnsdist peut résoudre le problème de la mauvaise adresse IP en utilisant le PROXY protocol mais je n'utilise pas cette possibilité.]

Nous avons vu jusqu'à présent le cas des données en transit, entre le client et le résolveur DNS. La réponse principale aux problèmes de vie privée était le chiffrement. Passons maintenant au cas des données « au repos », stockées sur le résolveur, par exemple dans des journaux, ou dans des pcap (ou équivalent) qui contiennent le trafic enregistré (section 5.2). Évidemment, pour qu'un service puisse se prétendre « protecteur de la vie privée », il faut qu'une telle rétention de données soit très limitée, notamment dans le temps (c'est un des principes du RGPD, par exemple). Si on garde de telles données pour des raisons légitimes (statistiques, par exemple), il est recommandé de les agréger afin de fournir un peu d'anonymisation. (La lecture recommandée sur ce point est le RFC 6973.) Pour des problèmes à court terme (analyser et comprendre une attaque par déni de service en cours, par exemple), le mieux est de ne pas stocker les données sur un support pérenne, mais de les garder uniquement en mémoire. [Mon résolveur n'enregistre rien sur disque. Des statistiques fortement agrégées sont possibles mais, à l'heure actuelle, ce n'est pas fait.]

Si le chiffrement est le principal outil technique dont on dispose pour protéger les données lorsqu'elles se déplacent d'une machine à l'autre, il est moins utile lorsque des données sont enregistrées sur le serveur. Ici, l'outil technique essentiel est la minimisation des données. C'est le deuxième pilier d'une vraie protection de la vie privée, avec le chiffrement, mais elle est très souvent oubliée, bien que des textes juridiques comme le RGPD insistent sur son importance. Mais attention, minimiser des données n'est pas facile. On a fait de gros progrès ces dernières années en matière de ré-identification de personnes à partir de données qui semblaient minimisées. Comme le note le RFC, il n'y a pas de solution générale, largement acceptée, qui permette de minimiser les données tout en gardant leur utilité. C'est pour cela que quand un décideur sérieux et sûr de lui affirme bien fort « Ne vous inquiétez pas de cette base de données, tout est anonymisé », vous pouvez être raisonnablement sûr qu'il est soit malhonnête, soit incompétent ; réellement anonymiser est très difficile.

Cela fait pourtant quinze ou vingt ans qu'il y a des recherches actives en « anonymisation », motivées entre autre par la volonté de partager des données à des fins de recherches scientifiques. Mais le problème résiste. Et les discussions sont difficiles, car les discoureurs utilisent souvent une terminologie floue, voire incorrecte (par mauvaise foi, ou par ignorance). Notre RFC rappelle donc des points de terminologie (déjà abordés dans le RFC 6973) :

  • Anonymiser, c'est faire en sorte qu'on ne puisse plus distinguer un individu d'un autre individu. C'est une propriété très forte, difficile à atteindre, puisqu'il s'agit de supprimer toute traçabilité entre différentes actions. En général, il faut supprimer ou tronquer une bonne partie des données pour y arriver.
  • Pseudonymiser, c'est remplacer un identificateur par un autre. (Le RFC parle de remplacer la « vraie » identité par une autre mais il n'existe pas d'identité qui soit plus vraie qu'une autre.) Ainsi, remplacer une adresse IP par son condensat SHA-256 est une pseudonymisation. Avec beaucoup de schémas de pseudonymisation, remonter à l'identité précédente est possible. Ici, par exemple, retrouver l'adresse IP originale est assez facile (en tout cas en IPv4).

Quand un politicien ou un autre Monsieur Sérieux parle d'anonymisation, il confond presque toujours avec la simple pseudonymisation. Mais la distinction entre les deux n'est pas toujours binaire.

Dans les données d'un résolveur DNS, l'une des principales sources d'information sur l'identité de l'utilisateur est l'adresse IP source (cf. RFC 7626, section 2.2). De telles adresses sont clairement des données personnelles et il serait donc intéressant de les « anonymiser ». De nombreuses techniques, de qualité très variable, existent pour cela, et le RFC les présente dans son annexe B, que je décris à la fin de cet article. Aucune de ces techniques ne s'impose comme solution idéale marchant dans tous les cas. (À part, évidemment, supprimer complètement l'adresse IP.) Notez qu'il existe d'autres sources d'information sur le client que son adresse IP, comme la question posée (s'il demande security.debian.org, c'est une machine Debian) ou comme le fingerprinting des caractéristiques techniques de sa session. Le problème est évidemment pire avec DoH, en raison des innombrables en-têtes HTTP très révélateurs que les navigateurs s'obstinent à envoyer (User-Agent:, par exemple). Dans certains cas, on voit même des équipements comme la box ajouter des informations précises sur le client.

Ces données stockées sur le résolveur peuvent parfois rester uniquement dans l'organisation qui gère le résolveur, mais elles sont parfois partagées avec d'autres. (Méfiez-vous des petites lettres : quand on vous promet que « nous ne vendrons jamais vos données », cela ne veut pas dire qu'elles ne seront pas partagées, juste que le partageur ne recevra pas directement d'argent pour ce partage.) Le partage peut être utile pour la science (envoi des données à des chercheurs qui feront ensuite d'intéressants articles sur le DNS), mais il est dangereux pour la vie privée. Comme exemple d'organisation qui partage avec les universités, voir SURFnet et leur politique de partage. [Mon résolveur ne partage avec personne.]

Après les données qui circulent entre le client et le résolveur, puis le cas des données stockées sur le résolveur, voyons le cas des données envoyées par le résolveur, pour obtenir la réponse à ses questions (section 5.3 du RFC). Bien sûr, cela serait très bien si le résolveur chiffrait ses communications avec les serveurs faisant autorité, mais il n'existe actuellement pas de norme pour cela (des propositions ont été faites, et des tests menés, mais sans résultat pour l'instant).

Et, de toute façon, cela ne protègerait que contre un tiers, pas contre les serveurs faisant autorité qui enregistrent les questions posées. Face à ce risque, la principale recommandation du RFC est d'utiliser la QNAME minimisation (RFC 7816) pour réduire ces données, en application du principe général « ne pas envoyer plus que ce qui est nécessaire ». [Mon résolveur DoT/DoH fait appel à un résolveur qui fait cela.] Seconde recommandation, respecter les consignes ECS (RFC 7871) : si le client demande à ce que les données ECS soient réduites ou supprimées, il faut lui obéir. [Mon résolveur débraye ECS avec la directive dnsdist useClientSubnet=false.] (Notez que, par défaut, cela permet toujours au résolveur de transmettre aux serveurs faisant autorité l'adresse de son client…)

Deux autres façons, pour le résolveur, de limiter les données qu'il envoie aux serveurs faisant autorité :

  • Générer des réponses à partir de sa mémoire (RFC 8020 et RFC 8198),
  • avoir une copie locale de la racine, privant les serveurs racine d'informations (RFC 8806).

[Sur mon résolveur DoH/DoT, le vrai résolveur qui tourne derrière, un Unbound, a les options harden-below-nxdomain et aggressive-nsec, qui mettent à peu près en œuvre les RFC 8020 et RFC 8198.] On peut aussi imaginer un résolveur qui envoie des requêtes bidon, pour brouiller les informations connues du serveur faisant autorité, ou, moins méchamment, qui demande en avance (avant l'expiration du TTL) des données qui sont dans sa mémoire, sans attendre une requête explicite d'un de ses clients, pour casser la corrélation entre un client et une requête. Pour l'instant, il n'existe pas de recommandations consensuelles sur ces techniques. Enfin, un résolveur peut aussi faire suivre toutes ses requêtes à un résolveur plus gros (forwarder), au-dessus d'un lien sécurisé. Cela a l'avantage que le gros résolveur, ayant une mémoire plus importante, enverra moins de données aux serveurs faisant autorité, et que sa base de clients plus large rendra plus difficile la corrélation. Évidemment, dans ce cas, le danger peut venir du gros résolveur à qui on fait tout suivre, et le choix final n'est donc pas évident du tout. [Mon résolveur ne fait pas suivre à un gros résolveur public, aucun ne me semblant satisfaisant. Comme il a peu de clients, cela veut dire que les données envoyées aux serveurs faisant autorité peuvent être un problème, question vie privée.]

Une fois que cette section 5 du RFC a exposé tous les risques et les solutions possibles, la section 6 parle de la RPS, Recursive operator Privacy Statement, la déclaration officielle des gérants d'un résolveur à ses clients, exposant ce qu'ils ou elles font, et ce qu'elles ou ils ne font pas avec les données. (Au début du développement de ce RFC, la RPS se nommait DROP - DNS Recursive Operator Privacy statement, ce qui était plus drôle.) Notre RFC recommande très fortement que les opérateurs d'un résolveur DNS publient une RPS, l'exposition de leur politique. Le but étant de permettre aux utilisateurs humains de choisir en toute connaissance de cause un résolveur ayant une politique qu'ils acceptent. Évidemment, il y a des limites à cette idée. D'abord, les utilisateurs ne lisent pas forcément les conditions d'utilisation / codes de conduite et autres textes longs et incompréhensibles. (Le RFC propose des recommandations sur la rédaction des RPS, justement pour diminuer ce problème, en facilitant les comparaisons.) [Mon résolveur a une RPS publique, accessible en https://doh.bortzmeyer.fr/policy. Rédigée bien avant la publication de ce RFC, elle y est pourtant relativement conforme.]

Le RFC propose un plan pour la RPS. Rien n'est évidemment obligatoire dans ce plan, mais c'est une utile liste pour vérifier qu'on n'a rien oublié. Le but est de faire une RPS utilisable, et lisible, et le plan ne couvre pas donc d'éventuelles questions financières (si le service est payant), et ne prétend pas être un document juridique rigoureux, puisqu'elle doit pouvoir être lue et comprise.

Parmi les points à considérer quand on écrit une RPS :

  • Le statut des adresses IP : bien préciser qu'elles sont des données personnelles,
  • le devenir des données : collectées ou non, si elles sont collectées, partagées ou non, gardées combien de temps,
  • si les données sont « anonymisées », une description détaillée du processus d'anonymisation (on a vu que la seule proclamation « les données sont anonymisées », sans détail, était un signal d'alarme, indiquant que l'opérateur se moque de vous),
  • les exceptions, car toute règle doit permettre des exceptions, par exemple face à une attaque en cours, qui peut nécessiter de scruter avec tcpdump, même si on se l'interdit normalement,
  • les détails sur l'organisation qui gère le résolveur, ses partenaires et ses sources de financement,
  • et l'éventuel filtrage des résultats (« DNS menteur ») : a-t-il lieu, motifs de filtrage (noms utilisés pour la distribution de logiciel malveillant, par exemple), loi applicable (« nous sommes enregistrés en France donc nous sommes obligés de suivre les lois françaises »), mécanismes de décision internes (« nous utilisons les sources X et Y de noms à bloquer »)…

L'opérateur du résolveur qui veut bien faire doit également communiquer à ses utilisateurs :

  • Les écarts par rapport à la politique affichée, s'il a fallu en urgence dévier des principes,
  • les détails techniques de la connexion (est-ce qu'on accepte seulement DoH, seulement DoT, ou les deux, par exemple), et des services (est-ce qu'on valide avec DNSSEC ?),
  • les détails de l'authentification du résolveur : nom à valider, clé publique le cas échéant.

Un autre problème avec ces RPS est évidemment le risque de mensonge. « Votre vie privée est importante », « Nous nous engageons à ne jamais transmettre vos données à qui que ce soit », il serait très naïf de prendre ces déclarations pour argent comptant. Le RFC est réaliste et sa section 6.2 du RFC parle des mécanismes qui peuvent permettre, dans le cas idéal, d'augmenter légèrement les chances que la politique affichée soit respectée. Par exemple, on peut produire des transparency reports où on documente ce qu'on a été forcés de faire (« en raison d'une injonction judiciaire, j'ai dû ajouter une règle pour bloquer machin.example »). Des vérifications techniques peuvent être faites de l'extérieur, comme l'uptime, le remplissage ou la minimisation des requêtes. De tels outils existent pour la qualité TLS des serveurs, comme SSLlabs.com, déjà cité, ou Internet.nl. [Opinion personnelle : beaucoup de tests d'Internet.nl ne sont pas pertinents pour DoH.] Plus fort, on peut recourir à des audits externes, qui augmentent normalement la confiance qu'on accorde à la RPS, puisqu'un auditeur indépendant aura vérifié sa mise en œuvre. [Mon avis est que c'est d'une fiabilité limitée, puisque l'auditeur est choisi et payé par l'organisation qui se fait auditer… Et puis, même si vous êtes root sur les machines de l'organisation auditée, ce qui n'arrive jamais, s'assurer que, par exemple, les données sont bien détruites au bout du temps prescrit est non-trivial.] Cloudflare est ainsi audité (par KPMG) et vous pouvez lire le premier rapport (très court et sans aucun détail sur le processus de vérification).

L'annexe A du RFC liste les documents, notamment les RFC, qui sont pertinents pour les opérateurs de résolveurs promettant le respect de la vie privée. On y trouve les normes techniques sur les solutions améliorant la protection de l'intimité, mais aussi celles décrivant les solutions qui peuvent diminuer la vie privée, comme ECS (RFC 7871), les cookies DNS (RFC 7873), la reprise de sessions TLS (RFC 5077, un format pour stocker des données sur le trafic DNS (RFC 8618, mais il y a aussi le traditionnel pcap), le passive DNS (RFC 8499), etc.

L'annexe C du RFC cite une intéressante comparaison, faite en 2019, de diverses RPS (Recursive operator Privacy Statement, les politiques des gérants de résolveurs). Comme ces RPS sont très différentes par la forme, une comparaison est difficile. Certaines font plusieurs pages, ce qui les rend longues à analyser. Aujourd'hui, en l'absence de cadres et d'outils pour éplucher ces RPS, une comparaison sérieuse par les utilisateurs n'est pas réaliste. Quelques exemples réels : la RPS de mon résolveur (la seule en français), celle de PowerDNS, celle de Quad9, celle de CloudflareMozilla a développé, pour son programme TRR (Trusted Recursive Resolve), une liste de critères que les résolveurs qui veulent rejoindre le programme doivent respecter. Une sorte de méta-RPS. [Apparemment, le seul truc qui me manque est le transparency report annuel.]

L'annexe D du RFC contient un exemple de RPS. Elle ne prétend pas être parfaite, et il ne faudrait surtout pas la copier/coller directement dans une vraie RPS mais elle peut servir de source d'inspiration. Notez également qu'elle est écrite en anglais, ce qui ne conviendra pas forcément à un service non-étatsunien. Parmi les points que j'ai notés dans cette RPS (rappelez-vous qu'il s'agit juste d'un exemple et vous n'avez pas à l'utiliser telle quelle) :

  • L'engagement à ne pas conserver les données en mémoire stable utilise un terme technique (does not have data logged to disk) pour parler d'un type de support stable particulier, ce qui peut encourager des trucs comme d'enregistrer sur un support stable qui ne soit pas un disque.
  • Cette déclaration liste la totalité des champs de la requête DNS qui sont enregistrés. L'adresse IP n'y est pas (remplacée par des données plus générales comme le préfixe annoncé dans BGP au moment de l'analyse).
  • La RPS s'engage à ne pas partager ces données. Mais des extraits ou des agrégations des données peuvent l'être.
  • Des exceptions sont prévues, permettant aux administrateurs de regarder les données de plus près en cas de suspicion de comportement malveillant.
  • Le résolveur est un résolveur menteur, bloquant la résolution des noms utilisés pour des activités malveillantes (C&C d'un botnet, par exemple). Hypocritement, la RPS d'exemple affirme ensuite ne pas faire de censure.
  • La RPS promet que l'ECS ne sera pas envoyé aux serveurs faisant autorité.

On a dit plus haut que l'« anonymisation » d'adresses IP était un art très difficile. L'annexe B de notre RFC fait le point sur les techniques existantes, leurs avantages et inconvénients. C'est une lecture très recommandée pour ceux et celles qui veulent réellement « anonymiser », pas juste en parler. Un tableau résume ces techniques, leurs forces et leurs faiblesses. Le problème est de dégrader les données suffisamment pour qu'on ne puisse plus identifier l'utilisateur, tout en gardant assez d'informations pour réaliser des analyses. À bien des égards, c'est la quadrature du cercle.

Pour mettre de l'ordre dans les techniques d'« anonymisation », le RFC commence par lister les propriétés possibles des techniques (cf. RFC 6235). Il y a entre autres :

  • Maintien du format : la donnée « anonymisée » a la même syntaxe que la donnée originale. Ainsi, ne garder que les 64 premiers bits d'une adresse IP et mettre les autres à zéro préserve le format (2001:db8:1:cafe:fafa:42:1:b53 devient 2001:db8:1:cafe::, qui est toujours une adresse IP). Par contre, condenser ne garde pas le format (le condensat SHA-256 de cette adresse deviendrait 98a09452f58ffeba29e7ca06978b3d65e104308a7a7f48b0399d6e33c391f663).
  • Maintien du préfixe : l'adresse « anonymisée » garde un préfixe (qui n'est pas forcément le préfixe original, mais qui est cohérent pour toutes les adresses partageant ce préfixe). Cela peut être utile pour les adresses IP mais aussi pour les adresses MAC, où le préfixe identifie le fabricant.

Pour atteindre nos objectifs, on a beaucoup de solutions (pas forcément incompatibles) :

  • Généralisation : sans doute l'une des méthodes les plus efficaces, car elle réduit réellement la quantité d'information disponible. C'est réduire la précision d'une estampille temporelle (par exemple ne garder que l'heure et oublier minutes et secondes), approximer une position (en ne gardant qu'un carré de N kilomètres de côté), remplacer tous les ports TCP et UDP par un booléen indiquant simplement s'ils sont supérieurs ou inférieurs à 1 024, etc.
  • Permutation : remplacer chaque identificateur par un autre, par exemple via un condensat cryptographique.
  • Filtrage : supprimer une partie des données, par exemple, dans une requête DNS, ne garder que le nom demandé.
  • Et bien d'autres encore.

L'annexe B rentre ensuite dans les détails en examinant plusieurs techniques documentées (rappelez-vous que les responsables des données sont souvent très vagues, se contentant d'agiter les mains en disant « c'est anonymisé avec des techniques très avancées »). Par exemple, Google Analytics permet aux webmestres de demander à généraliser les adresses IP en mettant à zéro une partie des bits. Comme le note le RFC, cette méthode est très contestable, car elle garde bien trop de bits (24 en IPv4 et 48 en IPv6).

Plusieurs des techniques listées ont une mise en œuvre dans du logiciel publiquement accessible, mais je n'ai pas toujours réussi à tester. Pour dnswasher, il faut apparemment compiler tout PowerDNS. Une fois que c'est fait, dnswasher nous dit ce qu'il sait faire :

% ./dnswasher -h
Syntax: dnswasher INFILE1 [INFILE2..] OUTFILE
Allowed options:
  -h [ --help ]           produce help message
  --version               show version number
  -k [ --key ] arg        base64 encoded 128 bit key for ipcipher
  -p [ --passphrase ] arg passphrase for ipcipher (will be used to derive key)
  -d [ --decrypt ]        decrypt IP addresses with ipcipher
  

dnswasher remplace chaque adresse IP dans un fichier pcap par une autre adresse, attribuée dans l'ordre. C'est de la pseudonymisation (chaque adresse correspond à un pseudonyme et un seul), le trafic d'une adresse suffit en général à l'identifier, par ses centres d'intérêt. Traitons un pcap de requêtes DNS avec ce logiciel :

% ./dnswasher ~/tmp/dns.pcap ~/tmp/anon.pcap
Saw 18 correct packets, 1 runts, 0 oversize, 0 unknown encaps

% tcpdump -n -r ~/tmp/anon.pcap
15:28:49.674322 IP 1.0.0.0.41041 > 213.251.128.136.53: 51257 [1au] SOA? toto.fr. (36)
  

L'adresse 1.0.0.0 n'est pas la vraie, c'est le résultat du remplacement fait par dnswasher. En revanche, 213.251.128.136 est la vraie adresse. dnswasher ne remplace pas les adresses quand le port est 53, considérant que les serveurs n'ont pas de vie privée, contrairement aux clients.

Dans l'exemple ci-dessus, on n'avait pas utilisé de clé de chiffrement, et dnswasher remplace les adresses IP avec 1.0.0.0, 1.0.0.1, etc. En rajoutant l'option -p toto où toto est notre clé (simpliste) :

15:28:49.674322 IP 248.71.56.220.41041 > 213.251.128.136.53: 51257 [1au] SOA? toto.fr. (36)

L'adresse est remplacée par une adresse imprévisible, ici 248.71.56.220. Cela marche aussi en IPv6 :

15:28:49.672925 IP6 b4a:7e80:52fe:4003:6116:fcb8:4e5a:b334.52346 > 2001:41d0:209::1.53: 37568 [1au] SOA? toto.fr. (36)
  

(Une adresse comme b4a:7e80:52fe:4003:6116:fcb8:4e5a:b334.52346 ne figure pas encore dans les plages d'adresses attribuées.)

TCPdpriv, lui, préserve le préfixe des adresses IP. À noter qu'il n'est pas déterministe : les valeurs de remplacement seront différentes à chaque exécution.

On peut aussi chiffrer l'adresse IP, en utilisant une clé qu'on garde secrète. Cela a l'avantage qu'un attaquant ne peut pas simplement faire tourner l'algorithme sur toutes les adresses IP possibles, comme il le peut si on utilise une simple condensation. C'est ce que fait Crypto-PAn.

Comme chaque logiciel a sa propre façon de remplacer les adresses IP, cela peut rendre difficile l'échange de données et le travail en commun. D'où le projet ipcipher. Ce n'est pas du code, mais une spécification (avec des pointeurs vers des logiciels qui mettent en œuvre cette spécification). Au passage, je vous recommande cet article à ce sujet. La spécification ipcipher peut être mise en œuvre, par exemple, avec le programme ipcrypt, qui traite des fichiers texte au format CSV :

% cat test2.csv
foo,127.0.0.1
bar,9.9.9.9
baz,204.62.14.153
sha,9.9.9.9

% ./ipcrypt test2.csv 1 e
foo,118.234.188.6
bar,142.118.72.81
baz,235.237.54.9
sha,142.118.72.81
  

Notez le déterminisme : 9.9.9.9 est toujours remplacé par la même adresse IP.

Enfin, des structures de données rigolotes peuvent fournir d'autres services. C'est ainsi que les filtres de Bloom peuvent permettre de savoir si une requête a été faite, mais sans pouvoir trouver la liste des requêtes. Un exemple d'application aux données DNS est l'article « Privacy-Conscious Threat Intelligence Using DNSBLOOM ».


Téléchargez le RFC 8932


L'article seul

RFC 8927: JSON Type Definition

Date de publication du RFC : Novembre 2020
Auteur(s) du RFC : U. Carion (Segment)
Expérimental
Première rédaction de cet article le 7 novembre 2020


Il existe plusieurs langages pour décrire la structure d'un document JSON. Aucun ne fait l'objet d'un clair consensus. Sans compter les nombreux programmeurs qui ne veulent pas entendre parler d'un schéma formel. Ce nouveau RFC décrit un de ces langages, JTD, JSON Type Definition. Une de ses particularités est que le schéma est lui-même écrit en JSON. Son cahier des charges est de faciliter la génération automatique de code à partir du schéma. JTD est plus limité que certains langages de schéma, afin de faciliter l'écriture d'outils JTD.

On l'a dit, il n'existe pas d'accord dans le monde JSON en faveur d'un langage de schéma particulier. La culture de ce monde JSON est même souvent opposée au principe d'un schéma. Beaucoup de programmeurs qui utilisent JSON préfèrent l'agilité, au sens « on envoie ce qu'on veut et le client se débrouille pour le comprendre ». Les mêmes désaccords existent à l'IETF, et c'est pour cela que ce RFC n'est pas sur le chemin des normes, mais a juste l'état « Expérimental ».

JSON est normalisé dans le RFC 8259. D'innombrables fichiers de données sont disponibles au format JSON, et de très nombreuses API prennent du JSON en entrée et en rendent en sortie. La description des structures de ces requêtes et réponses est typiquement faite en langage informel. C'est par exemple le cas de beaucoup de RFC qui normalisent un format utilisant JSON comme le RFC 9083, les RFC 8620 et RFC 8621, le RFC 7033, etc. Une des raisons pour lesquelles il est difficile de remplacer ces descriptions en langue naturelle par un schéma formel (comme on le fait couramment pour XML, par exemple avec Relax NG) est qu'il n'y a pas d'accord sur le cahier des charges du langage de schéma. JTD (JSON Type Definition) a des exigences bien précises (section 1 du RFC). Avant de comparer JTD à ses concurrents (cf. par exemple l'annexe B), il faut bien comprendre ces exigences, qui influent évidemment sur le langage :

  • Description sans ambiguïté de la structure du document JSON (d'accord, cette exigence est assez banale, pour un langage de schéma…),
  • Possibilité de décrire toutes les constructions qu'on trouve dans un document JSON typique,
  • Une syntaxe qui doit être facile à lire et à écrire, aussi bien pour les humains que pour les programmes,
  • Et, comme indiqué plus haut, tout faire pour faciliter la génération de code à partir du schéma, la principale exigence de JTD ; l'idée est que, si un format utilise JTD, un programme qui analyse ce format soit en bonne partie générable uniquement à partir de la description JTD.

Ainsi, JTD a des entiers sur 8, 16 et 32 bits, qu'un générateur de code peut traduire directement en (le RFC utilise des exemples C++) int8_t, int16_t, etc, mais pas d'entiers de 64 bits, pourtant admis en JSON mais peu portables (cf. annexe A.1). JTD permet de décrire les propriétés d'un objet (« objet », en JSON, désigne un dictionnaire) qu'on peut traduire en struct ou std::map C++.

Les fans de théorie des langages et de langages formels noteront que JTD n'est pas lui-même spécifié en JTD. Le choix ayant été fait d'un format simple, JTD n'a pas le pouvoir de se décrire lui-même et c'est pour cela que la description de JTD est faite en CDDL (Concise Data Definition Language, RFC 8610).

La syntaxe exacte est spécifiée en section 2, une fois que vous avez (re)lu le RFC 8610. Ainsi, la description de base, en CDDL, d'un membre d'un objet JSON est :

properties = (with-properties // with-optional-properties)

with-properties = (
     properties: { * tstr => { schema }},
     ? optionalProperties: { * tstr => { schema }},
     ? additionalProperties: bool,
     shared,
   )
  

Ce qui veut dire en langage naturel que le schéma JTD peut avoir un membre properties, lui-même ayant des membres composés d'un nom et d'un schéma. Le schéma peut être, entre autres, un type, ce qui est le cas dans l'exemple ci-dessous. Voici un schéma JTD trivial, en JSON comme il se doit :

{
  "properties": {
    "name": {
      "type": "string"
    },
    "ok": {
      "type": "boolean",
      "nullable": true
    },
    "level": {
      "type": "int32"
    }
  }
}
  

Ce schéma accepte le document JSON :

{
  "name": "Foobar",
  "ok": false,
  "level": 1
}
  

Ou bien ce document :

{
  "name": "Durand",
  "ok": null,
  "level": 42
}
  

(L'élément nullable peut valoir null ; si on veut pouvoir omettre complètement un membre, il faut le déclarer dans optionalProperties, pas properties.) Par contre, cet autre document n'est pas valide :

{
  "name": "Zig",
  "ok": true,
  "level": 0,
  "extra": true
}
  

Car il y a un membre de trop, extra. Par défaut, JTD ne le permet pas mais un schéma peut comporter additionalProperties: true ce qui les autorisera.

Ce document JSON ne sera pas accepté non plus :

{
  "name": "Invalid",
  "ok": true
}
  

Car la propriété level ne peut pas être absente.

Un exemple plus détaillé, et pour un cas réel, figure dans l'annexe C du RFC, en utilisant le langage du RFC 7071.

JTD ne permet pas de vrai mécanisme d'extension mais on peut toujours ajouter un membre metadata dont la valeur est un objet JSON quelconque, et qui sert à définir des « extensions » non portables.

Jouons d'ailleurs un peu avec une mise en œuvre de JTD. Vous en trouverez plusieurs ici, pour divers langages de programmation. Essayons avec celui en Python. D'abord, installer le paquetage :

% git clone https://github.com/jsontypedef/json-typedef-python.git
% cd json-typedef-python
% python setup.py build
% python setup.py install --user
  

(Oui, on aurait pu utiliser pip install jtd à la place.) Le paquetage n'est pas livré avec un script exécutable, on en crée un en suivant la documentation. Il est simple :

#!/usr/bin/env python3

import sys
import json

import jtd

if len(sys.argv) != 3:
    raise Exception("Usage: %s schema json-file" % sys.argv[0])

textSchema = open(sys.argv[1], 'r').read()
textJsonData = open(sys.argv[2], 'r').read()

schema = jtd.Schema.from_dict(json.loads(textSchema))
jsonData = json.loads(textJsonData)

result = jtd.validate(schema=schema, instance=jsonData)
print(result)
  

Si le fichier JSON correspond au schéma, il affichera un tableau vide, sinon un tableau contenant la liste des erreurs :

% ./jtd.py myschema.json mydoc1.json 
[]
  

(myschema.json contient le schéma d'exemple plus haut et mydoc1.json est le premier exemple JSON.) Si, par contre, le fichier JSON est invalide :

 % ./jtd.py myschema.json mydoc3.json
[ValidationError(instance_path=['extra'], schema_path=[])]
  

(mydoc3.json était l'exemple avec le membre supplémentaire, extra.)

Une particularité de JTD est de normaliser le mécanisme de signalement d'erreurs. Les erreurs doivent être formatées en JSON (évidemment…) avec un membre instancePath qui est un pointeur JSON (RFC 6901) indiquant la partie invalide du document, et un membre schemaPath, également un pointeur, qui indique la partie du schéma qui invalidait cette partie du document (cf. le message d'erreur ci-dessus, peu convivial mais normalisé).

JTD est spécifié en CDDL donc on peut tester ses schémas avec les outils CDDL, ici un outil en Ruby :

% gem install cddl --user
  

Ensuite, on peut valider ses schémas :

% cddl jtd.cddl validate myschema.json 
%
  

Si le schéma a une erreur (ici, j'ai utilisé le type char, qui n'existe pas) :

% cddl jtd.cddl validate wrongschema.json
CDDL validation failure (nil for {"properties"=>{"name"=>{"type"=>"char"}, "ok"=>{"type"=>"boolean", "nullable"=>true}, "level"=>{"type"=>"int32"}}}):
["char", [:text, "timestamp"], nil]
["char", [:text, "timestamp"], null]
  

(Oui, les messages d'erreur de l'outil cddl sont horribles.)

Et avec l'exemple de l'annexe C, le reputon du RFC 7071 :

% cddl jtd.cddl validate reputon.json
% 
  

C'est parfait, le schéma du RFC est correct, validons le fichier JSON tiré de la section 6.4 du RFC 7071 :

% ./jtd.py reputon.json r1.json 
[]
  

Si jamais il y a une erreur (ici, on a enlevé le membre rating) :

% ./jtd.py reputon.json r1.json
[ValidationError(instance_path=['reputons', '0'], schema_path=['properties', 'reputons', 'elements', 'properties', 'rating'])]
  

Une intéressante annexe B fait une comparaison de JTD avec CDDL. Par exemple, le schéma CDDL :

root = "PENDING" / "DONE" / "CANCELED"
  

accepterait les mêmes documents que le schéma JTD :

{ "enum": ["PENDING", "DONE", "CANCELED"]} 
  

Et celui-ci, en CDDL (où le point d'interrogation indique un terme facultatif) :

root = { a: bool, b: number, ? c: tstr, ? d: tdate }
  

reviendrait à ce schéma JTD :

{
  "properties": {
    "a": {
      "type": "boolean"
    },
    "b": {
      "type": "float32"
    }
  },
  "optionalProperties": {
    "c": {
      "type": "string"
    },
    "d": {
      "type": "timestamp"
    }
  }
}
  

Merci à Ulysse Carion pour sa relecture.


Téléchargez le RFC 8927


L'article seul

RFC 8925: IPv6-Only-Preferred Option for DHCPv4

Date de publication du RFC : Octobre 2020
Auteur(s) du RFC : L. Colitti, J. Linkova (Google), M. Richardson (Sandelman), T. Mrugalski (ISC)
Chemin des normes
Réalisé dans le cadre du groupe de travail IETF dhc
Première rédaction de cet article le 17 octobre 2020


Si une machine a IPv6 et est ravie de n'utiliser que ce protocole, pas la peine pour un serveur DHCP de lui envoyer une des rares adresses IPv4 restantes. Ce nouveau RFC décrit une option de DHCP-IPv4 qui permet au client de dire au serveur « je n'ai pas vraiment besoin d'IPv4 donc, s'il y a IPv6 sur ce réseau, tu peux garder les adresses IPv4 pour les nécessiteux ».

Idéalement, l'administrateur réseaux qui configure un nouveau réseau voudrait le faire en IPv6 seulement, ce qui simplifierait son travail. Mais, en pratique, c'est difficile (le RFC 6586 décrit une telle configuration). En effet, beaucoup de machines ne sont pas encore entrées dans le XXIe siècle, et ne gèrent qu'IPv4 ou, plus exactement, ont besoin d'IPv4 pour au moins certaines fonctions (comme des versions de Windows qui avaient IPv6 mais ne pouvaient parler à leur résolveur DNS qu'au-dessus d'IPv4). Il faut donc se résigner à gérer IPv4 pour ces machines anciennes. Mais, vu la pénurie d'adresses IPv4, il est également souhaitable de ne pas allouer d'adresses IPv4 aux machines qui n'en ont pas besoin.

La solution la plus courante à l'heure actuelle est de mettre les machines antédiluviennes et les machines modernes dans des réseaux séparés (VLAN ou SSID différents, par exemple, comme c'est fait aux réunions IETF ou RIPE), le réseau pour les ancêtres étant le seul à avoir un serveur DHCPv4 (RFC 2131). Cela a plusieurs inconvénients :

  • Cela complique le réseau et son administration,
  • Le risque d'erreur est élevé (utilisateur sélectionnant le mauvais SSID dans le menu des réseaux Wi-Fi accessibles, par exemple).

L'idéal serait de n'avoir qu'un réseau, accueillant aussi bien des machines IPv6 que des machines IPv4, sans que les machines IPv6 n'obtiennent d'adresse IPv4, dont elles n'ont pas besoin. On ne peut pas demander aux utilisateurs de débrayer complètement IPv4, car les machines mobiles peuvent en avoir besoin sur des réseaux purement IPv4 (ce qui existe encore). La machine IPv6 ne sachant pas, a priori, si le réseau où elle se connecte est seulement IPv4, seulement IPv6 ou accepte les deux, elle va donc, logiquement, demander une adresse IPv4 en DHCP, et bloquer ainsi une précieuse adresse IPv4. Retarder cette demande pour voir si IPv6 ne suffirait pas se traduirait par un délai peu acceptable lorsque le réseau n'a pas IPv6.

Notre RFC résout ce problème en créant une nouvelle option pour les requêtes et réponses DHCP v4 : dans la requête, cette option indique que le client n'a pas absolument besoin d'une adresse IPv4, qu'il peut se passer de ce vieux protocole, si le réseau a de l'IPv6 bien configuré et, dans la réponse, cette option indique que le réseau fonctionne bien en « IPv6 seulement ». Et comment fait-on si on veut joindre un service IPv4 depuis un tel réseau ? Il existe des techniques pour cela, la plus répandue étant le NAT64 du RFC 6146, et la réponse du serveur DHCP indique également qu'une de ces techniques est présente.

La section 1.2 précise quelques termes importants pour comprendre les choix à effectuer pour le client et pour le serveur DHCP :

  • Capable de faire de l'IPv6 seule (IPv6-only capable host) : une machine terminale qui peut se débrouiller sans problème sans adresse IPv4,
  • Nécessite IPv4 (IPv4-requiring host) : le contraire,
  • IPv4 à la demande (IPv4-on-demand) : le scénario, rendu possible par ce RFC, où le même réseau accueille des machines capables de faire de l'IPv6 seul, et des machines qui nécessitent IPv4 ; ce réseau se nomme « Essentiellement IPv6 » (IPv6-mostly network), et fournit NAT64 (RFC 6146) ou une technique équivalente,
  • En mode seulement IPv6 (IPv6-only mode) : état d'une machine capable de faire de l'IPv6 seule et qui n'a pas reçu d'adresse IPv4,
  • Réseau seulement en IPv6 (IPv6-only network) : un réseau qui ne fournit pas du tout d'IPv4 et ne peut donc pas accueillir les machines qui nécessitent IPv4 (c'est un tel réseau qui sert de base à l'expérience du RFC 6586),
  • Notez que le cas d'un réseau qui non seulement serait seulement en IPv6 mais en outre ne fournirait pas de technique comme celle de NAT64 (RFC 6146) n'apparait pas dans le RFC. Un tel réseau ne permettrait pas aux machines terminales de joindre les services qui sont restées en IPv4 seulement comme MicrosoftHub ou l'Élysée.

La section 2 du RFC résume ensuite les bonnes raisons qu'il y a pour signaler au réseau, via l'option DHCP, qu'on se débrouille très bien avec IPv6 seul :

  • Il semble logique que la nouvelle option, qui veut dire « je n'ai pas réellement besoin d'IPv4 » soit envoyée via le protocole qui sert à demander les adresses IPv4,
  • Tout le monde a déjà DHCP v4 et, surtout, utiliser le protocole existant n'introduit pas de nouvelles vulnérabilités (permettre à un nouveau protocole de couper IPv4 sur les machines terminales serait un risque de sécurité important),
  • Comme il faut ajouter explicitement l'option aux requêtes et aux réponses, IPv4 ne sera coupé que si le client et le serveur DHCP sont tous les deux d'accord pour cela,
  • Ce système n'ajoute aucun retard à la configuration via DHCP, le client qui envoie l'option alors que le réseau ne permet pas IPv6 seul, aura son adresse IPv4 aussi vite qu'avant (pas d'aller-retours de négociation),
  • DHCP permet des résultats qui dépendent du client, donc, sur un même réseau, le serveur DHCP pourra économiser les adresses IPv4 en en n'envoyant pas aux machines qui peuvent s'en passer, tout en continuant à en distribuer aux autres.

La section 3 du RFC présente l'option elle-même. Elle contient le code 108 (IPv6-only Preferred), enregistré à l'IANA (cf. section 5), la taille de l'option (toujours quatre, mais précisée car c'est l'encodage habituel des options DHCP, cf. RFC 2132) et la valeur, qui est, dans la réponse, le nombre de secondes que le client peut mémoriser cette information, avant de redemander au serveur DHCP.

Le logiciel client DHCP doit donc offrir un moyen à son administrateur pour activer « je me débrouille très bien en IPv6 », et cela doit être par interface réseau. Dans ce cas, le client DHCP doit envoyer l'option décrite dans notre RFC. La décision d'activer le mode « IPv6 seul » peut aussi être prise automatiquement par le système d'exploitation, par exemple parce qu'il détecte que 464XLAT (RFC 6877) fonctionne. De même, des objets connectés qui ne parlent qu'à des services accessibles en IPv6 pourraient être livrés avec l'option « IPv6 seul » déjà activée.

Outre le côté « je suis un bon citoyen, je ne gaspille pas des ressources rares » qu'il y a à ne pas demander d'adresse IPv4, cette option permet de couper un protocole réseau inutile, diminuant la surface d'attaque de la machine.

Côté serveur DHCP, il faut un moyen de configurer, pour chaque réseau, si on accepte l'option « IPv6 me suffit ». Si elle est activée, le serveur, lorsqu'il recevra cette option dans une requête (et uniquement dans ce cas), la mettra dans sa réponse, et n'attribuera pas d'adresse IPv4. On voit donc que les vieux clients DHCP, qui ne connaissent pas cette option et ne l'inclueront donc pas dans leurs requêtes, ne verront pas de changement, ils continueront à avoir des adresses IPv4 comme avant (s'il en reste…).

A priori (section 4 du RFC), l'administrateurice du serveur DHCP ne va pas activer cette option si son réseau ne fournit pas un mécanisme permettant aux machines purement IPv6 de joindre des services purement IPv4 (par exemple le mécanisme NAT64 du RFC 6146). En effet, on ne peut pas s'attendre, à court terme, à ce que tous les services Internet soient accessibles en IPv6. Activer l'option « IPv6 seul » sans avoir un mécanisme de traduction comme NAT64 n'est réaliste que sur des réseaux non-connectés à l'Internet public.

Un petit mot sur la sécurité, juste pour rappeler que DHCP n'est pas vraiment sécurisé et que l'option « v6 seul » a pu être mise, retirée ou modifiée suite aux actions d'un attaquant. Cela n'a rien de spécifique à cette option, c'est un problème général de DHCP, contre lequel il faut déployer des protections comme le DHCP snooping.

À l'heure actuelle, je ne connais pas encore de mise en œuvre de cette option (qui ne devrait pas être trop dure à ajouter, elle n'a rien de très nouveau ou d'extraordinaire).

Un dernier mot : le RFC 2563 prévoyait une option pour couper l'auto-configuration des adresses IPv4. Elle peut être utilisée en même temps que l'option de notre RFC, qu'elle complète assez bien.


Téléchargez le RFC 8925


L'article seul

RFC 8923: A Minimal Set of Transport Services for End Systems

Date de publication du RFC : Octobre 2020
Auteur(s) du RFC : M. Welzl, S. Gjessing (University of Oslo)
Pour information
Réalisé dans le cadre du groupe de travail IETF taps
Première rédaction de cet article le 22 octobre 2020


Ce nouveau RFC s'incrit dans le travail en cours à l'IETF pour formaliser un peu plus les services que la couche transport offre aux applications. Le RFC 8095 décrivait tous les services possibles. Ce nouveau RFC 8923 liste les services minimums, ceux à offrir dans presque tous les cas.

Comme le note le RFC 8095, les applications tournant sur la famille de protocoles TCP/IP ne peuvent en général pas aujourd'hui exprimer les services attendus de la couche transport. Elles doivent choisir un protocole de transport (comme TCP ou UDP), même si ce protocole ne convient pas tout à fait. Toute l'idée derrière le travail du groupe TAPS de l'IETF est de permettre au contraire aux applications d'indiquer des services (« je veux un envoi de données fiable et dans l'ordre, avec confidentialité et intégrité ») et que le système d'exploitation choisisse alors la solution la plus adaptée (ici, cela pourrait être TCP ou SCTP avec TLS par dessus, ou bien le futur QUIC). De la même façon qu'un langage de programmation de haut niveau utilise des concepts plus abstraits qu'un langage de bas niveau, on pourrait ainsi avoir une interface de programmation réseau de plus haut niveau que les traditionnelles prises normalisées par Posix. Ce RFC ne normalise pas une telle API, il décrit seulement les services attendus. La liste des services possibles est dans le RFC 8095, et les protocoles qui les fournissent sont documentés dans le RFC 8303 et RFC 8304. Le cœur de notre nouveau RFC 8923 est la section 6, qui liste les services minimum attendus (je vous en dévoile l'essentiel tout de suite : établissement de connexion, fin de connexion, envoi de données, réception de données).

Ce RFC ne spécifie pas une API précise, cela sera éventuellement fait dans un autre document, draft-ietf-taps-interface.

À noter que les services minimums exposés ici peuvent être mis en œuvre sur TCP, et, dans certains cas, sur UDP. TCP offre un ensemble de services qui est quasiment un sur-ensemble de celui d'UDP, donc, sur le seul critère des services, on pourrait n'utiliser que TCP. (Exercice : quelle(s) exception(s) trouvez-vous ?) Comme il s'agit de services de la couche transport aux applications, l'implémentation peut être faite d'un seul côté (il n'est pas nécessaire que les deux partenaires qui communiquent le fassent).

Et, si vous lisez le RFC, faites attention à la section 2 sur la terminologie, section qui reprend le RFC 8303 : une fonction (Transport Feature) est une fonction particulière que le protocole de transport va effectuer, par exemple la confidentialité, la fiabilité de la distribution des données, le découpage des données en messages, etc, un service (Transport Service) est un ensemble cohérent de fonctions, demandé par l'application, et un protocole (Transport Protocol) est une réalisation concrète d'un ou plusieurs services.

Comment définir un jeu minimal de services (section 3 du RFC) ? D'abord, il faut définir les services « sémantiques » (functional features) que l'application doit connaitre. Par exemple, l'application doit savoir si le service « distribution des données dans l'ordre » est disponible ou pas. Et il y a les services qui sont plutôt des optimisations comme l'activation ou la désactivation de DSCP ou comme l'algorithme de Nagle. Si l'application ignore ces services, ce n'est pas trop grave, elle fonctionnera quand même, même si c'est avec des performances sous-optimales.

La méthode utilisée par les auteurs du RFC pour construire la liste des services minimums est donc :

  • Catégorisation des services du RFC 8303 (sémantique, optimisation…) ; le résultat de cette catégorisation figure dans l'annexe A de notre RFC,
  • Réduction aux services qui peuvent être mis en œuvre sur les protocoles de transport qui peuvent passer presque partout, TCP et UDP,
  • Puis établissement de la liste des services minimum.

Ainsi (section 4 du RFC), le service « établir une connexion » peut être mis en œuvre sur TCP de manière triviale, mais aussi sur UDP, en refaisant un équivalent de la triple poignée de mains. Le service « envoyer un message » peut être mis en œuvre sur UDP et TCP (TCP, en prime, assurera sa distribution à l'autre bout, mais ce n'est pas nécessaire). En revanche, le service « envoyer un message avec garantie qu'il soit distribué ou qu'on soit notifié », s'il est simple à faire en TCP, ne l'est pas en UDP (il faudrait refaire tout TCP au dessus d'UDP).

La section 5 du RFC discute de divers problèmes quand on essaie de définir un ensemble minimal de services. Par exemple, TCP ne met pas de délimiteur dans le flot d'octets qui circulent. Contrairement à UDP, on ne peut pas « recevoir un message », seulement recevoir des données. Certains protocoles applicatifs (comme DNS ou EPP) ajoutent une longueur devant chaque message qu'ils envoient, pour avoir une sémantique de message et plus de flot d'octets. TCP n'est donc pas un strict sur-ensemble d'UDP.

Pour contourner cette limitation, notre RFC définit (section 5.1) la notion de « flot d'octets découpé en messages par l'application » (Application-Framed Bytestream). Dans un tel flot, le protocole applicatif indique les frontières de message, par exemple en précédant chaque message d'une longueur, comme le fait le DNS.

Autre problème amusant, certains services peuvent être d'assez bas niveau par rapport aux demandes des applications. Ainsi, le RFC 8303 identifie des services comme « couper Nagle », « configurer DSCP » ou encore « utiliser LEDBAT ». Il serait sans doute plus simple, pour un protocole applicatif, d'avoir une configuration de plus haut niveau. Par exemple, « latence minimale » désactiverait Nagle et mettrait les bits qui vont bien en DSCP.

Nous arrivons finalement au résultat principal de ce RFC, la section 6, qui contient l'ensemble minimal de services. Chaque service est configurable via un ensemble de paramètres. Il est implémentable uniquement avec TCP, et d'un seul côté de la connexion. Dans certains cas, TCP fournira plus que ce qui est demandé, mais ce n'est pas un problème. Je ne vais pas lister tout cet ensemble minimal ici, juste énumérer quelques-uns de ses membres :

  • Créer une connexion, avec des paramètres comme la fiabilité demandée,
  • Mettre fin à la connexion,
  • Envoyer des données, sans indication de fin de message (cf. la discussion plus haut),
  • Recevoir des données.

Pour l'envoi de données, les paramètres sont, entre autres :

  • Fiabilité (les données sont reçues à l'autre bout ou, en tout cas, l'émetteur est prévenu si cela n'a pas été possible) ou non,
  • Contrôle de congestion ou non, sachant que si l'application ne demande pas de contrôle de congestion au protocole de transport, elle doit faire ce contrôle elle-même (RFC 8085),
  • Idempotence,
  • Etc.

Pour la réception de données, le service est juste la réception d'octets, un protocole applicatif qui veut des messages doit utiliser les « flots d'octets découpés par l'application » décrits en section 5.1.

On notera que la sécurité est un service, également, ou plutôt un ensemble de services (section 8 du RFC). Dans les protocoles IETF, la sécurité est souvent fournie par une couche intermédiaire entre la couche de transport et la couche application à proprement parler. C'est ainsi que fonctionne TLS, par exemple. Mais la tendance va peut-être aller vers l'intégration, avec QUIC.


Téléchargez le RFC 8923


L'article seul

RFC 8922: A Survey of the Interaction Between Security Protocols and Transport Services

Date de publication du RFC : Octobre 2020
Auteur(s) du RFC : T. Enghardt (TU Berlin), T. Pauly (Apple), C. Perkins (University of Glasgow), K. Rose (Akamai Technologies), C.A. Wood (Cloudflare)
Pour information
Réalisé dans le cadre du groupe de travail IETF taps
Première rédaction de cet article le 22 octobre 2020


Quels sont les services de sécurité fournis par les protocoles de transport existants ? Ce nouveau RFC fait le point et examine ces services de sécurité pour un certain nombre de ces protocoles, comme TLS, QUIC, WireGuard, etc. Cela intéressera tous les gens qui travaillent sur la sécurité de l'Internet.

Ce RFC est issu du groupe de travail IETF TAPS, dont le projet est d'abstraire les services que rend la couche Transport pour permettre davantage d'indépendance entre les applications et les différents protocoles de transport. Vous pouvez en apprendre plus dans les RFC 8095 et RFC 8303.

Parmi les services que fournit la couche Transport aux applications, il y a la sécurité. Ainsi, TCP (si le RFC 5961 a bien été mis en œuvre et si l'attaquant n'est pas sur le chemin) protège assez bien contre les usurpations d'adresses IP. Notez que le RFC utilise le terme de « protocole de transport » pour tout ce qui est en dessous de l'application, et utilisé par elle. Ainsi, TLS est vu comme un protocole de transport, du point de vue de l'application, il fonctionne de la même façon (ouvrir une connexion, envoyer des données, lire des réponses, fermer la connexion), avec en prime les services permis par la cryptographie, comme la confidentialité. TLS est donc vu, à juste titre, comme un protocole d'infrastructure, et qui fait donc partie de ceux étudiés dans le cadre du projet TAPS.

La section 1 du RFC explique d'ailleurs quels protocoles ont été intégrés dans l'étude et pourquoi. L'idée était d'étudier des cas assez différents les uns des autres. Des protocoles très répandus comme SSH (RFC 4251), GRE (avec ses extensions de sécurité du RFC 2890) ou L2TP (RFC 5641) ont ainsi été écartés, pas parce qu'ils sont mauvais mais parce qu'ils fournissent à peu près les mêmes services que des protocoles étudiés (par exemple SSH vs. TLS) et ne nécessitaient donc pas d'étude séparée. Plus animée, au sein du groupe de travail TAPS, avait été la discussion sur des protocoles non-IETF comme WireGuard ou MinimaLT. Ils ne sont pas normalisés (et, souvent, même pas décrits dans une spécification stable), mais ils sont utilisés dans l'Internet, et présentent des particularités suffisamment importantes pour qu'ils soient étudiés ici. En revanche, les protocoles qui ne fournissent que de l'authentification, comme AO (RFC 5925) n'ont pas été pris en compte.

Comme c'est le cas en général dans le projet TAPS, le but est de découvrir et de documenter ce que les protocoles de transport ont en commun, pour faciliter leur choix et leur utilisation par l'application. Par contre, contrairement à d'autres cas d'usage de TAPS, il n'est pas prévu de permettre le choix automatique d'un protocole de sécurité. Pour les autres services, un tel choix automatique se justifie (RFC 8095). Si une application demande juste le service « transport d'octets fiable, j'ai des fichiers à envoyer », lui fournir TCP ou SCTP revient au même, et l'application se moque probablement du protocole choisi. Mais la sécurité est plus compliquée, avec davantage de pièges et de différences subtiles, et il est donc sans doute préférable que l'application choisisse explicitement le protocole.

La section 2 du RFC permet de réviser la terminologie, je vous renvoie à mon article sur le RFC 8303 pour la différence entre fonction (Transport Feature) et service (Transport Service).

Et la section 3 s'attaque aux protocoles eux-mêmes. Elle les classe selon qu'ils protègent n'importe quelle application, une application spécifique, l'application et le transport, ou bien carrément tout le paquet IP. Le RFC note qu'aucune classification n'est parfaite. Notamment, plusieurs protocoles sont séparés en deux parties, un protocole de connexion, permettant par exemple l'échange des clés de session, protocole utilisé essentiellement, voire uniquement, à l'ouverture et à la fermeture de la session, et le protocole de chiffrement qui traite les données transmises. Ces deux protocoles sont plus ou moins intégrés, de la fusion complète (tcpcrypt, RFC 8548) à la séparation complète (IPsec, où ESP, décrit dans le RFC 4303, ne fait que chiffrer, les clés pouvant être fournies par IKE, spécifié dans le RFC 7296, ou par une autre méthode). Par exemple, TLS décrit ces deux protocoles séparement (RFC 8446) mais on les considérait comme liés… jusqu'à ce que QUIC décide de n'utiliser qu'un seul des deux.

Bon, assez d'avertissements, passons à la première catégorie, les protocoles qui fournissent une protection générique aux applications. Ils ne protègent donc pas contre des attaques affectant la couche 4 comme les faux paquets RST. Le plus connu de ces protocoles est évidemment TLS (RFC 8446). Mais il y a aussi DTLS (RFC 6347).

Ensuite, il y a les protocoles spécifiques à une application. C'est par exemple le cas de SRTP (RFC 3711), extension sécurisée de RTP qui s'appuie sur DTLS.

Puis il y a la catégorie des protocoles qui protègent le transport ce qui, par exemple, protège contre les faux RST (ReSeT), et empêche un observateur de voir certains aspects de la connexion. C'est le cas notamment de QUIC, normalisé depuis. QUIC utilise TLS pour obtenir les clés, qui sont ensuite utilisées par un protocole sans lien avec TLS, et qui chiffre y compris la couche Transport. Notons que le prédécesseur de QUIC, développé par Google sous le même nom (le RFC nomme cet ancien protocole gQUIC pour le distinguer du QUIC normalisé), n'utilisait pas TLS.

Toujours dans la catégorie des protocoles qui protègent les couches Transport et Application, tcpcrypt (RFC 8548). Il fait du chiffrement « opportuniste » (au sens de « sans authentification ») et est donc vulnérable aux attaques actives. Mais il a l'avantage de chiffrer sans participation de l'application, contrairement à TLS. Bon, en pratique, il n'a connu quasiment aucun déploiement.

Il y a aussi MinimaLT, qui intègre l'équivalent de TCP et l'équivalent de TLS (comme le fait QUIC).

Le RFC mentionne également CurveCP, qui fusionne également transport et chiffrement. Comme QUIC ou MinimaLT, il ne permet pas de connexions non-sécurisées.

Et enfin il y a les protocoles qui protègent tout, même IP. Le plus connu est évidemment IPsec (RFC 4303 et aussi RFC 7296, pour l'échange de clés, qui est un protocole complètement séparé). Mais il y a aussi WireGuard qui est nettement moins riche (pas d'agilité cryptographique, par exemple, ni même de négociation des paramètres cryptographiques ; ce n'est pas un problème pour des tunnels statiques mais c'est ennuyeux pour un usage plus général sur l'Internet). Et il y a OpenVPN, qui est sans doute le plus répandu chez les utilisateurs ordinaires, pour sa simplicité de mise en œuvre. On trouve par exemple OpenVPN dans tous des systèmes comme OpenWrt et donc dans tous les routeurs qui l'utilisent.

Une fois cette liste de protocoles « intéressants » établie, notre RFC va se mettre à les classer, selon divers critères. Le premier (section 4 du RFC) : est-ce que le protocole dépend d'un transport sous-jacent, qui fournit des propriétés de fiabilité et d'ordre des données (si oui, ce transport sera en général TCP) ? C'est le cas de TLS, bien sûr, mais aussi, d'une certaine façon, de tcpcrypt (qui dépend de TCP et surtout de son option ENO, normalisée dans le RFC 8547). Tous les autres protocoles peuvent fonctionner directement sur IP mais, en général, ils s'appuient plutôt sur UDP, pour réussir à passer les différentes middleboxes qui bloquent les protocoles de transport « alternatifs ».

Et l'interface avec les applications ? Comment ces différents protocoles se présentent-ils aux applications qui les utilisent ? Il y a ici beaucoup à dire (le RFC fournit un tableau synthétique de quelles possibilités et quels choix chaque protocole fournit aux applications). L'analyse est découpée en plusieurs parties (les services liés à l'établissement de la connexion, ceux accessibles pendant la session et ceux liés à la terminaison de la connexion), chaque partie listant plusieurs services. Pour chaque service, le RFC dresse la liste des différents protocoles qui le fournissent. Ainsi, la partie sur l'établissement de connexion indique entre autres le service « Extensions au protocole de base accessibles à l'application ». Ce service est fourni par TLS (via ALPN, RFC 7301) ou QUIC mais pas par IPsec ou CurveCP. De même le service « Délégation de l'authentification » peut être fourni par IPsec (par exemple via EAP, RFC 3748) ou tcpcrypt mais pas par les autres.

La section 7 rappelle que ce RFC ne fait qu'analyser les protocoles existants, il ne propose pas de changement. D'autre part, cette section note que certaines attaques ont été laissées de côté (comme celle par canal secondaire ou comme l'analyse de trafic).

La section 8 sur la vie privée rappelle ces faiblesses ; même si l'un des buts principaux du chiffrement est d'assurer la confidentialité, la plupart des protocoles présentés laissent encore fuiter une certaine quantité d'information, par exemple les certificats en clair de TLS (avant la version 1.3).


Téléchargez le RFC 8922


L'article seul

RFC 8915: Network Time Security for the Network Time Protocol

Date de publication du RFC : Septembre 2020
Auteur(s) du RFC : D. Franke (Akamai), D. Sibold, K. Teichel (PTB), M. Dansarie, R. Sundblad (Netnod)
Chemin des normes
Réalisé dans le cadre du groupe de travail IETF ntp
Première rédaction de cet article le 1 octobre 2020


Ce nouveau RFC spécifie un mécanisme de sécurité pour le protocole de synchronisation d'horloges NTP. Une heure incorrecte peut empêcher, par exemple, de valider des certificats cryptographiques. Ou fausser les informations enregistrées dans les journaux. Ce mécanisme de sécurité NTS (Network Time Security) permettra de sécuriser le mode client-serveur de NTP, en utilisant TLS pour négocier les clés cryptographiques qui serviront à chiffrer le trafic NTP ultérieur..

La sécurité n'était pas jugée trop importante au début de NTP : après tout, l'heure qu'il est n'est pas un secret. Et il n'y avait pas d'application cruciale dépendant de l'heure. Aujourd'hui, les choses sont différentes. NTP est un protocole critique pour la sécurité de l'Internet. NTP a quand même un mécanisme de sécurité, fondé sur des clés secrètes partagées entre les deux pairs qui communiquent. Comme tout mécanisme à clé partagée, il ne passe pas à l'échelle, d'où le développement du mécanisme « Autokey » dans le RFC 5906. Ce mécanisme n'a pas été un grand succès et un RFC a été écrit pour préciser le cahier des charges d'une meilleure solution, le RFC 7384. La sécurité de l'Internet repose de plus en plus que l'exactitude de l'horloge de la machine. Plusieurs systèmes de sécurité, comme DNSSEC ou X.509 doivent vérifier des dates et doivent donc pouvoir se fier à l'horloge de la machine. Même criticité de l'horloge quand on veut coordonner une smart grid, ou tout autre processus industriel, analyser des journaux après une attaque, assurer la traçabilité d'opérations financières soumises à régulation, etc. Or, comme l'explique bien le RFC 7384, les protocoles de synchronisation d'horloge existants, comme NTP sont peu ou pas sécurisés. (On consultera également avec profit le RFC 8633 qui parle entre autre de sécurité.)

Sécuriser la synchronisation d'horloge est donc crucial aujourd'hui. Cela peut se faire par un protocole extérieur au service de synchronisation, comme IPsec, ou bien par des mesures dans le service de synchronisation. Les deux services les plus populaires sont NTP (RFC 5905) et PTP. Aucun des deux n'offre actuellement toutes les fonctions de sécurité souhaitées par le RFC 7384. Mais les protocoles externes ont un inconvénient : ils peuvent affecter le service de synchronisation. Par exemple, si l'envoi d'un paquet NTP est retardé car IKE cherche des clés, les mesures temporelles vont être faussées. Le paquet arrivera intact mais n'aura pas la même utilité.

Les menaces contre les protocoles de synchronisation d'horloge sont décrites dans le RFC 7384. Les objectifs de notre RFC sont d'y répondre, notamment :

  • Permettre à un client d'authentifier son maître, via X.509 tel que l'utilise TLS,
  • Protéger ensuite l'intégrité des paquets (via un MAC).
  • En prime, mais optionnel, assurer la confidentialité du contenu des paquets.
  • Empêcher la traçabilité entre les requêtes d'un même client (si un client NTP change d'adresse IP, un observateur passif ne doit pas pouvoir s'apercevoir qu'il s'agit du même client).
  • Et le tout sans permettre les attaques par réflexion qui ont souvent été un problème avec NTP.
  • Passage à l'échelle car un serveur peut avoir de très nombreux clients,
  • Et enfin performances (RFC 7384, section 5.7).

Rappelons que NTP dispose de plusieurs modes de fonctionnement (RFC 5905, section 3). Le plus connu et sans doute le plus utilisé est le mode client-serveur (si vous faites sur Unix un ntpdate ntp.nic.fr, c'est ce mode que vous utilisez). Mais il y aussi un mode symétrique, un mode à diffusion et le « mode » de contrôle (dont beaucoup de fonctions ne sont pas normalisées). Le mode symétrique est sans doute celui qui a le plus d'exigences de sécurité (les modes symétriques et de contrôle de NTP nécessitent notamment une protection réciproque contre le rejeu, qui nécessite de maintenir un état de chaque côté) mais le mode client-serveur est plus répandu, et pose des difficultés particulières (un serveur peut avoir beaucoup de clients, et ne pas souhaiter maintenir un état par client). Notre RFC ne couvre que le mode client-serveur. Pour ce mode, la solution décrite dans notre RFC 8915 est d'utiliser TLS afin que le client authentifie le serveur, puis d'utiliser cette session TLS pour générer la clé qui servira à sécuriser du NTP classique, sur UDP. Les serveurs NTP devront donc désormais également écouter en TCP, ce qu'ils ne font pas en général pas actuellement. La partie TLS de NTS (Network Time Security, normalisé dans ce RFC) se nomme NTS-KE (pour Network Time Security - Key Exchange). La solution à deux protocoles peut sembler compliquée mais elle permet d'avoir les avantages de TLS (protocole éprouvé) et d'UDP (protocole rapide).

Une fois qu'on a la clé (et d'autres informations utiles, dont les cookies), on ferme la session TLS, et on va utiliser la cryptographie pour sécuriser les paquets. Les clés sont dérivées à partir de la session TLS, suivant l'algorithme du RFC 5705. Quatre nouveaux champs NTP sont utilisés pour les paquets suivants (ils sont présentés en section 5), non TLS. Dans ces paquets se trouve un cookie qui va permettre au serveur de vérifier le client, et de récupérer la bonne clé. Les cookies envoyés du client vers le serveur et en sens inverse sont changés à chaque fois, pour éviter toute traçabilité, car ils ne sont pas dans la partie chiffrée du paquet. Notez que le format des cookies n'est pas spécifié par ce RFC (bien qu'un format soit suggéré en section 6). Pour le client, le cookie est opaque : on le traite comme une chaîne de bits, sans structure interne.

On a dit que NTS (Network Time Security) utilisait TLS. En fait, il se restreint à un profil spécifique de TLS, décrit en section 3. Comme NTS est un protocole récent, il n'y a pas besoin d'interagir avec les vieilles versions de TLS et notre RFC impose donc TLS 1.3 (RFC 8446) au minimum, ALPN (RFC 7301), les bonnes pratiques TLS du RFC 7525, et les règles des RFC 5280 et RFC 6125 pour l'authentification du serveur.

Passons ensuite au détail du protocole NTS-KE (Network Time Security - Key Establishment) qui va permettre, en utilisant TLS, d'obtenir les clés qui serviront au chiffrement symétrique ultérieur. Il est spécifié dans la section 4 du RFC. Le serveur doit écouter sur le port ntske (4460 par défaut). Le client utilise ALPN (RFC 7301) pour annoncer qu'il veut l'application ntske/1. On établit la session TLS, on échange des messages et on raccroche (l'idée est de ne pas obliger le serveur à garder une session TLS ouverte pour chacun de ses clients, qui peuvent être nombreux).

Le format de ces messages est composé d'un champ indiquant le type du message, de la longueur du message, des données du message, et d'un bit indiquant la criticité du type de message. Si ce bit est à 1 et que le récepteur ne connait pas ce type, il raccroche. Les types de message actuels sont notamment :

  • Next Protocol Negotiation (valeur 1) qui indique les protocoles acceptés (il n'y en a qu'un à l'heure actuelle, NTP, mais peut-être d'autres protocoles comme PTP suivront, la liste est dans un registre IANA),
  • Error (valeur 2) qui indique… une erreur (encore un registre IANA de ces codes d'erreur, qui seront sans doute moins connus que ceux de HTTP),
  • AEAD Algorithm Negotiation (valeur 3) ; NTS impose l'utilisation du chiffrement intègre (AEAD pour Authenticated Encryption with Associated Data) et ce type de message permet de choisir un algorithme de chiffrement intègre parmi ceux enregistrés,
  • New Cookie (valeur 5), miam, le serveur envoie un cookie, que le client devra renvoyer (sans chercher à la comprendre), ce qui permettra au serveur de reconnaitre un client légitime ; le RFC recommande d'envoyer plusieurs messages de ce type, pour que le client ait une provision de cookies suffisante,
  • Server Negotiation (valeur 6) (et Port Negotiation, valeur 7), pour indiquer le serveur NTP avec lequel parler de façon sécurisée, serveur qui n'est pas forcément le serveur NTS-KE.

D'autres types de message pourront venir dans le futur, cf. le registre.

Une fois la négociation faite avec le protocole NTS-KE, tournant sur TLS, on peut passer au NTP normal, avec le serveur indiqué. Comme indiqué plus haut, quatre champs supplémentaires ont été ajoutés à NTP pour gérer la sécurité. Ils sont présentés dans la section 5 de notre RFC. Les clés cryptographiques sont obtenues par la fonction de dérivation (HKDF) du RFC 8446, section 7.5 (voir aussi le RFC 5705). Les clés du client vers le serveur sont différentes de celles du serveur vers le client (ce sont les clés c2s et s2c dans l'exemple avec ntsclient montré plus loin).

Les paquets NTP échangés ensuite, et sécurisés avec NTS, comporteront l'en-tête NTP classique (RFC 5905, section 7.3), qui ne sera pas chiffré (mais sera authentifié), et le paquet original chiffré dans un champ d'extension (cf. RFC 5905, section 7.5, et RFC 7822). Les quatre nouveaux champs seront :

  • L'identifiant du paquet (qui sert à détecter le rejeu), il sera indiqué (cf. section 5.7) dans la réponse du serveur, permetant au client de détecter des attaques menées en aveugle par des malfaisants qui ne sont pas situés sur le chemin (certaines mises en œuvre de NTP utilisaient les estampilles temporelles pour cela, mais elles sont courtes, et en partie prévisibles),
  • Le cookie,
  • La demande de cookies supplémentaires,
  • Et le principal, le champ contenant le contenu chiffré du paquet NTP original.

On a vu que le format des cookies n'était pas imposé. Cela n'affecte pas l'interopérabilité puisque, de toute façon, le client n'est pas censé comprendre les cookies qu'il reçoit, il doit juste les renvoyer tels quels. La section 6 décrit quand même un format suggéré. Elle rappelle que les cookies de NTS sont utilisés à peu près comme les cookies de session de TLS (RFC 5077). Comme le serveur doit pouvoir reconnaitre les bons cookies des mauvais, et comme il est préférable qu'il ne conserve pas un état différent par client, le format suggéré permet au serveur de fabriquer des cookies qu'il pourra reconnaitre, sans qu'un attaquant n'arrive à en fabriquer. Le serveur part pour cela d'une clé secrète, changée de temps en temps. Pour le cas un peu plus compliqué où un ou plusieurs serveurs NTP assureraient le service avec des cookies distribués par un serveur NTS-KE séparé, le RFC recommande que les clés suivantes soient dérivées de la première, par un cliquet et la fonction de dérivation HKDF du RFC 5869.

Quelles sont les mises en œuvre de NTS à l'heure actuelle ? La principale est dans chrony (mais dans le dépôt git seulement, la version 3.5 n'a pas encore NTS). chrony est écrit en C et comprend un client et un serveur. NTS est compilé par défaut (cela peut être débrayé avec l'option --disable-nts du script ./configure), si et seulement si les bibliothèques sur lesquelles s'appuie chrony (comme la bibliothèque cryptographique nettle) ont tout ce qu'il faut. Ainsi, sur une Ubuntu stable, ./configure n'active pas l'option NTS alors que ça marche sur une Debian instable (sur cette plate-forme, pensez à installer les paquetages bison et asciidoctor, ./configure ne vérifie pas leur présence). Cela se voit dans cette ligne émise par ./configure (le +NTS) :

%  ./configure
   ...
   Checking for SIV in nettle : No
   Checking for SIV in gnutls : Yes
   Features : +CMDMON +NTP +REFCLOCK +RTC -PRIVDROP -SCFILTER -SIGND +ASYNCDNS +NTS +READLINE +SECHASH +IPV6 -DEBUG
  

Autre serveur ayant NTS, NTPsec. (Développement sur Github.) Écrit en C. C'est ce code qui est utilisé pour deux serveurs NTS publics, ntp1.glypnod.com:123 et ntp2.glypnod.com:123 (exemples plus loin).

Il y a également une mise en œuvre de NTS en FPGA. La même organisation gère deux serveurs NTP publics, nts.sth1.ntp.se:4460 et nts.sth2.ntp.se:4460.

Dernier serveur public ayant NTS, celui de Cloudflare, time.cloudflare.com. Il utilise sans doute nts-rust, écrit par Cloudflare (en Rust).

Les autres mises en œuvre de NTS semblent assez expérimentales, plutôt de la preuve de concept pas très maintenue. Il y a :

Voici un exemple avec ntsclient et les serveurs publics mentionnés plus haut. Vérifiez que vous avez un compilateur Go puis :

% git clone https://gitlab.com/hacklunch/ntsclient.git
% make
% ./ntsclient --server=ntp1.glypnod.com:123 -n     
Network time on ntp1.glypnod.com:123 2020-07-14 09:01:08.684729607 +0000 UTC. Local clock off by -73.844479ms.
   

(Le serveur devrait migrer bientôt vers le port 4460.) Si on veut voir plus de détails et toute la machinerie NTS (le type de message 4 est AEAD, le type 5 le cookie, l'algorithme 15 est AEAD_AES_SIV_CMAC_256) :

% ./ntsclient --server=ntp1.glypnod.com:123 -n --debug  
Conf: &main.Config{Server:"ntp1.glypnod.com:123", CACert:"", Interval:1000}
Connecting to KE server ntp1.glypnod.com:123
Record type 1
Critical set
Record type 4
Record type 5
Record type 5
Record type 5
Record type 5
Record type 5
Record type 5
Record type 5
Record type 5
Record type 0
Critical set
NTSKE exchange yielded:
  c2s: ece2b86a7e86611e6431313b1e45b02a8665f732ad9813087f7fc773bd7f2ff9
  s2c: 818effb93856caaf17e296656a900a9b17229e2f79e69f43f9834d3c08194c06
  server: ntp1.glypnod.com
  port: 123
  algo: 15
  8 cookies:
  [puis les cookies]

Notez que les messages de type 5 ont été répétés huit fois, car, conformément aux recommandations du RFC, le serveur envoie huit cookies. Notez aussi que si vous voulez analyser avec Wireshark, il va par défaut interpréter ce trafic sur le port 123 comme étant du NTP et donc afficher n'importe quoi. Il faut lui dire explicitement de l'interpréter comme du TLS (Decode as...). On voit le trafic NTS-KE en TCP puis du trafic NTP plus classique en UDP.

Enfin, la section 8 du RFC détaille quelques points de sécurité qui avaient pu être traités un peu rapidement auparavant. D'abord, le risque de dDoS. NTS, décrit dans notre RFC, apporte une nouveauté dans le monde NTP, la cryptographie asymétrique. Nécessaire pour l'authentification du serveur, elle est bien plus lente que la symétrique et, donc, potentiellement, un botnet pourrait écrouler le serveur sous la charge, en le forçant à faire beaucoup d'opérations de cryptographie asymétrique. Pour se protéger, NTS sépare conceptuellement l'authentification (faite par NTS-KE) et le service NTP à proprement parler. Ils peuvent même être assurés par des serveurs différents, limitant ainsi le risque qu'une attaque ne perturbe le service NTP.

Lorsqu'on parle de NTP et d'attaques par déni de service, on pense aussi à l'utilisation de NTP dans les attaques par réflexion et amplification. Notez qu'elles utilisent en général des fonctions non-standard des serveurs NTP. Le protocole lui-même n'a pas forcément de défauts. En tout cas, NTS a été conçu pour ne pas ajouter de nouvelles possibilités d'amplification. Tous les champs envoyés par le serveur ont une taille qui est au maximum celle des champs correspondants dans la requête du client. C'est par exemple pour cela que le client doit envoyer un champ de demande de cookie, rempli avec des zéros, pour chaque cookie supplémentaire qu'il réclame. Cela peut sembler inutile, mais c'est pour décourager toute tentative d'amplification.

Cette section 8 discute également la vérification du certificat du serveur. Bon, on suit les règles des RFC 5280 et RFC 6125, d'accord. Mais cela laisse un problème amusant : la date d'expiration du certificat. Regardons celui des serveurs publics cités plus haut, avec gnutls-cli :

% gnutls-cli ntp1.glypnod.com:123
Processed 126 CA certificate(s).
Resolving 'ntp1.glypnod.com:123'...
Connecting to '104.131.155.175:123'...
- Certificate type: X.509
- Got a certificate list of 2 certificates.
- Certificate[0] info:
 - subject `CN=ntp1.glypnod.com', issuer `CN=Let's Encrypt Authority X3,O=Let's Encrypt,C=US', serial 0x04f305691067ef030d19eb53bbb392588d07, RSA key 2048 bits, signed using RSA-SHA256, activated `2020-05-20 02:12:34 UTC', expires `2020-08-18 02:12:34 UTC', pin-sha256="lLj5QsLH8M8PjLSWe6SNlXv4fxVAyI6Uep99RWskvOU="
	Public Key ID:
		sha1:726426063ea3c388ebcc23f913b41a15d4ef38b0
		sha256:94b8f942c2c7f0cf0f8cb4967ba48d957bf87f1540c88e947a9f7d456b24bce5
	Public Key PIN:
		pin-sha256:lLj5QsLH8M8PjLSWe6SNlXv4fxVAyI6Uep99RWskvOU=

- Certificate[1] info:
 - subject `CN=Let's Encrypt Authority X3,O=Let's Encrypt,C=US', issuer `CN=DST Root CA X3,O=Digital Signature Trust Co.', serial 0x0a0141420000015385736a0b85eca708, RSA key 2048 bits, signed using RSA-SHA256, activated `2016-03-17 16:40:46 UTC', expires `2021-03-17 16:40:46 UTC', pin-sha256="YLh1dUR9y6Kja30RrAn7JKnbQG/uEtLMkBgFF2Fuihg="
- Status: The certificate is trusted. 
- Description: (TLS1.3-X.509)-(ECDHE-SECP256R1)-(RSA-PSS-RSAE-SHA256)-(AES-256-GCM)
- Options:
- Handshake was completed    
  

On a un classique certificat Let's Encrypt, qui expire le 18 août 2020. Cette date figure dans les certificats pour éviter qu'un malveillant qui aurait mis la main sur la clé privée correspondant à un certificat puisse profiter de son forfait éternellement. Même si la révocation du certificat ne marche pas, le malveillant n'aura qu'un temps limité pour utiliser le certificat. Sauf que la vérification que cette date d'expiration n'est pas atteinte dépend de l'horloge de la machine. Et que le but de NTP est justement de mettre cette horloge à l'heure… On a donc un problème d'œuf et de poule : faire du NTP sécurisé nécessite NTS, or utiliser NTS nécessite de vérifier un certificat, mais vérifier un certificat nécessite une horloge à l'heure, donc que NTP fonctionne. Il n'y a pas de solution idéale à ce problème. Notre RFC suggère quelques trucs utiles, comme :

  • Prendre un risque délibéré en ne vérifiant pas la date d'expiration du certificat,
  • Chercher à savoir si l'horloge est correcte ou pas ; sur un système d'exploitation qui dispose de fonctions comme ntp_adjtime, le résultat de cette fonction (dans ce cas, une valeur autre que TIME_ERROR) permet de savoir si l'horloge est correcte,
  • Permettre à l'administrateur système de configurer la validation des dates des certificats ; s'il sait que la machine a une horloge matérielle sauvegardée, par exemple via une pile, il peut demander une validation stricte,
  • Utiliser plusieurs serveurs NTP et les comparer, dans l'espoir qu'ils ne soient pas tous compromis (cf. RFC 5905, section 11.2.1).

Comme toujours avec la cryptographie, lorsqu'il existe une version non sécurisée d'un protocole (ici, le NTP traditionnel), il y a le risque d'une attaque par repli, qui consiste à faire croire à une des deux parties que l'autre partie ne sait pas faire de la sécurité (ici, du NTS). Ce NTP stripping est possible si, par exemple, le client NTP se rabat en NTP classique en cas de problème de connexion au serveur NTS-KE. Le RFC recommande donc que le repli ne se fasse pas par défaut, mais uniquement si le logiciel a été explicitement configuré pour prendre ce risque.

Enfin, si on veut faire de la synchronisation d'horloges sécurisée, un des problèmes les plus difficiles est l'attaque par retard (section 11.5 du RFC). Le MAC empêche un attaquant actif de modifier les messages mais il peut les retarder, donnant ainsi au client une fausse idée de l'heure qu'il est réellement (RFC 7384, section 3.2.6 et Mizrahi, T., « A game theoretic analysis of delay attacks against time synchronization protocols », dans les Proceedings of Precision Clock Synchronization for Measurement Control and Communication à ISPCS 2012). Ce dernier article n'est pas en ligne mais, heureusement, il y a Sci-Hub (DOI 10.1109/ISPCS.2012.6336612).

Les contre-mesures possibles ? Utiliser plusieurs serveurs, ce que fait déjà NTP. Faire attention à ce qu'ils soient joignables par des chemins différents (l'attaquant devra alors contrôler plusieurs chemins). Tout chiffrer avec IPsec, ce qui n'empêche pas l'attaque mais rend plus difficile l'identification des seuls paquets de synchronisation.

Revenons au mode symétrique de NTP, qui n'est pas traité par notre RFC. Vous vous demandez peut-être comment le sécuriser. Lors des discussions à l'IETF sur NTS, il avait été envisagé d'encapsuler tous les paquets dans DTLS mais cette option n'a finalement pas été retenue. Donc, pour le symétrique, la méthode recommandée est d'utiliser le MAC des RFC 5905 (section 7.3) et RFC 8573. Et le mode à diffusion ? Il n'y a pas pour l'instant de solution à ce difficile problème.

Et la vie privée (section 9) ? Il y a d'abord la question de la non-traçabilité. On ne veut pas qu'un observateur puisse savoir que deux requêtes NTP sont dues au même client. D'où l'idée d'envoyer plusieurs cookies (et de les renouveler), puisque, autrement, ces identificateurs envoyés en clair trahiraient le client, même s'il a changé d'adresse IP.

Et la confidentialité ? Le chiffrement de l'essentiel du paquet NTP fournit ce service, les en-têtes qui restent en clair, comme le cookie, ne posent pas de problèmes de confidentialité particuliers.

Un petit mot maintenant sur l'historique de ce RFC. Le projet initial était plus ambitieux (cf. l'Internet-Draft draft-ietf-ntp-network-time-security). Il s'agissait de développer un mécanisme abstrait, commun à NTP et PTP/IEEE 1588 (alors que ce RFC 8915 est spécifique à NTP). Un autre groupe de travail IETF TICTOC continue de son côté mais ne fait plus de sécurité.

Si vous voulez des bonnes lectures sur NTS, autres que le RFC, il y a un article sur la sécurité NTP à la conférence ISPCS 17, « New Security Mechanisms for Network Time Synchronization Protocols ») (un des auteurs de l'article est un des auteurs du RFC). C'est un exposé général des techniques de sécurité donc il inclut PTP mais la partie NTP est presque à jour, à part la suggestion de DTLS pour les autres modes, qui n'a finalement pas été retenue). Les supports du même article à la conférence sont sur Slideshare. Et cet article est résumé dans l'IETF journal de novembre 2017, volume 13 issue 2. Il y a aussi l'article de l'ISOC, qui résume bien les enjeux.


Téléchargez le RFC 8915


L'article seul

RFC 8914: Extended DNS Errors

Date de publication du RFC : Octobre 2020
Auteur(s) du RFC : W. Kumari (Google), E. Hunt (ISC), R. Arends (ICANN), W. Hardaker (USC/ISI), D. Lawrence (Oracle + Dyn)
Chemin des normes
Réalisé dans le cadre du groupe de travail IETF dnsop
Première rédaction de cet article le 24 octobre 2020


Un problème classique du DNS est qu'il n'y a pas assez de choix pour le code de retour renvoyé par un serveur DNS. Contrairement à la richesse des codes de retour HTTP, le DNS est très limité. Ainsi, le code de retour SERVFAIL (SERver FAILure) sert à peu près à tout, et indique des erreurs très différentes entre elles. D'où ce nouveau RFC qui normalise un mécanisme permettant des codes d'erreur étendus, ce qui facilitera le diagnostic des problèmes DNS.

Ces codes de retour DNS (RCODE, pour Response CODE) sont décrits dans le RFC 1035, section 4.1.1. Voici un exemple de requête faite avec dig. Le code de retour est indiqué par l'étiquette status:. Ici, c'est REFUSED, indiquant que le serveur faisant autorité pour google.com ne veut pas répondre aux requêtes pour mon .org :


% dig @ns1.google.com AAAA www.bortzmeyer.org 

; <<>> DiG 9.11.3-1ubuntu1.12-Ubuntu <<>> @ns1.google.com AAAA www.bortzmeyer.org
; (2 servers found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: REFUSED, id: 4181
;; flags: qr rd; QUERY: 1, ANSWER: 0, AUTHORITY: 0, ADDITIONAL: 1
;; WARNING: recursion requested but not available

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags: do; udp: 512
;; QUESTION SECTION:
;www.bortzmeyer.org.	IN AAAA

;; Query time: 7 msec
;; SERVER: 2001:4860:4802:32::a#53(2001:4860:4802:32::a)
;; WHEN: Sat Jun 20 11:07:13 CEST 2020
;; MSG SIZE  rcvd: 47

  

Codés sur seulement quatre bits, ces codes de retour ne peuvent pas être très nombreux. Le RFC 1035 en normalisait six, et quelques autres ont été ajoutés par la suite, mais sont rarement vus sur le terrain. En pratique, les plus fréquents sont NOERROR (pas de problème), NXDOMAIN (ce nom de domaine n'existe pas) et SERVFAIL (la plupart des autres erreurs). Notez que le RFC 1034 n'utilisait pas ces sigles à l'origine ils ont été introduits après.

On l'a dit, ces codes de retour sont insuffisants. Lorsqu'un client DNS reçoit SERVFAIL, il doit essayer de deviner ce que cela voulait dire. Lorsqu'on parle à un résolveur, le SERVFAIL indique-t-il un problème de validation DNSSEC ? Ou bien que les serveurs faisant autorité sont injoignables ? Lorsqu'on parle à un serveur faisant autorité, le SERVFAIL indiquait-il que ce serveur n'avait pas pu charger sa zone (peut-être parce que le serveur maître était injoignable) ? Ou bien un autre problème ?

L'un des scénarios de déboguage les plus communs est celui de DNSSEC. Au moins, il existe un truc simple : refaire la requête avec le bit CD (Checking Disabled). Si ça marche (NOERROR), alors on est raisonnablement sûr que le problème était un problème DNSSEC. Mais cela ne nous dit pas lequel (signatures expirées ? clés ne correspondant pas à la délégation ?). DNSSEC est donc un des principaux demandeurs d'erreurs plus détaillées.

Alors, après cette introduction (section 1 du RFC), la solution (section 2). Les erreurs étendues sont transportées via une option EDNS (RFC 6891) dans la réponse. Cette option comporte un type (numéro 15) et une longueur (comme toutes les options EDNS) suivis d'un code d'information (sur deux octets) et de texte libre, non structuré (en UTF-8, cf. RFC 5198, et attention à ne pas le faire trop long, pour éviter la troncation DNS). Les codes d'information possibles sont dans un registre à l'IANA. En ajouter est simple, par la procédure « Premier Arrivé, Premier Servi » du RFC 8126. Cette option EDNS peut être envoyée par le serveur DNS, quel que soit le code de retour (y compris donc NOERROR). Il peut y avoir plusieurs de ces options. La seule condition est que la requête indiquait qu'EDNS est accepté par le client. Le client n'a pas à demander explicitement des erreurs étendues et ne peut pas forcer l'envoi d'une erreur étendue, cela dépend uniquement du serveur.

Les codes d'information initiaux figurent en section 4 du RFC. Le RFC ne précise pas avec quels codes de retour les utiliser (ce fut une grosse discussion à l'IETF…) mais, évidemment, certains codes d'erreur étendus n'ont de sens qu'avec certains codes de retour, je les indiquerai ici. Voici quelques codes d'erreur étendus :

  • 0 indique un cas non prévu, le texte qui l'accompagne donnera des explications.
  • 1 signale que le domaine était signé, mais avec uniquement des algorithmes inconnus du résolveur. Il est typiquement en accompagnement d'un NOERROR pour expliquer pourquoi on n'a pas validé (en DNSSEC, un domaine signé avec des algorithmes inconnus est considéré comme non signé).
  • 3 (ou 19, pour le NXDOMAIN) indique une réponse rassise (RFC 8767). Voir aussi le 22, plus loin.
  • 4 est une réponse fabriquée de toutes pièces par un résolveur menteur. Notez qu'il existe d'autres codes, plus loin, de 15 à 17, permettant de détailler les cas de filtrage. 4 est typiquement en accompagnement d'un NOERROR puisqu'il y a une réponse.
  • 6 vient avec les SERVFAIL pour indiquer que la validation DNSSEC a déterminé un problème (signature invalide ou absente). Plusieurs autres codes traitent des problèmes DNSSEC plus spécifiques.
  • 7 vient également avec SERVFAIL pour le cas des signatures expirées (sans doute un des problèmes DNSSEC les plus fréquents).
  • Trois codes viennent pour les cas où le résolveur refuse de répondre pour ce domaine, et le dit (au lieu, ou en plus, de fabriquer une réponse mensongère). Ce sont 15 (réponse bloquée par une décision de l'administrateur du résolveur), 16 (réponse bloquée par une décision extérieure, typiquement l'État, et il ne sert donc à rien de se plaindre à l'administrateur du résolveur, c'est de la censure) et 17 (réponse bloquée car le client a demandé qu'on filtre pour lui, par exemple parce qu'il a choisi de bloquer les domaines liés à la publicité). 16 est donc à peu près l'équivalent du 451 de HTTP, normalisé dans le RFC 7725. Gageons que les résolveurs menteurs en France se garderont bien de l'utiliser…
  • 18 accompagne en général REFUSED et indique au client qu'il n'est pas le bienvenu (par exemple une requête à un résolveur venue de l'extérieur du réseau local).
  • 22 indique qu'un résolveur n'a pas pu joindre aucun des serveurs faisant autorité. Ce code peut accompagner une réponse rassise ou bien un SERVFAIL. J'en profite pour vous rappeler que, si vous gérez une zone DNS, attention à sa robustesse : évitez les SPOF. Ayez plusieurs serveurs faisant autorité, et dans des lieux séparés (et de préférence dans des AS distincts).

Le client DNS qui reçoit un code d'erreur étendu est libre de l'utiliser comme il le souhaite (les précédentes versions de ce mécanisme prévoyaient des suggestions faites au client mais ce n'est plus le cas). Le but principal de cette technique est de fournir des informations utiles pour le diagnostic. (Une discussion à l'IETF avait porté sur des codes indiquant précisément si le problème était local - et devait donc être soumis à son administrateur système - ou distant, auquel cas il n'y avait plus qu'à pleurer, mais cela n'a pas été retenu. Notez que la différence entre les codes 15 et 16 vient de là, du souci d'indiquer à qui se plaindre.)

Attention à la section 6 du RFC, sur la sécurité. Elle note d'abord que certains clients, quand ils reçoivent un SERVFAIL, essaient un autre résolveur (ce qui n'est pas une bonne idée si le SERVFAIL était dû à un problème DNSSEC). Ensuite, les codes d'erreur étendus ne sont pas authentifiés (comme, d'ailleurs, les codes de retour DNS habituels), sauf si on utilise, pour parler au serveur, un canal sécurisé comme DoT (RFC 7858) ou DoH (RFC 8484). Il ne faut donc pas s'y fier trop aveuglément. Enfin, ces codes étendus vont fuiter de l'information à un éventuel observateur (sauf si on utilise un canal chiffré, comme avec DoT ou DoH) ; par exemple, 18 permet de savoir que vous n'êtes pas le bienvenu sur le serveur.

Et, désolé, je ne connais pas de mise en œuvre de cette option à l'heure actuelle. J'avais développé le code nécessaire pour Knot lors du hackathon de l'IETF à Prague en mars 2019 (le format a changé depuis donc ce code ne peut pas être utilisé tel quel). La principale difficulté n'était pas dans le formatage de la réponse, qui est trivial, mais dans le transport de l'information, depuis les entrailles du résolveur où le problème était détecté, jusqu'au code qui fabriquait la réponse. Il fallait de nouvelles structures de données, plus riches.

Sinon, le RFC mentionne le groupe Infected Mushroom. Si vous voulez voir leurs clips


Téléchargez le RFC 8914


L'article seul

RFC 8910: Captive-Portal Identification in DHCP and Router Advertisements (RAs)

Date de publication du RFC : Septembre 2020
Auteur(s) du RFC : W. Kumari (Google), E. Kline (Loon)
Chemin des normes
Réalisé dans le cadre du groupe de travail IETF capport
Première rédaction de cet article le 22 septembre 2020


Les portails captifs sont une des plaies de l'accès à l'Internet. À défaut de pouvoir les supprimer, ce nouveau RFC propose des options aux protocoles DHCP et RA pour permettre de signaler la présence d'un portail captif, au lieu que la malheureuse machine ne doive le détecter elle-même. Il remplace le RFC 7710, le premier à traiter ce sujet. Il y a peu de changements de fond mais quand même deux modifications importantes. D'abord, il a fallu changer le code qui identifiait l'option DHCP en IPv4 (du code 160 au 114). Et ensuite l'URI annoncé sur le réseau est désormais celui de l'API et pas une page Web conçue pour un humain.

On trouve de tels portails captifs en de nombreux endroits où de l'accès Internet est fourni, par exemple dans des hôtels, des trains ou des cafés. Tant que l'utilisateur ne s'est pas authentifié auprès du portail captif, ses capacités d'accès à l'Internet sont très limitées. Quels sont les problèmes que pose un portail captif ?

  • Le plus important est qu'il détourne le trafic normal : en cela, le portail captif est une attaque de l'Homme du Milieu et a donc de graves conséquences en terme de sécurité. Il éduque les utilisateurs à trouver normal le fait de ne pas arriver sur l'objectif désiré, et facilite donc le hameçonnage. Au fur et à mesure que les exigences de sécurité augmentent, ces portails captifs seront, on peut l'espérer, de plus en plus mal vus.
  • Le portail captif ne marche pas ou mal si la première connexion est en HTTPS, ce qui est, à juste titre, de plus en plus fréquent. Là encore, il éduque les utilisateurs à trouver normaux les avertissements de sécurité (« mauvais certificat, voulez-vous continuer ? »).
  • Le portail captif n'est pas authentifié, lui, et l'utilisateur est donc invité à donner ses informations de créance à un inconnu.
  • En Wifi, comme l'accès n'est pas protégé par WPA, le trafic peut être espionné par les autres utilisateurs.
  • Spécifique au Web, le portail captif ne marche pas avec les activités non-Web (comme XMPP). Même les clients HTTP non-interactifs (comme une mise à jour du logiciel via HTTP) sont affectés.

Pourquoi est-ce que ces hôtels et cafés s'obstinent à imposer le passage par un portail captif ? On lit parfois que c'est pour authentifier l'utilisateur mais c'est faux. D'abord, certains portails captifs ne demandent pas d'authentification, juste une acceptation des conditions d'utilisation. Ensuite, il existe une bien meilleure solution pour authentifier, qui n'a pas les défauts indiqués plus haut. C'est 802.1X, utilisé entre autres dans eduroam (voir RFC 7593). La vraie raison est plutôt une combinaison d'ignorance (les autres possibilités comme 802.1X ne sont pas connues) et de désir de contrôle (« je veux qu'ils voient mes publicités et mes CGU »).

L'IETF travaille à développer un protocole complet d'interaction avec les portails captifs, pour limiter leurs conséquences. En attendant que ce travail soit complètement terminé, ce RFC propose une option qui permet au moins au réseau local de signaler « attention, un portail captif est là, ne lance pas de tâches - comme Windows Update - avant la visite du portail ». Cette option peut être annoncée par le serveur DHCP (RFC 2131 et RFC 8415) ou par le routeur qui envoie des RA (RFC 4861).

Cette option (section 2 du RFC) annonce au client qu'il est derrière un portail captif et lui fournit l'URI de l'API à laquelle accéder (ce qui évite d'être détourné, ce qui est grave quand on utilise HTTPS). Les interactions avec l'API de ce serveur sont spécifiées dans le RFC 8908.

Les sections 2.1, 2.2 et 2.3 de notre RFC donnent le format de l'option en DHCP v4, DHCP v6 et en RA. Le code DHCP v4 est 114 (un changement de notre RFC), le DHCP v6 est 103 et le type RA est 37. Pour la création d'options DHCPv6 avec des URI, voir le RFC 7227, section 5.7.

Un URI spécial, urn:ietf:params:capport:unrestricted est utilisé pour le cas où il n'y a pas de portail captif. (Cet URI spécial est enregistré à l'IANA, avec les autres suffixes du RFC 3553.)

Bien que la première version de ce RFC date de plus de quatre ans, la grande majorité des réseaux d'accès avec portail captif n'annoncent toujours pas la présence de ce portail et il faudra encore, pendant de nombreuses années, que les machines terminales « sondent » et essaient de détecter par elles-même s'il y a un portail captif ou pas. Elles font cela, par exemple, en essayant de se connecter à une page Web connue (comme http://detectportal.firefox.com/ avec Firefox). Ici, en cas de vrai accès Internet :

%  curl http://detectportal.firefox.com/
success
  

Et ici lorsqu'il y a un portail captif qui détourne le trafic :


% curl -v http://detectportal.firefox.com/
> GET / HTTP/1.1
> Host: detectportal.firefox.com
> User-Agent: curl/7.58.0
> Accept: */*
> 
< HTTP/1.1 302 Moved Temporarily
< Server: nginx
< Date: Mon, 27 Jul 2020 18:29:52 GMT
...
< Location: https://wifi.free.fr/?url=http://detectportal.firefox.com/
< 
<html>
<head><title>302 Found</title></head>
<body bgcolor="white">
<center><h1>302 Found</h1></center>
<hr><center>nginx</center>
</body>
</html>

  

La section 5 de notre RFC étudie les conséquences de cette option pour la sécurité. Elle rappelle que DHCP et RA ne sont pas sécurisés, de toute façon. Donc, un méchant peut envoyer une fausse option « il y a un portail captif, allez à cet URI pour vous authentifier » mais le même méchant peut aussi bien annoncer un faux routeur et ainsi recevoir tout le trafic... Si on n'utilise pas des précautions comme le RA Guard (RFC 6105) ou le DHCP shield (RFC 7610), on ne peut de toute façon pas se fier à ce qu'on a obtenu en DHCP ou RA.

Il est même possible que cette option nouvelle améliore la sécurité, en n'encourageant pas les utilisateurs à couper les mécanismes de validation comme la vérification des certificats, contrairement à ce que font les portails captifs actuels, qui se livrent à une véritable attaque de l'Homme du Milieu.

Pour DHCP, la plupart des serveurs permettent de servir une option quelconque, en mettant ses valeurs manuellement et une future mise à jour ne servira donc qu'à rendre l'usage de cette option plus simple. (Pour RA, c'est plus complexe, cf. l'expérience lors d'une réunion IETF.) Autrement, je ne connais pas encore de mise en œuvre côté clients DHCP ou RA, mais il semble (je n'ai pas testé personnellement) que ça marche sur iOS à partir de la version 14.

L'annexe B de notre RFC cite les changements depuis le RFC 7710. Les principaux :

  • L'ajout de l'URN urn:ietf:params:capport:unrestricted pour le cas où il n'y a pas de portail captif,
  • le changement de l'option DHCP en IPv4, suite à la collision avec les produits Polycom,
  • l'URI envoyée désigne désormais une API et plus una page Web ordinaire (contrairement à ce que dit l'annexe B de notre RFC, il ne s'agit pas d'une clarification mais d'un vrai changement),
  • les noms (et plus seulement les adresses IP) sont acceptés dans l'URI,
  • et plusieurs éclaircissements et précisions.

Téléchargez le RFC 8910


L'article seul

RFC 8909: Registry Data Escrow Specification

Date de publication du RFC : Novembre 2020
Auteur(s) du RFC : G. Lozano (ICANN)
Chemin des normes
Réalisé dans le cadre du groupe de travail IETF regext
Première rédaction de cet article le 14 novembre 2020


Prenons un registre, par exemple un registre de noms de domaine. Des tas de gens comptent sur lui. Si ce registre disparait en emportant sa base de données, ce serait une catastrophe. Il est donc nécessaire de garder les données à part, ce qu'on nomme un séquestre (escrow). Ce RFC décrit un format standard pour les données de séquestre, indépendant du type de registre (même si la demande prioritaire est pour les registres de noms de domaine). Il s'agit d'un format générique, qui sera complété par des spécifications pour les différents types de registre, par exemple le RFC 9022 pour les registres de noms de domaine.

L'idée (section 1 du RFC) est donc que le registre dépose à intervalles réguliers une copie de sa base de données auprès de l'opérateur de séquestre. Il ne s'agit pas d'une sauvegarde. La sauvegarde est conçue pour faire face à des accidents comme un incendie. Le séquestre, lui, est conçu pour faire face à une disparition complète du registre, par exemple une faillite si le registre est une entreprise commerciale. Ou bien une redélégation complète du domaine à un autre registre. Les sauvegardes, non standardisées et liées à un système d'information particulier, ne peuvent pas être utilisées dans ces cas. D'où l'importance d'un format standard pour un séquestre, précisément ce que normalise notre RFC. Lors d'une défaillance complète de l'ancien registre, l'opérateur de séquestre transmettra les données de séquestre au nouveau registre, qui pourra l'importer dans son système d'information, qui sera alors prêt à prendre le relais. Pour un registre de noms de domaine, le dépôt des données de séquestre devra comprendre la liste des noms, les titulaires et contacts pour chacun des noms, les serveurs de noms, les enregistrements DS (utilisés pour DNSSEC), etc. Par contre, certaines informations ne pourront pas être incluses, comme les parties privées des clés utilisées pour DNSSEC (qui sont peut-être enfermées dans un HSM) et le nouveau registre aura donc quand même un peu de travail de réparation.

Ainsi, la convention entre l'État et l'AFNIC pour la gestion du .fr prévoit dans sa section 5 que « L'Office d'enregistrement [sic] s'engage à mettre en place et à maintenir sur le sol français un séquestre de données quotidien ». Même chose pour les TLD sous contrat avec l'ICANN. Le Base Registry Agreement impose un séquestre dans sa spécification 2 « DATA ESCROW REQUIREMENTS ». Elle dit « Deposit’s Format. Registry objects, such as domains, contacts, name servers, registrars, etc. will be compiled into a file constructed as described in draft-arias-noguchi-registry-data-escrow, see Part A, Section 9, reference 1 of this Specification and draft-arias-noguchi-dnrd-objects-mapping, see Part A, Section 9, reference 2 of this Specification (collectively, the “DNDE Specification”). ». [Le document draft-arias-noguchi-registry-data-escrow est devenu ce RFC 8909.] L'ICANN reçoit actuellement 1 200 séquestres, pour chaque TLD qu'elle régule, les dépôts ayant lieu une fois par semaine.

Ces exigences sont tout à fait normales : la disparition d'un registre de noms de domaine, s'il n'y avait pas de séquestre, entrainerait la disparition de tous les noms, sans moyen pour les titulaires de faire valoir leurs droits. Si .org disparaissait sans séquestre, bortzmeyer.org n'existerait plus, et si un registre prenait le relais avec une base de données vide, rien ne me garantit d'obtenir ce nom, l'adresse de ce blog devrait donc changer. Cette question de continuité de l'activité de registre, même si des organisations disparaissent, est la motivation pour ce RFC (section 3).

Un exemple de service de « registre de secours » (third-party beneficiary, dans la section 2 du RFC, on pourrait dire aussi backup registry) est l'EBERO (Emergency Back-End Registry Operator) de l'ICANN, décrit dans les « Registry Transition Processes ».

Ce RFC ne spécifie qu'un format de données. Il ne règle pas les questions politiques (faut-il faire un séquestre, à quel rythme, qui doit être l'opérateur de séquestre, qui va désigner un registre de secours, etc).

Passons donc à la technique (section 5). Le format est du XML. L'élément racine est <deposit> (dépôt). Un attribut type indique si ce dépôt est complet (la totalité des données) ou bien incrémental (différence avec le dépôt précédent). L'espace de noms est urn:ietf:params:xml:ns:rde-1.0, enregistré à l'IANA (voir la section 8). RDE signifie Registry Data Escrow. Parmi les éléments obligatoires sous <deposit>, il y a :

  • <watermark> qui indique le moment où ce dépôt a été fait, au format du RFC 3339 (section 4). On peut traduire watermark par jalon, point de synchronisation, point d'étape ou marque.
  • <rdeMenu>, diverses métadonnées.
  • <contents> contient les données (rappelez-vous que ce RFC est générique, le format exact des données, qui dépend du type de registre, sera dans un autre RFC).
  • <deletes> ne sert que pour les dépôts incrémentaux, et indique les objets qui ont été détruits depuis la dernière fois.
  • Eh non, il n'y a pas d'élément <adds> ; dans un dépôt incrémental, les éléments ajoutés sont sous <contents>.

Voici un exemple très partiel d'un dépôt complet :


<?xml version="1.0" encoding="UTF-8"?>
<d:deposit xmlns:d="urn:ietf:params:xml:ns:rde-1.0"
	   xmlns:myobj="urn:example:my-objects-1.0"
	   type="FULL" id="20201006" resend="0">
  <d:watermark>2020-10-06T13:12:15Z</d:watermark>
  <d:rdeMenu>
    <d:version>1.0</d:version>
    <d:objURI>urn:example:my-objects-1.0</d:objURI>
  </d:rdeMenu>
  <d:contents>
    <myobj:object>
      <myobj:value>42</myobj:value>
    </myobj:object>
  </d:contents>  
</d:deposit>

Dans cet exemple, le fichier XML contient un dépôt complet, fait le 6 octobre 2020, et dont le contenu est un seul élément, de valeur 42. (Pour un vrai dépôt lors d'un séquestre d'un registre de noms de domaine, on aurait, au lieu de <myobj:object>, les domaines, leurs titulaires, etc.) Vous avez des exemples un peu plus réalistes dans les sections 11 à 13 du RFC.

Attention si vous utilisez des dépôts incrémentaux, l'ordre des <contents> et <deletes> compte : il faut mettre les <deletes> en premier.

La syntaxe complète est décrite dans la section 6 du RFC, en XML Schema. Du fait de l'utilisation de XML, l'internationalisation est automatique, on peut par exemple mettre les noms des contacts en Unicode (section 7).

Le format décrit dans ce RFC est seulement un format. Des tas de questions subsistent si on veut un système de séquestre complet, il faut par exemple spécifier un mécanisme de transport des données entre le registre et l'opérateur de séquestre (par exemple avec SSH, ou bien un POST HTTPS). Pour l'ICANN, c'est spécifié dans l'Internet-Draft draft-lozano-icann-registry-interfaces.

Il faut également se préoccuper de tout ce qui concerne la sécurité. La section 9 du RFC rappelle que, si on veut que les données de séquestre soient confidentielles (pour un registre de noms de domaine, les coordonnées des titulaires et contacts sont certainement en bonne partie des données personnelles, cf. section 10), il faut chiffrer la communication entre le registre et l'opérateur de séquestre. Et il faut bien sûr tout authentifier. L'opérateur de séquestre doit vérifier que le dépôt vient bien du registre et n'est pas un dépôt injecté par un pirate, le registre doit vérifier qu'il envoie bien le dépôt à l'opérateur de séquestre et pas à un tiers. Comme exemple des techniques qui peuvent être utilisées pour atteindre ce but, l'ICANN cite OpenPGP (RFC 4880) : « Files processed for compression and encryption will be in the binary OpenPGP format as per OpenPGP Message Format - RFC 4880, see Part A, Section 9, reference 3 of this Specification. Acceptable algorithms for Public-key cryptography, Symmetric-key cryptography, Hash and Compression are those enumerated in RFC 4880, not marked as deprecated in OpenPGP IANA Registry, see Part A, Section 9, reference 4 of this Specification, that are also royalty-free. ».

Comme le séquestre ne servirait qu'en cas de terminaison complète du registre, on peut penser que le registre actuel n'est pas très motivé pour assurer ce service (et c'est pour cela qu'il doit être imposé). Il y a un risque de négligence (voire de malhonnêté) dans le production des dépôts. Voici pourquoi l'opérateur de séquestre doit tester que les dépôts qu'il reçoit sont corrects. Au minimum, il doit vérifier leur syntaxe. Ici, on va se servir de xmllint pour cela. D'abord, on utilise un schéma pour les données spécifiques de notre type de registre. Ici, il est très simple, uniquement des données bidon :


% cat myobj.xsd                                
<?xml version="1.0" encoding="utf-8"?>

<schema targetNamespace="urn:example:my-objects-1.0"
	xmlns:myobj="urn:example:my-objects-1.0"
	xmlns:rde="urn:ietf:params:xml:ns:rde-1.0"
	xmlns="http://www.w3.org/2001/XMLSchema"
	elementFormDefault="qualified">

    <annotation>
      <documentation>
        Test
      </documentation>
    </annotation>
    
    <element name="object" type="myobj:objectType" substitutionGroup="rde:content"/>

    <complexType name="objectType">
      <complexContent>
	<extension base="rde:contentType">
	  <sequence>
	    <element name="value" type="integer"/>
	  </sequence>
      </extension>
      </complexContent>
    </complexType>
 
</schema>

  

Ensuite, on écrit un petit schéma qui va importer les deux schémas, le nôtre (ci-dessus), spécifique à un type de registre, et le schéma du RFC, générique :


% cat wrapper.xsd                              
<?xml version="1.0" encoding="utf-8"?>

<schema targetNamespace="urn:example:tmp-1.0"
	xmlns="http://www.w3.org/2001/XMLSchema">

  <import namespace="urn:ietf:params:xml:ns:rde-1.0" schemaLocation="rde.xsd"/>
  <import namespace="urn:example:my-objects-1.0" schemaLocation="myobj.xsd"/>

</schema>

  

Ensuite, on produit le dépôt :

  
% cat test.xml                                 
<?xml version="1.0" encoding="UTF-8"?>
<d:deposit xmlns:d="urn:ietf:params:xml:ns:rde-1.0"
	   xmlns:myobj="urn:example:my-objects-1.0"
	   type="FULL" id="20201006" resend="0">
  <d:watermark>2020-10-06T13:12:15Z</d:watermark>
  <d:rdeMenu>
    <d:version>1.0</d:version>
    <d:objURI>urn:example:my-objects-1.0</d:objURI>
  </d:rdeMenu>
  <d:contents>
    <myobj:object>
      <myobj:value>42</myobj:value>
    </myobj:object>
  </d:contents>  
</d:deposit>

  

Et on n'a plus qu'à valider ce dépôt :

    
% xmllint --noout --schema wrapper.xsd test.xml
test.xml validates

  

Si les données étaient incorrectes (dépôt mal fait), xmllint nous préviendrait :

% xmllint --noout --schema wrapper.xsd test.xml
test.xml:12: element value: Schemas validity error : Element '{urn:example:my-objects-1.0}value': 'toto' is not a valid value of the atomic type 'xs:integer'.
test.xml fails to validate
  

Idéalement, il faudrait même que l'opérateur de séquestre teste un chargement complet des données dans un autre logiciel de gestion de registre (oui, je sais, c'est beaucoup demander). Bon, s'il vérifie la syntaxe, c'est déjà ça.


Téléchargez le RFC 8909


L'article seul

RFC 8908: Captive Portal API

Date de publication du RFC : Septembre 2020
Auteur(s) du RFC : T. Pauly (Apple), D. Thakore (CableLabs)
Chemin des normes
Réalisé dans le cadre du groupe de travail IETF capport
Première rédaction de cet article le 22 septembre 2020


Un des nombreux problèmes posés par les portails captifs est l'interaction avec le portail, par exemple pour accepter les CGU et continuer. Ces portails ne sont en général prévus que pour une interaction avec un humain. Ce RFC décrit une API ultra-simple qui permet à des programmes, au moins de savoir s'il y a un portail, quelles sont ses caractéristiques et comment sortir de captivité.

L'API suit les principes du RFC 8952. Elle permet donc de récupérer l'état de la captivité (est-ce que j'ai un accès à l'Internet ou pas), et l'URI de la page Web avec laquelle l'humain devra interagir.

Comment est-ce que la machine qui tente de se connecter a appris l'existence de l'API et son URI d'entrée ? Typiquement via les options de DHCP ou de RA décrites dans le RFC 8910. On accède ensuite à l'API avec HTTPS (RFC 2818). Il faudra naturellement authentifier le serveur, ce qui peut poser des problèmes tant qu'on n'a pas un accès à l'Internet complet (par exemple à OCSP, RFC 6960, et à NTP, RFC 5905, pour mettre l'horloge à l'heure et ainsi vérifier que le certificat n'a pas encore expiré). De même, des certificats intermédiaires qu'il faut récupérer sur l'Internet via Authority Information Access (AIA, section 5.2.7 du RFC 5280) peuvent poser des problèmes et il vaut mieux les éviter.

L'API elle-même est présentée en section 5 du RFC. Le contenu est évidement en JSON (RFC 8259), et servi avec le type application/captive+json. Dans les réponses du serveur, l'objet JSON a obligatoirement un membre captive dont la valeur est un booléen et qui indique si on on est en captivité ou pas. Les autres membres possibles sont :

  • user-portal-uri indique une page Web pour humains, avec laquelle l'utilisateur peut interagir,
  • venue-info-url est une page Web d'information,
  • can-extend-session, un booléen qui indique si on peut prolonger la session, ce qui veut dire que cela peut être une bonne idée de ramener l'humain vers la page Web user-portal-uri lorsque la session va expirer,
  • seconds-remaining, le nombre de secondes restant pour cette session, après quoi il faudra se reconnecter (si can-extend-session est à Vrai),
  • bytes-remaining, la même chose mais pour des sessions limitées en quantité de données et plus en temps.

Ces réponses de l'API peuvent contenir des données spécifiques au client, et donc privées. Auquel cas, le serveur doit penser à utiliser Cache-control: private (RFC 7234) ou un mécanisme équivalent, pour éviter que ces données se retrouvent dans des caches.

Un exemple complet figure en section 6 du RFC. On suppose que le client a découvert l'URL https://example.org/captive-portal/api/X54PD39JV via un des mécanismes du RFC 8910. Il envoie alors une requête HTTP :

GET /captive-portal/api/X54PD39JV HTTP/1.1
Host: example.org
Accept: application/captive+json
  

Et reçoit une réponse :

HTTP/1.1 200 OK
Cache-Control: private
Date: Mon, 02 Mar 2020 05:07:35 GMT
Content-Type: application/captive+json

{
      "captive": true,
      "user-portal-url": "https://example.org/portal.html"
}    
  

Il sait alors qu'il est en captivité, et que l'utilisateur doit aller en https://example.org/portal.html pour accepter des CGU léonines, s'authentifier, etc. Une fois que c'est fait, il peut continuer à faire des requêtes à l'API et avoir, par exemple :

{
      "captive": false,
      "user-portal-url": "https://example.org/portal.html",
      "venue-info-url": "https://flight.example.com/entertainment",
      "seconds-remaining": 326,
      "can-extend-session": true
}   
  

D'autres membres de l'objet JSON pourront apparaitre, selon la procédure « Spécification nécessaire » (RFC 8126), un registre IANA a été créé pour les stocker.

Un peu de sécurité pour finir (section 7 du RFC). Le protocole de notre RFC impose l'utilisation de HTTPS (donc de TLS) ce qui règle pas mal de problèmes, notamment de confidentialité et d'authentification. À noter donc (mais cela en vaut la peine) que cela complique un peu les choses pour l'administrateur du portail captif, qui ne peut pas se contenter de HTTP en clair, et qui doit avoir un certificat valide. (Cet argument de la complexité avait été mentionné lors des discussions à l'IETF, où certains trouvaient notre RFC trop exigeant.) Combien de fois ai-je vu des portails captifs avec un certificat auto-signé et/ou expiré !

Mais attention, TLS va seulement sécuriser le fait qu'on se connecte bien au serveur indiqué via les méthodes du RFC 8910. Or, comme ces méthodes ne sont pas elle-mêmes très sûres, la sécurité du portail captif ne doit jamais être surestimée.

Le protocole décrit dans le RFC 8910 et dans ce RFC a été testé lors de réunions IETF, sur le grand réseau de ces réunions. En dehors de cela, il n'y a pas encore de déploiement. Je crains que, vu la nullité technique de la plupart des points d'accès WiFi, vite installés, mal configurés et plus maintenus après, il ne faille attendre longtemps un déploiement significatif.


Téléchargez le RFC 8908


L'article seul

RFC 8906: A Common Operational Problem in DNS Servers: Failure To Communicate

Date de publication du RFC : Septembre 2020
Auteur(s) du RFC : M. Andrews (ISC), R. Bellis (ISC)
Réalisé dans le cadre du groupe de travail IETF dnsop
Première rédaction de cet article le 23 septembre 2020


Normalement, le protocole DNS est très simple : le client écrit au serveur, posant une question, et le serveur répond. Il peut répondre qu'il ne veut pas ou ne sait pas répondre, mais il le dit explicitement. Cela, c'est la théorie. En pratique, beaucoup de serveurs DNS bogués (ou, plus fréquemment, situés derrière une middlebox boguée) ne répondent pas du tout, laissant le client perplexe se demander s'il doit réeessayer différemment ou pas. Ce nouveau RFC documente le problème et ses conséquences. Il concerne surtout la question des serveurs faisant autorité.

(Cette absence de réponse est en général due, non pas aux serveurs DNS eux-même, qui sont la plupart du temps corrects de ce point de vue, mais plutôt à ces middleboxes codées avec les pieds et mal configurées, que certains managers s'obstinent à placer devant des serveurs DNS qui marcheraient parfaitement sans cela. Ainsi, on voit des pare-feux inutiles mis parce que « il faut un pare-feu, a dit l'auditeur » et qui bloquent tout ce qu'ils ne comprennent pas. Ainsi, par exemple, le pare-feu laissera passer les requêtes de type A mais pas celles de type NS, menant à une expiration du délai de garde. La section 4 de notre RFC détaille ces erreurs communes des middleboxes.)

Voici un exemple d'un service DNS bogué. Le domaine mabanque.bnpparibas est délégué à deux serveurs mal configurés (ou placés derrière une middlebox mal faite), sns6.bnpparibas.fr et sns5.bnpparibas.net :


% dig @sns6.bnpparibas.fr A mabanque.bnpparibas 

; <<>> DiG 9.11.5-P4-5.1-Debian <<>> @sns6.bnpparibas.fr A mabanque.bnpparibas
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 57381
;; flags: qr aa rd ad; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0
;; WARNING: recursion requested but not available

;; QUESTION SECTION:
;mabanque.bnpparibas.		IN	A

;; ANSWER SECTION:
mabanque.bnpparibas.	30	IN	A	159.50.187.79

;; Query time: 24 msec
;; SERVER: 159.50.105.65#53(159.50.105.65)
;; WHEN: Wed Jun 17 08:21:07 CEST 2020
;; MSG SIZE  rcvd: 53



% dig @sns6.bnpparibas.fr NS mabanque.bnpparibas 

; <<>> DiG 9.11.5-P4-5.1-Debian <<>> @sns6.bnpparibas.fr NS mabanque.bnpparibas
; (1 server found)
;; global options: +cmd
;; connection timed out; no servers could be reached

  

Face à un service aussi mal fait, le client DNS est très désarmé. Est-ce que le serveur a ignoré la requête ? Est-ce que le paquet a été perdu (c'est l'Internet, rien n'est garanti) ? Dans le deuxième cas, il faut réessayer, dans le premier, cela ne servirait à rien, qu'à perdre du temps, et à en faire perdre à l'utilisateur. Le client peut aussi supposer que l'absence de réponse est due à telle ou telle nouveauté du protocole DNS qu'il a utilisé, et se dire qu'il faudrait réessayer sans cette nouveauté, voire ne jamais l'utiliser. On voit que ces services grossiers, qui ne répondent pas aux requêtes, imposent un coût conséquent au reste de l'Internet, en délais, en trafic réseau, et en hésitation à déployer les nouvelles normes techniques.

Il n'y a aucune raison valable pour une absence de réponse ? Notre RFC en note une : une attaque par déni de service en cours. Dans ce cas, l'absence de réponse est légitime (et, de toute façon, le serveur peut ne pas avoir le choix). En dehors d'une telle attaque, le serveur doit répondre, en utilisant un des codes de retour DNS existants, qui couvrent tous les cas possibles (de NOERROR, quand tout va bien, à REFUSED, le refus délibéré, en passant par SERVFAIL, l'impossibilité de produire une réponse sensée). Ici, un cas où je demande à un serveur de .fr des informations sur un .com, qu'il n'a évidemment pas, d'où le refus explicite (cf. le champ status:) :


 % dig @d.nic.fr A www.microsofthub.com

; <<>> DiG 9.11.5-P4-5.1-Debian <<>> @d.nic.fr A www.microsofthub.com
; (2 servers found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: REFUSED, id: 4212
;; flags: qr rd; QUERY: 1, ANSWER: 0, AUTHORITY: 0, ADDITIONAL: 1
;; WARNING: recursion requested but not available

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 1432
; COOKIE: 452269774eb7b7c574c779de5ee9b8e6efe0f777274b2b17 (good)
;; QUESTION SECTION:
;www.microsofthub.com.		IN	A

;; Query time: 4 msec
;; SERVER: 2001:678:c::1#53(2001:678:c::1)
;; WHEN: Wed Jun 17 08:32:06 CEST 2020
;; MSG SIZE  rcvd: 77

  

Le RFC note qu'il n'y a pas que l'absence de réponse, il y a aussi parfois des réponses incorrectes. Ainsi, certains serveurs (ou, là encore, la middlebox placée devant eux) copient le bit AD de la requête dans la réponse au lieu de déterminer par eux-mêmes si ce bit - qui signifie Authentic Data - doit être mis dans la réponse.

Donc, ne pas répondre, c'est mal, et égoïste. Mais quelles sont les conséquences exactes de cette absence de réponse ? Parmi elles :

  • Pendant longtemps, les résolveurs DNS, face à une absence de réponse, réessayaient sans EDNS, ce qui résolvait parfois le problème, mais a sérieusement gêné le déploiement d'EDNS.
  • Même problème avec les nouvelles utilisations d'EDNS.
  • Un comportement fréquent des pare-feux est de bloquer les requêtes demandant des types de données qu'ils ne connaissent pas. La liste connue de ces pare-feux est toujours très réduite par rapport à la liste officielle, ce qui se traduit par une grande difficulté à déployer de nouveaux types de données DNS. C'est une des raisons derrière l'échec du type SPF (cf. RFC 6686). Cela encourage l'usage de types de données fourre-tout comme TXT.
  • Même problème pour le déploiement de DANE et de son type TLSA.
  • Le RFC ne cite pas ce cas, mais ne pas répondre peut aussi dans certains cas faciliter des attaques par empoisonnement d'un résolveur, cf. l'attaque SLIP.

Quels sont les cas où il est particulièrement fréquent qu'il n'y ait pas de réponse, ou bien une réponse erronée ? La section 3 en décrit un certain nombre (et le RFC vient avec un script qui utilise dig pour tester une grande partie de ces cas). Elle rappelle également les réponses correctes attendues. Par exemple, une requête de type SOA (Start Of Authority) à un serveur faisant autorité pour une zone doit renvoyer une réponse (contrairement aux serveurs de BNP Paribas cités plus haut, qui ne répondent pas). Même si le type de données demandé est inconnu du serveur, il doit répondre (probablement NOERROR s'il n'a tout simplement pas de données du type en question).

On voit parfois également des serveurs (ou plutôt des combinaisons serveur / middlebox boguée située devant le serveur) qui achoppent sur les requêtes utilisant des options (flags) DNS spécifiques. Ici, l'option Z fait que le serveur de la RATP ne répond plus :


% dig @193.104.162.15 +noedns +noad +norec +zflag soa ratp.fr  
; <<>> DiG 9.11.5-P4-5.1-Debian <<>> @193.104.162.15 +noedns +noad +norec +zflag soa ratp.fr
; (1 server found)
;; global options: +cmd
;; connection timed out; no servers could be reached

  

Alors qu'il marche parfaitement sans cette option :


  % dig @193.104.162.15 +noedns +noad +norec soa ratp.fr  

; <<>> DiG 9.11.5-P4-5.1-Debian <<>> @193.104.162.15 +noedns +noad +norec soa ratp.fr
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 30635
;; flags: qr aa; QUERY: 1, ANSWER: 1, AUTHORITY: 4, ADDITIONAL: 2

;; QUESTION SECTION:
;ratp.fr.			IN	SOA

;; ANSWER SECTION:
ratp.fr.		3600	IN	SOA	ns0.ratp.fr. hostmaster.ratp.fr. 2020051201 21600 3600 4204800 300
...
;; ADDITIONAL SECTION:
ns0.ratp.fr.		3600	IN	A	193.104.162.15
ns1.ratp.fr.		3600	IN	A	193.104.162.14

;; Query time: 6 msec
;; SERVER: 193.104.162.15#53(193.104.162.15)
;; WHEN: Mon May 18 17:42:33 CEST 2020
;; MSG SIZE  rcvd: 213

  

Autre exemple, l'option AD (Authentic Data) dans la requête, qui indique que le client espère une validation DNSSEC, déclenche parfois des bogues, comme de ne pas répondre ou bien de répondre en copiant aveuglément le bit AD dans la réponse. La section 6 du RFC détaille un cas similaire, celui des serveurs qui réutilisent une réponse déjà mémorisée, mais pour des options différentes dans la requête.

Et les opérations (opcodes) inconnus ? Des opérations comme NOTIFY ou UPDATE n'existaient pas au début du DNS et d'autres seront encore ajoutées dans le futur. Si le serveur ne connait pas une opération, il doit répondre NOTIMP (Not Implemented). Ici, avec l'opération 1 (IQUERY, ancienne mais abandonnée par le RFC 3425) :


% dig @d.nic.fr +opcode=1 toto.fr

; <<>> DiG 9.11.5-P4-5.1+deb10u1-Debian <<>> @d.nic.fr +opcode=1 toto.fr
; (2 servers found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: IQUERY, status: NOTIMP, id: 20180
;; flags: qr; QUERY: 0, ANSWER: 0, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 1432
; COOKIE: 71f9ced2f29076a65d34f3ed5ef2f718bab037034bb82106 (good)
;; Query time: 4 msec
;; SERVER: 2001:678:c::1#53(2001:678:c::1)
;; WHEN: Wed Jun 24 08:47:52 CEST 2020
;; MSG SIZE  rcvd: 51

  

Au contraire, voici un serveur qui répond incorrectement (REFUSED au lieu de NOTIMP) :


% dig  @ns3-205.azure-dns.org. +noedns +noad +opcode=15 +norec microsoft.com                        

; <<>> DiG 9.11.5-P4-5.1+deb10u1-Debian <<>> @ns3-205.azure-dns.org. +noedns +noad +opcode=15 +norec microsoft.com
; (2 servers found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: RESERVED15, status: REFUSED, id: 41518
;; flags: qr; QUERY: 1, ANSWER: 0, AUTHORITY: 0, ADDITIONAL: 0

;; QUESTION SECTION:
;microsoft.com.			IN	A

;; Query time: 22 msec
;; SERVER: 2a01:111:4000::cd#53(2a01:111:4000::cd)
;; WHEN: Wed Jun 24 09:16:19 CEST 2020
;; MSG SIZE  rcvd: 31

  

Soyons positifs : c'était bien pire il y a encore seulement cinq ou dix ans, malgré des tests techniques obligatoires dans certains registres comme Zonecheck pour .fr. Des efforts comme le DNS Flag Day ont permis d'arranger sérieusement les choses.

Bien sûr, les serveurs DNS doivent accepter les requêtes venant sur TCP et pas seulement sur UDP. Cela a toujours été le cas, mais le RFC 7766 rend cette exigence encore plus stricte. Il y a fort longtemps, l'outil de test Zonecheck de l'AFNIC testait ce fonctionnement sur TCP, et avait attrapé beaucoup d'erreurs de configuration, suscitant parfois des incompréhensions de la part d'administrateurs système ignorants qui prétendaient que le DNS n'utilisait pas TCP.

Et il y a bien sûr EDNS (RFC 6891). Introduit après la norme originale du DNS, mais quand même ancienne de plus de vingt ans, EDNS est toujours mal compris par certains programmeurs, et bien des middleboxes déconnent toujours sur des particularités d'EDNS. Déjà, un serveur doit répondre aux requêtes EDNS (même si lui-même ne connait pas EDNS, il doit au moins répondre FORMERR). Ensuite, s'il connait EDNS, il doit gérer les numéros de version d'EDNS (attention, la version actuelle est la 0, il n'y a pas encore eu de version 1 mais, pour qu'elle puisse être déployée un jour, il ne faut pas que les serveurs plantent sur les versions supérieures à zéro). EDNS permet d'indiquer des options (la liste complète est dans un registre IANA) et les options inconnues doivent être ignorées (autrement, on ne pourrait jamais déployer une nouvelle option).

Pour tester si vos serveurs gèrent correctement tous ces cas, le RFC (section 8) vient avec une série de commandes utilisant dig, dont il faudra analyser le résultat manuellement en suivant le RFC. J'en ai fait un script de test, check-dns-respond-rfc8906.sh. Si vous testez vos serveurs avec ce script, faites-le depuis l'extérieur (pour intégrer dans le test les éventuelles middleboxes, cf. section 4). Un exemple d'exécution du test :

%  ./check-dns-respond-rfc8906.sh cyberstructure.fr 2001:4b98:dc0:41:216:3eff:fe27:3d3f >& /tmp/mytest.txt

Et il vous reste à lire le fichier des résultats et à comparer avec les résultats attendus.


Téléchargez le RFC 8906


L'article seul

RFC 8905: The 'payto' URI scheme for payments

Date de publication du RFC : Octobre 2020
Auteur(s) du RFC : F. Dold (Taler Systems SA), C. Grothoff (BFH)
Pour information
Première rédaction de cet article le 26 octobre 2020


Le paiement de services ou de biens est un problème crucial sur l'Internet. En l'absence de mécanisme simple, léger et respectant la vie privée, on n'a aujourd'hui que des solutions centralisées dans des gros monstres, comme Amazon pour vendre des objets physiques, ou YouTube pour monétiser ses vidéos. Ce RFC ne propose pas de solution magique mais il spécifie au moins une syntaxe pour indiquer un mécanisme de paiement : le plan d'URI payto:, qui permettra peut-être un jour de faciliter les paiements.

Le paysage d'aujourd'hui est le suivant. Si vous êtes un créateur (d'articles, de vidéos, de dessins, peu importe) et que vous voulez être payé pour vos créations (ce qui est tout à fait légitime), vous n'avez comme solutions que de faire appel à du financement participatif (style Liberapay ou Ulule) ou bien de passer par une grosse plate-forme comme YouTube, qui imposera ses règles, comme la captation de données personnelles. Sans compter le contrôle du contenu, qui fait qu'une vidéo parlant de sujets sensibles sera démonétisée. (Voyez par exemple cette vidéo de Charlie Danger où, à partir de 10:45 et surtout 11:45, elle explique comment YouTube n'a pas aimé sa vidéo sur l'IVG et ce qui en est résulté.) Mais cette solution d'hébergement sur un GAFA est sans doute la plus simple pour le créateur, et elle ne dépend pas de la bonne volonté des lecteurs/spectacteurs. Lorsqu'on discute avec un·e vidéaste de son hébergement chez Google et qu'on lui propose d'utiliser plutôt un service libre et décentralisé fait avec PeerTube, la réponse la plus fréquente est « mais je perdrais la monétisation, et je ne peux pas vivre seulement d'amour et d'eau fraîche ». Je l'ai dit, il n'existe pas encore de solution parfaite à ce problème. Pour un cas plus modeste, celui de ce blog, j'ai tenté Flattr et Bitcoin mais avec très peu de succès.

Ce nouveau RFC ne propose pas une solution de paiement, juste un moyen de faire des URI qu'on pourra mettre dans ces pages Web pour pointer vers un mécanisme de paiement. Par exemple, payto://bitcoin/1HtNJ6ZFUc9yu9u2qAwB4tGdGwPQasQGax?amount=BITCOIN:0.01&message="Ce%20blog%20est%20super" va indiquer qu'il faut envoyer 10 milli-bitcoins à cette adresse (c'est la mienne), avec un gentil message. Si votre navigateur Web gère le plan d'URI payto: (ce qu'aucun ne fait à l'heure actuelle), que vous cliquez puis confirmez, je recevrai 0,01 bitcoin. (Notez qu'il existe une syntaxe spécifique à Bitcoin, pour un paiement, décrite dans le BIP 0021, et qui ressemble beaucoup à celle du RFC.)

Un peu de technique maintenant : les plans d'URI (scheme) sont définis dans le RFC 3986, section 3.1. Vous connaissez certainement des plans comme http: ou mailto:. Le plan payto: est désormais dans le registre IANA des plans. Après le plan et les deux barres se trouve l'autorité. Ici, elle indique le type de paiement. Notre RFC en décrit certains (comme bitcoin montré dans l'exemple) et on pourra en enregistrer d'autres. L'idée est de pouvoir présenter à l'utilisateur un mécanisme uniforme de paiement, quel que soit le type de paiement. (À l'heure actuelle, si vous acceptez les paiements par Flattr et PayPal, vous devez mettre deux boutons différents sur votre site Web, et qui déclencheront deux expériences utilisateur très différentes. Sans compter les traqueurs qu'il y a probablement derrière le bouton de Paypal.)

Question interface utilisateur, le RFC recommande que le navigateur permette ensuite à l'utilisateur de choisir le compte à utiliser, s'il en a plusieurs et, bien sûr, lui demande clairement une confirmation claire. Pas question qu'un simple clic déclenche le paiement ! (Cf. section 8 du RFC, qui pointe entre autres le risque de clickjacking.) Notez aussi que le RFC met en garde contre le fait d'envoyer trop d'informations (par exemple l'émetteur) dans le paiement, ce qui serait au détriment de la vie privée.

On peut ajouter des options à l'URI (section 5 du RFC). Par exemple la quantité à verser (amount, cf. l'exemple Bitcoin plus haut, le code de la monnaie, s'il a trois lettres, devant être conforme à ISO 4217, le Bitcoin n'ayant pas de code officiel, j'ai utilisé un nom plus long), receiver-name et sender-name, message (pour le destinataire) et instruction, ce dernier servant à préciser le traitement à faire par l'organisme de paiement.

Voici un autre exemple d'URI payto:, après Bitcoin, IBAN (ISO 20022) : payto://iban/DE75512108001245126199?amount=EUR:200.0&message=hello. L'exemple est tiré du RFC. Je n'ai pas mis mon vrai numéro IBAN car je ne suis pas expert en sécurité des IBAN et je ne sais pas s'il n'y a pas des inconvénients à le rendre public. Un expert pour confirmer ? À part ça, avec le type iban, l'option message correspond à ce que SEPA appelle unstructured remittance information et instruction au end to end identifier.

Puisqu'il y a une option permettant d'envoyer un message, la section 6 du RFC note qu'il n'y a pas de vrai mécanisme d'internationalisation, entre autres parce qu'on ne peut pas garantir de manière générale comment ce sera traité par les établissements de paiement.

Outre Bitcoin et IBAN, déjà vus, notre RFC enregistre plusieurs autres types de mécanismes de paiement. ACH, BIC/Swift et UPI appartiennent au monde bancaire classique. Le type ilp vient, lui, du monde Internet, comme Bitcoin, est pour les paiements via InterLedger, s'appuyant sur les adresses InterLedger. Et il y a aussi un type void, qui indique que le paiement se fera en dehors du Web, par exemple en liquide.

Cette liste n'est pas fermée, mais est stockée dans un registre évolutif, registre peuplé selon la politique « Premier arrivé, premier servi ». Vous noterez que ce registre n'est pas géré par l'IANA mais par GANA.

La section 7 indique les critères d'enregistrement souhaitables d'un nouveau type (les enregistrements existants peuvent servir d'exemple) : fournir des références précises, trouver un nom descriptif, préférer un nom neutre à celui d'un organisme particulier, etc. J'ai regardé ces critères pour le cas de PayPal, mécanisme de paiement très répandu sur l'Internet, mais ce n'était pas évident. Même si on spécifie des URI du genre payto://paypal/smith@example.com, il ne serait pas évident pour le navigateur de les traduire en une requête PayPal (PayPal privilégie l'installation de son propre bouton actif, ou bien le système PayPal.me). Bref, je n'ai pas continué mais si quelqu'un de plus courageux veut enregistrer le type paypal, il n'est pas nécessaire d'être représentant officiel de PayPal, et ce n'est pas trop difficile. Idem pour d'autres systèmes de paiement par encore spécifiés comme Liberapay. Notez que le système conçu par les auteurs du RFC, Taler, n'est pas encore enregistré non plus.


Téléchargez le RFC 8905


L'article seul

RFC 8904: DNS Whitelist (DNSWL) Email Authentication Method Extension

Date de publication du RFC : Septembre 2020
Auteur(s) du RFC : A. Vesely
Pour information
Première rédaction de cet article le 18 septembre 2020


Le RFC 8601 décrit un en-tête pour le courrier qui indique le résultat d'une tentative d'authentification. Cet en-tête Authentication-Results: permet plusieurs méthodes d'authentification, telles que SPF ou DKIM. Notre nouveau RFC 8904 ajoute une nouvelle méthode, dnswl (pour DNS White List), qui indique le résultat d'une lecture dans une liste blanche, ou liste d'autorisation via le DNS. Ainsi, si le client SMTP avait son adresse IP dans cette liste, un en-tête Authentication-Results: d'authentification positive sera ajouté.

Accéder via le DNS à des listes blanches (autorisation) ou des listes noires (rejet) de MTA est une pratique courante dans la gestion du courrier. Elle est décrite en détail dans le RFC 5782. Typiquement, le serveur SMTP qui voit une connexion entrante forme un nom de domaine à partir de l'adresse IP du client SMTP et fait une requête DNS pour ce nom et le type de données A (adresse IP). S'il obtient une réponse non-nulle, c'est que l'adresse IP figurait dans la liste (blanche ou noire). On peut aussi faire une requête de type TXT pour avoir du texte d'information. Ensuite, c'est au serveur SMTP de décider ce qu'il fait de l'information. La liste (noire ou blanche) lui donne une information, c'est ensuite sa responsabilité de décider s'il accepte ou rejette la connexion. La décision n'est pas forcément binaire, le serveur peut décider d'utiliser cette information comme entrée dans un algorithme de calcul de la confiance (« il est listé dans bl.example, 20 points en moins dans le calcul »).

Les plus connues des listes sont les listes noires (liste de clients SMTP mauvais) mais il existe aussi des listes blanches (liste de clients SMTP qu'on connait et à qui on fait confiance), et elles font l'objet de ce RFC. Il crée une nouvelle méthode pour l'en-tête Authentication-Results:, permettant ainsi d'enregistrer, dans un message, le fait qu'il ait été « authentifié » positivement via une liste blanche. On peut après se servir de cette « authentification » pour, par exemple, vérifier que le nom de domaine annoncé dans l'enregistrement TXT correspondant soit celui attendu (dans l'esprit de DMARCRFC 7489 - ce qui est d'ailleurs très casse-gueule mais c'est une autre histoire).

Et le RFC fait un rappel utile : se servir d'une liste (noire ou blanche) gérée à l'extérieur, c'est sous-traiter. Cela peut être pratique mais cela peut aussi avoir des conséquences néfastes si la liste est mal gérée (comme le sont la plupart des listes noires, adeptes du « on tire d'abord et on négocie après »). Comme le dit le RFC, « vous épousez la politique du gérant de la liste ». Lisez aussi le RFC 6471, au sujet de la maintenance de ces listes.

La nouvelle méthode d'authentification (dnswl, section 2 de notre RFC) figure désormais dans le registre IANA des méthodes d'authentification, spécifié dans le RFC 8601 (notamment section 2.7). Notre RFC 8904 décrit également les propriétés (RFC 8601, section 2.3 et RFC 7410) associées à cette authentification.

La méthode dnswl peut renvoyer pass (cf. le RFC 8601 pour ces valeurs renvoyées), qui indique que l'adresse IP du client est dans la liste blanche interrogée, none (client absent de la liste), ou bien une erreur (si la résolution DNS échoue). Contrairement à d'autres méthodes, il n'y a pas de résultat fail, ce qui est logique pour une liste blanche (qui liste les gentils et ne connait pas les méchants). Les principales propriétés possibles sont :

  • dns.zone : le nom de domaine de la liste blanche,
  • policy.ip : l'adresse IP renvoyée par la liste blanche, elle indique de manière codée les raisons pour lesquelles cette adresse est dans la liste,
  • policy.txt : l'enregistrement TXT optionnel, peut indiquer le nom de domaine associé à ce client SMTP,
  • dns.sec : indique si la requête DNS a été validée avec DNSSEC (yes si c'est le cas, no si la zone n'est pas signée, na si le résolveur ne sait pas valider).

Le type dns pour les propriétés est une nouveauté de ce RFC, désormais enregistrée à l'IANA.

La section 3 de notre RFC décrit l'enregistrement de type TXT qui donne des explications sur la raison pour laquelle l'adresse IP est dans la liste blanche (cf. RFC 5782, sections 2.1 et 2.2). Par exemple, il permet d'indiquer le domaine concerné (ADMD, ADministrative Management Domain, cf. RFC 8601 pour ce concept).

Tiré de l'annexe A du RFC, voici un exemple d'un message qui a reçu un Authentication-Results:, qui contient les quatre propriétés indiquées plus haut :

Authentication-Results: mta.example.org;
     dnswl=pass dns.zone=list.dnswl.example dns.sec=na
     policy.ip=127.0.10.1
     policy.txt="fwd.example https://dnswl.example/?d=fwd.example"   
  

Il se lit ainsi : le MTA mta.example.org estime son client authentifié par la méthode dnswl (DNS White List) de ce RFC. La liste blanche a renvoyé la valeur 127.0.10.1 (sa signification exacte dépend de la liste blanche) et le TXT associé disait que le client SMTP appartenait au domaine fwd.example.

Dans l'exemple plus détaillé du RFC, le message avait été retransmis d'une manière qui cassait SPF et n'aurait donc pas été accepté sans la liste blanche, qui certifie que fwd.example est un retransmetteur connu et légitime.

Enfin, la section 5 du RFC traite de sécurité. Notamment, elle insiste sur le fait que le DNS n'est pas, par défaut, protégé contre diverses manipulations et qu'il est donc recommandé d'utiliser DNSSEC (ce que ne fait pas la liste blanche d'exemple citée plus loin).

Voyons maintenant des exemples avec une liste blanche réelle, https://www.dnswl.org/. Prenons le serveur de messagerie de l'AFNIC, 2001:67c:2218:2::4:12. On inverse l'adresse (par exemple, avec ipv6calc -a 2001:67c:2218:2::4:12) et on fait une requête sous list.dnswl.org :


% dig A 2.1.0.0.4.0.0.0.0.0.0.0.0.0.0.0.2.0.0.0.8.1.2.2.c.7.6.0.1.0.0.2.list.dnswl.org
...
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 1634
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 8, ADDITIONAL: 11
...
;; ANSWER SECTION:
2.1.0.0.4.0.0.0.0.0.0.0.0.0.0.0.2.0.0.0.8.1.2.2.c.7.6.0.1.0.0.2.list.dnswl.org.	7588 IN	A 127.0.9.1
...
;; Query time: 0 msec
;; SERVER: 127.0.0.1#53(127.0.0.1)
;; WHEN: Sat Jul 04 09:16:37 CEST 2020
;; MSG SIZE  rcvd: 555

  

Que veut dire la valeur retournée, 127.0.9.1 ? On consulte la documentation de la liste et on voit que 9 veut dire Media and Tech companies (ce qui est exact) et 1 low trustworthiness. Il s'agit de la confiance dans le classement, pas dans le serveur. Pour le même serveur, la confiance est plus grande en IPv4 (3 au lieu de 1) :

    
% dig A 12.4.134.192.list.dnswl.org
...
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 266
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1
...
;; ANSWER SECTION:
12.4.134.192.list.dnswl.org. 10788 IN A	127.0.9.3
...
;; Query time: 0 msec
;; SERVER: 127.0.0.1#53(127.0.0.1)
;; WHEN: Sat Jun 20 12:31:57 CEST 2020
;; MSG SIZE  rcvd: 72

  

Et les enregistrements TXT ? Ici, il valent :

% dig TXT 2.1.0.0.4.0.0.0.0.0.0.0.0.0.0.0.2.0.0.0.8.1.2.2.c.7.6.0.1.0.0.2.list.dnswl.org
...
;; ANSWER SECTION:
2.1.0.0.4.0.0.0.0.0.0.0.0.0.0.0.2.0.0.0.8.1.2.2.c.7.6.0.1.0.0.2.list.dnswl.org.	8427 IN	TXT "nic.fr https://dnswl.org/s/?s=8580"
  

En visant l'URL indiquée, on peut avoir tous les détails que la liste blanche connait de ce serveur. (Je n'ai pas investigué en détail mais j'ai l'impression que certains serveurs faisant autorité pour le domaine dnswl.org renvoient des NXDOMAIN à tort, par exemple sur les ENT - Empty Non-Terminals - ce qui pose problème si votre résolveur utilise une QNAME minimisationRFC 7816 - stricte.)

Pour utiliser cette liste blanche depuis votre MTA favori, vous pouvez regarder la documentation de dnswl.org. par exemple, pour Courier, ce sera :

   -allow=list.dnswl.org 
  

(Tous les détails dans la documentation de Courier). Pour Postfix, voyez la section dédiée dans la documentation de dnswl.org. SpamAssassin, quant à lui, utilise dnswl.org par défaut.


Téléchargez le RFC 8904


L'article seul

RFC 8903: Use cases for DDoS Open Threat Signaling

Date de publication du RFC : Mai 2021
Auteur(s) du RFC : R. Dobbins (Arbor Networks), D. Migault (Ericsson), R. Moskowitz (HTT Consulting), N. Teague (Iron Mountain Data Centers), L. Xia (Huawei), K. Nishizuka (NTT Communications)
Pour information
Réalisé dans le cadre du groupe de travail IETF dots
Première rédaction de cet article le 31 mai 2021


Le travail autour de DOTS (DDoS Open Threat Signaling) vise à permettre la communication, pendant une attaque par déni de service, entre la victime et une organisation qui peut aider à atténuer l'attaque. Ce nouveau RFC décrit quelques scénarios d'utilisation de DOTS. Autrement, DOTS est normalisé dans les RFC 8811 et ses copains.

Si vous voulez un rappel du paysage des attaque par déni de service, et du rôle de DOTS (DDoS Open Threat Signaling) là-dedans, je vous recommande mon article sur le RFC 8612. Notre RFC 8903 ne fait que raconter les scénarios typiques qui motivent le projet DOTS.

Par exemple, pour commencer, un cas simple où le fournisseur d'accès Internet d'une organisation est également capable de fournir des solutions d'atténuation d'une attaque. Ce fournisseur (ITP dans le RFC, pour Internet Transit Provider) est sur le chemin de tous les paquets IP qui vont vers l'organisation victime, y compris ceux de l'attaque. Et il a déjà une relation contractuelle avec l'utilisateur. Ce fournisseur est donc bien placé, techniquement et juridiquement, pour atténuer une éventuelle attaque. Il peut déclencher le système d'atténuation, soit à la demande de son client qui subit une attaque, soit au contraire contre son client si celui-ci semble une source (peut-être involontaire) d'attaque. (Le RFC appelle le système d'atténuation DMS pour DDoS Mitigation Service ; certains opérateurs ont des noms plus amusants pour le DMS comme OVH qui parle d'« aspirateur ».)

Dans le cas d'une demande du client, victime d'une attaque et qui souhaite l'atténuer, le scénario typique sera qu'une machine chez le client (un pare-feu perfectionné, par exemple), établira une session DOTS avec le serveur DOTS inclus dans le DMS du fournisseur d'accès. Lorsqu'une attaque est détectée (cela peut être automatique, avec un seuil pré-défini, ou bien manuel), la machine du client demandera l'atténuation. Pendant l'atténuation, le client pourra obtenir des informations de la part du DMS du fournisseur. Si l'attaque se termine, le client pourra décider de demander l'arrêt de l'atténuation. Notez que la communication entre le client DOTS (chez la victime) et le serveur DOTS (chez le fournisseur d'accès) peut se faire sur le réseau « normal » (celui qui est attaqué) ou via un lien spécial, par exemple en 4G, ce qui sera plus cher et plus compliqué mais aura l'avantage de fonctionner même si l'attaque sature le lien normal.

Ce scénario simple peut aussi couvrir le cas du réseau domestique. On peut imaginer une box disposant de fonctions de détection d'attaques (peut-être en communiquant avec d'autres, comme le fait la Turris Omnia) et d'un client DOTS, pouvant activer automatiquement l'atténuation en contactant le serveur DOTS du FAI.

Le second cas envisagé est celui où le fournisseur d'accès n'a pas de service d'atténuation mais où le client (mettons qu'il s'agisse d'un hébergeur Web), craignant une attaque, a souscrit un contrat avec un atténuateur spécialisé (de nombreuses organisations ont aujourd'hui ce genre de services, y compris sans but lucratif). Dans ce cas, il faudra « détourner » le trafic vers cet atténuateur, avec des trucs DNS ou BGP, qui sont sans doute hors de portée de la petite ou moyenne organisation. L'accord avec l'atténuateur doit prévoir la technique à utiliser en cas d'attaque. Si c'est le DNS, la victime va changer ses enregistrements DNS pour pointer vers les adresses IP de l'atténuateur (attention au TTL). Si c'est BGP, ce sera à l'atténuateur de commencer à annoncer le préfixe du client (attention aux règles de sécurité BGP, pensez IRR et ROA), et au client d'arrêter son annonce. Le reste se passe comme dans le scénario précédent.


Téléchargez le RFC 8903


L'article seul

RFC 8901: Multi-Signer DNSSEC Models

Date de publication du RFC : Septembre 2020
Auteur(s) du RFC : S. Huque, P. Aras (Salesforce), J. Dickinson (Sinodun), J. Vcelak (NS1), D. Blacka (Verisign)
Pour information
Réalisé dans le cadre du groupe de travail IETF dnsop
Première rédaction de cet article le 25 septembre 2020


Aujourd'hui, il est courant de confier l'hébergement de ses serveurs DNS faisant autorité à un sous-traitant. Mais si vous avez, comme c'est recommandé, plusieurs sous-traitants et qu'en prime votre zone, comme c'est recommandé, est signée avec DNSSEC ? Comment s'assurer que tous les sous-traitants ont bien l'information nécessaire ? S'ils utilisent le protocole standard du DNS pour transférer la zone, tout va bien. Mais hélas beaucoup d'hébergeurs ne permettent pas l'utilisation de cette norme. Que faire dans ce cas ? Ce nouveau RFC explique les pistes menant à des solutions possibles.

Pourquoi est-ce que des hébergeurs DNS ne permettent pas d'utiliser la solution normalisée et correcte, les transferts de zone du RFC 5936 ? Il peut y avoir de mauvaises raisons (la volonté d'enfermer l'utilisateur dans un silo, en lui rendant plus difficile d'aller voir la concurrence) mais aussi des (plus ou moins) bonnes raisons :

  • Si l'hébergeur signe dynamiquement les données DNS,
  • Si l'hébergeur produit dynamiquement des données DNS (ce qui implique de les signer en ligne),
  • Si l'hébergeur utilise des trucs non-standards, par exemple pour mettre un CNAME à l'apex d'une zone.

Dans ces cas, le transfert de zones classique n'est pas une solution, puisqu'il ne permet pas de transmettre, par exemple, les instructions pour la génération dynamique de données.

Résultat, bien des titulaires de noms de domaine se limitent donc à un seul hébergeur, ce qui réduit la robustesse de leur zone face aux pannes… ou face aux conflits commerciaux avec leur hébergeur.

La section 2 de notre RFC décrit les modèles possibles pour avoir à la fois DNSSEC et plusieurs hébergeurs. Si aucune des trois raisons citées plus haut ne s'applique, le cas est simple : un hébergeur (qui peut être le titulaire lui-même, par exemple avec un serveur maître caché) signe la zone, et elle est transférée ensuite, avec clés et signatures, vers les autres. C'est tout simple. (Pour information, c'est par exemple ainsi que fonctionne .fr, qui a plusieurs hébergeurs en plus de l'AFNIC : les serveurs dont le nom comprend un « ext » sont sous-traités.)

Mais si une ou davantage des trois (plus ou moins bonnes) raisons citées plus haut s'applique ? Là, pas de AXFR (RFC 5936), il faut trouver d'autres modèles. (Et je vous préviens tout de suite : aucun de ces modèles n'est géré par les logiciels existants, il va falloir faire du devops.) Celui présenté dans le RFC est celui des signeurs multiples. Chaque hébergeur reçoit les données non-signées par un mécanime quelconque (par exemple son API) et les signe lui-même avec ses clés, plus exactement avec sa ZSK (Zone Signing Key). Pour que tous les résolveurs validants puissent valider ces signatures, il faut que l'ensemble des clés, le DNSKEY de la zone, inclus les ZSK de tous les hébergeurs de la zone. (Un résolveur peut obtenir les signatures d'un hébergeur et les clés d'un autre.) Comment faire en sorte que tous les hébergeurs reçoivent les clés de tous les autres, pour les inclure dans le DNSKEY qu'ils servent ? Il n'existe pas de protocole pour cela, et le problème n'est pas purement technique, il est également que ces hébergeurs n'ont pas de relation entre eux. C'est donc au titulaire de la zone d'assurer cette synchronisation. (Un peu de Python ou de Perl avec les API des hébergeurs…)

Il y a deux variantes de ce modèle : KSK unique (Key Signing Key) et partagée, ou bien KSK différente par hébergeur. Voyons d'abord la première variante, avec une seule KSK. Elle est typiquement gérée par le titulaire de la zone, qui est responsable de la gestion de la clé privée, et d'envoyer l'enregistrement DS à la zone parente. Par contre, chaque hébergeur a sa ZSK et signe les données. Le titulaire récupère ces ZSK (par exemple via une API de l'hébergeur) et crée l'ensemble DNSKEY, qu'il signe. (Le RFC note qu'il y a un cas particulier intéressant de ce modèle, celui où il n'y a qu'un seul hébergeur, par exemple parce que le titulaire veut garder le contrôle de la KSK mais pas gérer les serveurs de noms.)

Deuxième variante du modèle : chaque hébergeur a sa KSK (et sa ZSK). Il ouvre son API aux autres hébergeurs pour que chacun connaisse les ZSK des autres et puisse les inclure dans l'ensemble DNSKEY. Le titulaire doit récolter toutes les KSK, pour envoyer les DS correspondants à la zone parente. Le remplacement d'une KSK (rollover) nécessite une bonne coordination.

Dans ces deux modèles, chaque serveur faisant autorité connait toutes les ZSK utilisées, et un résolveur validant récupérera forcément la clé permettant la validation, quel que soit le serveur faisant autorité interrogé, et quelle que soit la signature que le résolveur avait obtenue (section 3 du RFC). À noter qu'il faut que tous les hébergeurs utilisent le même algorithme de signature (section 4 du RFC) puisque la section 2.2 du RFC 4035 impose de signer avec tous les algorithmes présents dans l'ensemble DNSKEY.

En revanche, les hébergeurs ne sont pas forcés d'utiliser le même mécanisme de déni d'existence (NSEC ou NSEC3, cf. RFC 7129). Chacun peut faire comme il veut (section 5 de notre RFC) puisqu'une réponse négative est toujours accompagnée de ses NSEC (ou NSEC3). Évidemment, si un hébergeur utilise NSEC et un autre NSEC3, le gain en sécurité de NSEC3 sera inutile.

Comme signalé plus haut, le remplacement (rollover) des clés, déjà une opération compliquée en temps normal, va être délicat. La section 6 du RFC détaille les procédures que l'on peut suivre dans les cas « une seule KSK » et « une KSK par hébergeur ».

La discussion plus haut supposait qu'il y avait deux clés, la KSK et la ZSK, ce qui est le mode le plus fréquent d'organisation des clés. Mais ce n'est pas obligatoire : on peut avec n'avoir qu'une seule clé (baptisée alors CSK, pour Common Signing Key). Dans ce cas, il n'y a qu'un seul modèle possible, chaque hébergeur a sa CSK. Cela ressemble au modèle « une KSK par hébergeur » : le titulaire de la zone doit récolter toutes les CSK et envoyer les DS correspondants à la zone parente (section 7 du RFC).

Cette configuration DNSSEC à plusieurs signeurs peut fonctionner avec les enregistrements CDS et CDNSKEY des RFC 7344 et RFC 8078 (section 8 de notre RFC). Dans le premier modèle « une seule KSK », le titulaire de la zone crée CDS et/ou CDNSKEY qu'il envoie à tous les hébergeurs avec les autres données. Dans le deuxième modèle « une KSK par hébergeur », il faut échanger les CDS et/ou CDNSKEY pour que chaque hébergeur ait les enregistrements des autres.

On voit que dans cette configuration, il est nécessaire de communiquer, au moins entre le titulaire de la zone et ses hébergeurs. Ces hébergeurs doivent fournir une API, typiquement de type REST. Notre RFC ne normalise pas une telle API mais sa section 9 indique les fonctions minimum qu'elle doit fournir :

  • Permettre de récupérer les enregistrements DNSKEY (pour obtenir la ZSK),
  • Pour le premier modèle (une seule KSK), permettre d'indiquer l'ensemble DNSKEY complet et signé,
  • Pour le deuxième modèle, permettre d'indiquer les ZSK des autres hébergeurs,
  • Et idem pour les CDS et CDNSKEY.

Autre problème pratique avec la co-existence de plusieurs hébergeurs DNS, qui signent eux-mêmes la zone, la taille des réponses. Comme il faut inclure toutes les ZSK dans l'ensemble DNSKEY, les réponses aux requêtes DNSKEY vont forcément être d'assez grande taille (section 10). Le RFC fait remarquer que les algorithmes cryptographiques utilisant les courbes elliptiques ont des clés plus courtes et que cela peut aider.

Enfin, dans la section 12, dédiée aux questions de sécurité restantes, le RFC note que ces mécanismes à plusieurs signeurs nécessitent d'avoir confiance dans chacun des signeurs. Au contraire, le système où le maître signe tout et transfère ensuite aux serveurs esclaves ne demande aucune confiance dans les hébergeurs.

Allez, un joli dessin pour terminer (trouvé ici) : dnssec-multi-provider.jpg.


Téléchargez le RFC 8901


L'article seul

RFC 8900: IP Fragmentation Considered Fragile

Date de publication du RFC : Septembre 2020
Auteur(s) du RFC : R. Bonica (Juniper Networks), F. Baker, G. Huston (APNIC), R. Hinden (Check Point Software), O. Troan (Cisco), F. Gont (SI6 Networks)
Réalisé dans le cadre du groupe de travail IETF intarea
Première rédaction de cet article le 12 septembre 2020


Un concept important d'IP est la fragmentation, le découpage d'un paquet trop gros en plusieurs fragments, chacun acheminé dans un datagramme différent. Excellente idée sur le papier, la fragmentation est handicapée, dans l'Internet actuel, par les nombreuses erreurs de configuration de diverses middleboxes. Ce nouveau RFC constate la triste réalité : en pratique, la fragmentation marche mal, et les machines qui émettent les paquets devraient essayer de faire en sorte qu'elle ne soit pas utilisée.

Rappelons d'abord ce qu'est la fragmentation, dans IP. Tout lien entre deux machines a une MTU, la taille maximale des datagrammes qui peuvent passer. C'est par exemple 1 500 octets pour l'Ethernet classique. Si le paquet IP est plus grand, il faudra le fragmenter, c'est-à-dire le découper en plusieurs fragments, chacun de taille inférieure à la MTU. En IPv4, n'importe quel routeur sur le trajet peut fragmenter un paquet (sauf si le bit DF - Don't Fragment - est mis à un dans l'en-tête IP), en IPv6, seule la machine émettrice peut fragmenter (tout ce passe comme si DF était systématiquement présent).

Ces fragments seront ensuite réassemblés en un paquet à la destination. Chacun étant transporté dans un datagramme IP différent, ils auront pu arriver dans le désordre, certains fragments ont même pu être perdus, le réassemblage est donc une opération non-triviale. Historiquement, certaines bogues dans le code de réassemblage ont même pu mener à des failles de sécurité.

Une légende urbaine s'est constituée petit à petit, racontant que les fragments, en eux-mêmes, posaient un problème de sécurité. C'est faux, mais ce genre de légendes a la vie dure, et a mené un certain nombre d'administrateurs de pare-feux à bloquer les fragments. Le RFC 7872 faisait déjà le constat que les fragments, souvent, n'arrivaient pas à destination. Outre ce RFC, on peut citer une étude très ancienne, qui montre que le problème ne date pas d'aujourd'hui, « "Fragmentation Considered Harmful (SIGCOMM '87 Workshop on Frontiers in Computer Communications Technology) ou, pour le cas spécifique du DNS, la plus récente « IPv6, Large UDP Packets and the DNS ».

La section 2 de notre RFC explique en grand détail la fragmentation IP. Notez par exemple qu'IPv4 impose (RFC 791, section 3.2) une MTU minimale de 68 octets mais, en pratique, on a toujours au moins 576 octets (ou, sinon, une autre fragmentation/réassemblage, dans la couche 2). IPv6, lui, impose (RFC 8200), 1 280 octets. Il a même été suggéré de ne jamais envoyer de paquets plus grands, pour éviter la fragmentation. Un Ethernet typique offre une MTU de 1 500 octets, mais elle peut être réduite par la suite en cas d'utilisation de tunnels.

Chaque lien a sa MTU mais ce qui est important, pour le paquet, c'est la MTU du chemin (Path MTU), c'est-à-dire la plus petite des MTU rencontrées sur le chemin entre le départ et l'arrivée. C'est cette MTU du chemin qui déterminera si on fragmentera ou pas. (Attention, le routage étant dynamique, cette MTU du chemin peut changer dans le temps.) Il est donc intéressant, pour la machine émettrice d'un paquet, de déterminer cette MTU du chemin. Cela peut se faire en envoyant des paquets avec le bit DF (Don't Fragment, qui est implicite en IPv6) et en regardant les paquets ICMP renvoyés par les routeurs, indiquant que la MTU est trop faible pour ce paquet (et, depuis le RFC 1191, le message ICMP indique la MTU du lien suivant, ce qui évite de la deviner par essais/erreurs successifs.) Cette procédure est décrite dans les RFC 1191 et RFC 8201. Mais (et c'est un gros mais), cela suppose que les erreurs ICMP « Packet Too Big » arrivent bien à l'émetteur et, hélas, beaucoup de pare-feux configurés par des ignorants bloquent ces messages ICMP. D'autre part, les messages ICMP ne sont pas authentifiés (RFC 5927) et un attaquant peut générer de fausses erreurs ICMP pour faire croire à une diminution de la MTU du chemin, affectant indirectement les performances. (Une MTU plus faible implique des paquets plus petits, donc davantage de paquets.)

Quand une machine fragmente un paquet (en IPv4, cette machine peut être l'émetteur ou un routeur intermédiaire, en IPv6, c'est forcément l'émetteur), elle crée plusieurs fragments, dont seul le premier porte les informations des couches supérieures, comme le fait qu'on utilise UDP ou TCP, ou bien le numéro de port. La description détaillée figure dans le RFC 791 pour IPv4 et dans le RFC 8200 (notamment la section 4.5) pour IPv6.

Voici un exemple où la fragmentation a eu lieu, vu par tcpdump (vous pouvez récupérer le pcap complet en dns-frag-md.pcap). La machine 2605:4500:2:245b::bad:dcaf a fait une requête DNS à 2607:5300:201:3100::2f69, qui est un des serveurs faisant autorité pour le TLD .md. La réponse a dû être fragmentée en deux :

% tcpdump -e -n -r dns-frag-md.pcap                         
16:53:07.968917 length 105: 2605:4500:2:245b::bad:dcaf.44104 > 2607:5300:201:3100::2f69.53: 65002+ [1au] ANY? md. (43)
16:53:07.994555 length 1510: 2607:5300:201:3100::2f69 > 2605:4500:2:245b::bad:dcaf: frag (0|1448) 53 > 44104: 65002*- 15/0/16 SOA, RRSIG, Type51, RRSIG, RRSIG, DNSKEY, DNSKEY, RRSIG, NS dns-md.rotld.ro., NS nsfr.dns.md., NS nsb.dns.md., NS ns-ext.isc.org., NS nsca.dns.md., NS ns-int.dns.md., RRSIG (1440)
16:53:07.994585 length 321: 2607:5300:201:3100::2f69 > 2605:4500:2:245b::bad:dcaf: frag (1448|259)
  

Le premier paquet est la requête, le second est le premier fragment de la réponse (qui va des octets 0 à 1447), le troisième paquet est le second fragment de cette même réponse (octets 1448 à la fin). Regardez les longueur des paquets IP, et le fait que seul le premier fragment, qui porte l'en-tête UDP, a pu être interprété comme étant du DNS.

Notez que certaines versions de traceroute ont une option qui permet d'afficher la MTU du lien (par exemple traceroute -n --mtu IP-ADDRESS.)

Les couches supérieures (par exemple UDP ou TCP) peuvent ignorer ces questions et juste envoyer leurs paquets, comptant qu'IP fera son travail, ou bien elles peuvent tenir compte de la MTU du chemin, par exemple pour optimiser le débit, en n'envoyant que des paquets assez petits pour ne pas être fragmentés. Cela veut dire qu'elles ont accès au mécanisme de découverte de la MTU du chemin, ou bien qu'elles font leur propre découverte, via la procédure PLPMTUD (Packetization Layer Path MTU Discovery) du RFC 4821 (qui a l'avantage de ne pas dépendre de la bonne réception des paquets ICMP).

Bon, maintenant qu'on a vu la framentation, voyons les difficultés qui surviennent dans l'Internet d'aujourd'hui (section 3 du RFC). D'abord, chez les pare-feux et, d'une manière générale, tous les équipements intermédiaires qui prennent des décisions en fonction de critères au-dessus de la couche 3, par exemple un répartiteur de charge, ou un routeur qui enverrait les paquets à destination du port 443 vers un autre chemin que le reste des paquets. Ces décisions dépendent d'informations qui ne sont que dans le premier fragment d'un datagramme fragmenté. Décider du sort des fragments suivants n'est pas évident, surtout si on veut le faire sans maintenir d'état. Un pare-feu sans état peut toujours essayer d'accepter tous les fragments ultérieurs (ce qui pourrait potentiellement autoriser certaines attaques) ou bien tous les bloquer (ce qui arrêterait du trafic légitime fragmenté). Et les pare-feux avec état ne sont pas une solution idéale, puisque stocker et maintenir cet état est un gros travail, qui plante souvent, notamment en cas d'attaque par déni de service.

Certains types de NAT ont également des problèmes avec la fragmentation. Ainsi, les techniques A+P (RFC 6346) et CGN (RFC 6888) nécessitent toutes les deux que les fragments soient réassemblés en un seul paquet, avant de traduire.

Qui dit fragmentation dit réassemblage à un moment. C'est une opération délicate, et plusieurs programmeurs se sont déjà plantés en réassemblant sans prendre de précautions. Mais il y a aussi un problème de performance. Et il y a les limites d'IPv4. L'identificateur d'un fragment ne fait que 16 bits et cela peut mener rapidement à des réutilisations de cet identificateur, et donc à des réassemblages incorrects (les sommes de contrôle de TCP et UDP ne sont pas toujours suffisantes pour détecter ces erreurs, cf. RFC 4693). IPv6, heureusement, n'a pas ce problème, l'identificateur de fragment faisant 32 bits.

On l'a dit plus haut, la fragmentation, et surtout le réassemblage, ont une longue histoire de failles de sécurité liées à une lecture trop rapide du RFC par le programmeur qui a écrit le code de réassemblage. Ainsi, les fragments recouvrants sont un grand classique, décrits dans les RFC 1858, RFC 3128 et RFC 5722. Normalement, le récepteur doit être paranoïaque, et ne pas faire une confiance aveugle aux décalages (offset) indiqués dans les paquets entrants, mais tous les programmeurs ne sont pas prudents. Il y a aussi le risque d'épuisement des ressources, puisque le récepteur doit garder en mémoire (le réassemblage implique le maintien d'un état) les fragments pas encore réassemblés. Un attaquant peut donc épuiser la mémoire en envoyant des fragments d'un datagramme qui ne sera jamais complet. Et il y a des identificateurs de fragment non-aléatoires, qui permettent d'autres attaques, documentées dans le RFC 7739 ou dans des articles comme « Fragmentation Considered Poisonous de Herzberg et Shulman (cf. aussi mon résumé). Enfin, la fragmentation peut aider à échapper au regard des IDS (cf. « Insertion, Evasion and Denial of Service: Eluding Network Intrusion Detection »).

Un autre problème très fréquent avec la fragmentation est causé par la configuration erronée de pare-feux. Souvent, des administrateurs réseau incompétents bloquent les messages ICMP Packet Too Big, nécessaires pour la découverte de la MTU du chemin. C'est de leur part une grosse erreur (expliquée dans le RFC 4890) mais cela arrive trop souvent. Résultat, si la MTU du chemin est inférieure à la MTU du premier lien, la machine émettrice envoie des paquets trop gros, et ne sait pas que ces paquets n'ont pas pu passer. On a donc créé un trou noir (les paquets disparaissent sans laisser de trace).

Ce bloquage injustifié des messages ICMP peut également être dû à des causes plus subtiles. Par exemple, si un pare-feu laisse sortir tous les paquets mais, en entrée, n'autorise que les paquets dont l'adresse IP source a été utilisée comme destination récemment, alors, les erreurs ICMP, émises par des routeurs intermédiaires et ayant donc une adresse IP source jamais vue, seront jetées. Notre RFC note que cette bogue dans les pare-feux est apparemment assez fréquente dans les boxes.

Toujours côté mauvaise configuration, le RFC cite aussi le problème des routeurs qui jettent les paquets ayant options ou extensions qu'ils ne connaissent pas, ce qui peut inclure les fragments. L'analyse du RFC 7872, ou celle dans l'article de Huston « IPv6, Large UDP Packets and the DNS », montre bien que ce problème est fréquent, trop fréquent. Ainsi, même si la découverte de la MTU du chemin se passe bien, les fragments n'arriveront pas à destination. Pourquoi cette mauvaise configuration ? C'est évidemment difficile à dire, cela peut aller de logiciels bogués jusqu'à un choix délibéré d'un administrateur réseau ignorant qui a vaguement entendu une légende urbaine du genre « les fragments sont un risque de sécurité ».

Dans les cas précédents, la perte du message ICMP Packet Too Big était clairement de la faute d'un humain, l'administrateur du pare-feu. Mais il peut y avoir des obstacles plus fondamentaux au bon fonctionnement de la découverte de la MTU du chemin. Par exemple, si un serveur DNS anycasté envoie un paquet trop gros, et qu'un routeur intermédiaire envoie le message Packet Too Big, ledit message ira vers l'adresse anycast du serveur, et atterrira peut-être vers une autre instance du serveur DNS, si le routeur qui a signalé le problème n'est pas dans le même bassin d'attraction que le client original. Le message ICMP ne sera donc pas reçu par l'instance qui aurait eu besoin de l'information. Le problème est d'autant plus génant que le DNS est le plus gros utilisateur d'UDP, et est donc particulièrement sensible aux problèmes de fragmentation (TCP gère mieux ces problèmes, avec la négociation de MSS).

Une variante du problème anycast survient lorsque le routage est unidirectionnel. Si l'émetteur n'est pas joignable, il ne recevra pas les messages ICMP. (Le cas est cité par le RFC mais me semble peu convaincant ; il y a peu de protocoles où l'émetteur peut se passer de recevoir des réponses. Et beaucoup de routeurs jettent les paquets pour lesquels ils n'ont pas de voie de retour, cf. RFC 3704.)

Maintenant qu'on a présenté en détail les problèmes liés à la fragmentation IP dans l'Internet actuel, quelles sont les approches possibles pour traiter ce problème ? La section 4 du RFC les présente. D'abord, les solutions situées dans la couche Transport. Comme indiqué plus haut, TCP peut éviter la fragmentation en découpant les données en segments dont chacun a une taille inférieure à la MTU du chemin (paramètre MSS, Maximum Segment Size). Cela suppose que la MSS soit réglée à une telle valeur (cf. mon article). Cela peut être manuel (si on sait qu'on va toujours passer par un tunnel avec faible MTU, on peut toujours configurer sa machine pour réduire la MSS), cela peut utiliser la procédure classique de découverte de la MTU du chemin ou bien cela peut utiliser la découverte de MTU sans ICMP du RFC 4821. D'autres protocoles que TCP peuvent fonctionner ainsi, comme DCCP (RFC 4340) ou SCTP (RFC 4960). À noter qu'UDP, lui, n'a pas de tel mécanisme, même si des travaux sont en cours pour cela.

Pour TCP, la méthode manuelle se nomme TCP clamping et peut se faire, par exemple avec Netfilter en mettant sur le routeur :

%  iptables -t mangle -A FORWARD -p tcp -m tcp --tcp-flags SYN,RST SYN \
          -j TCPMSS --clamp-mss-to-pmtu
  

La méthode avec ICMP, on l'a vu, est fragile car les messages ICMP peuvent être bloqués. La méthode sans ICMP consiste pour TCP, en cas de détection de perte de paquets, à envoyer des paquets plus petits pour essayer de s'ajuster à la MTU du chemin. (Bien sûr, des paquets peuvent se perdre pour d'autres raisons que la MTU trop basse, et la mise en œuvre de cette technique est donc délicate.)

Autre solution, plutôt que d'impliquer la couche Transport, faire appel à la couche Application. Le RFC 8085 conseille aux applications qui font de l'UDP d'essayer d'éviter la fragmentation. Par exemple, si vous gérez un serveur DNS avec NSD, cela peut se faire en mettant dans le fichier de configuration :

   ipv4-edns-size: 1432
   ipv6-edns-size: 1432
  

Vous pouvez voir le résultat sur, par exemple, un des serveurs faisant autorité pour .bostik :

    
% dig +ignore @d.nic.fr DNSKEY bostik

; <<>> DiG 9.11.5-P4-5.1-Debian <<>> +ignore @d.nic.fr DNSKEY bostik
; (2 servers found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 12029
;; flags: qr aa tc rd; QUERY: 1, ANSWER: 0, AUTHORITY: 0, ADDITIONAL: 1
;; WARNING: recursion requested but not available

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags: do; udp: 1432
; COOKIE: ef8669d08e2d9b26bb1a8ab85e21830a8931f9ab23403def (good)
;; QUESTION SECTION:
;bostik.			IN DNSKEY

;; Query time: 2 msec
;; SERVER: 2001:678:c::1#53(2001:678:c::1)
;; WHEN: Fri Jan 17 10:48:58 CET 2020
;; MSG SIZE  rcvd: 63

  

Vous voyez que le serveur n'envoie pas de réponses de taille supérieure à 1 432 octets (la OPT PSEUDOSECTION). Au moment du test, la réponse faisait 1 461 octets, d'où le flag tc (TRuncated). Normalement, un client DNS, voyant que la réponse a été tronquée, réessaie en TCP (j'ai mis l'option +ignore à dig pour empêcher cela et illustrer le fonctionnement du DNS.)

En parlant du DNS, la section 5 du RFC liste des applications pour lesquelles la fragmentation joue un rôle important :

  • Le DNS utilise encore largement UDP (malgré le RFC 7766) et, notamment avec DNSSEC, les réponses peuvent nettement excéder la MTU typique. C'est en général le premier protocole qui souffre, quand la fragmentation ne marche pas, d'autant plus que la grande majorité des échanges sur l'Internet commence par des requêtes DNS. Le client DNS peut limiter la taille des données envoyées, via EDNS (RFC 6891) et le serveur peut également avoir sa propre limite. Le serveur qui n'a pas la place de tout mettre dans la réponse peut, dans certains cas, limiter les données envoyées (en omettant les adresses de colle, par exemple) et si ça ne suffit pas, indiquer que la réponse est tronquée, ce qui doit mener le client à réessayer avec TCP (que tout le monde devrait gérer mais certains clients et certains serveurs, ou plutôt leurs réseaux, ont des problèmes avec TCP, comme illustré dans « Measuring ATR »). L'importance de la fragmentation pour le DNS fait que le DNS Flag Day de 2020 est consacré à ce sujet.
  • Le protocole de routage OSPF utilise également UDP mais le problème est moins grave, car c'est un protocole interne, pas utilisé sur l'Internet public, l'administrateur réseau peut donc en général s'assurer que la fragmentation se passera bien. Et, de toute façon, la plupart des mises en œuvre d'OSPF limitent la taille de leurs messages pour être sûr de rester en dessous de la MTU.
  • L'encapsulation des paquets IP (pas vraiment la couche Application mais quand même un usage répandu) peut également créer des problèmes avec la fragmentation (cf. RFC 4459.) Cela concerne des protocoles comme IP-in-IP (RFC 2003), GRE (RFC 2784 et RFC 8086), et d'autres. Le RFC 7588 décrit une stratégie générale pour ces protocoles.
  • Le RFC note que certains protocoles, pour des raisons de performance, assument tout à fait d'envoyer de très grands paquets et donc de compter sur une fragmentation qui marche. Ce sont notamment des protocoles qui sont conçus pour des milieux très particuliers, comme LTP (RFC 5326) qui doit fonctionner en présence d'énormes latences.

Compte-tenu de tout ceci, quelles recommandations concrètes donner ? Cela dépend évidemment du public cible. La section 6 de notre RFC donne des conseils pour les concepteurs et conceptrices de protocoles et pour les différents types de développeurs et développeuses qui vont programmer des parties différentes du système. D'abord, les protocoles, sujet principal pour l'IETF. Compte-tenu des importants problèmes pratiques que pose la fragmentation dans l'Internet actuel, le RFC prend une décision douloureuse : plutôt que de chercher à réparer l'Internet, on jette l'éponge et on ne conçoit plus de protocoles qui dépendent de la fragmentation. De tels protocoles ne seraient raisonnables que dans des environnements fermés et contrôlés. Comme souvent à l'IETF, le choix était difficile car il faut choisir entre les principes (la fragmentation fait partie d'IP, les composants de l'Internet ne doivent pas l'empêcher) et la réalité d'un monde de middleboxes mal programmées et mal gérées. Comme pour la décision de faire passer beaucoup de nouveaux protocoles sur HTTPS, le choix ici a été de prendre acte de l'ossification de l'Internet, et de s'y résigner.

Pour ne pas dépendre de la fragmentation, les nouveaux protocoles peuvent utiliser une MTU suffisamment petite pour passer partout, ou bien utiliser un système de découverte de la MTU du chemin suffisamment fiable, comme celui du RFC 4821. Pour UDP, le RFC renvoie aux recommandations de la section 3.2 du RFC 8085.

Ensuite, les conseils aux programmeurs et programmeuses. D'abord, dans les systèmes d'exploitation. Le RFC demande que la PLPMTUD (RFC 8899) soit disponible dans les bibliothèques proposées aux applications.

Ensuite, pour ceux et celles qui programment les middleboxes, le RFC rappelle quand même qu'elles doivent respecter les RFC sur IPv4 (RFC 791) et IPv6 (RFC 8200). Pour beaucoup de fonctions assurées par ces boitiers (comme le filtrage), cela implique de réassembler les paquets fragmentés, et donc de maintenir un état. Les systèmes avec état sont plus compliqués et plus chers (il faut davantage de mémoire) ce qui motive parfois à préférer les systèmes sans état. Ceux-là n'ont que deux choix pour gérer la fragmentation : violer les RFC ou bien jeter tous les fragments. Évidemment, aucune de ces deux options n'est acceptable. Le RFC demande qu'au minimum, si on massacre le protocole IP, cela soit documenté. (Ces middleboxes sont souvent traitées comme des boites noires, installées sans les comprendre et sans pouvoir déboguer les conséquences.)

Enfin, les opérateurs des réseaux doivent s'assurer que la PMTUD fonctionne, donc émettre des Packet Too Big si le paquet est plus gros que la MTU, et ne pas bloquer les paquets ICMP. Notre RFC permet toutefois de limiter leur rythme (RFC 1812 et RFC 4443). En tout cas, comme le rappelle le RFC 4890, filtrer les paquets ICMP Packet Too Big est mal !

De la même façon, le RFC rappelle aux opérateurs réseau qu'on ne doit pas filtrer les fragments. Ils sont utiles et légitimes.

Notez que les recommandations de ce RFC peuvent sembler contradictoires : on reconnait que la fragmentation marche mal et qu'il ne faut donc pas compter dessus mais, en même temps, on rappelle qu'il faut se battre pour qu'elle marche mieux. En fait, il n'y a pas de contradiction, juste du réalisme. L'Internet n'ayant pas de Chef Suprême qui coordonne tout, on ne peut pas espérer qu'une recommandation de l'IETF soit déployée immédiatement partout. On se bat donc pour améliorer les choses (ne pas bloquer les fragments, ne pas bloquer les messages ICMP Packet Too Big) tout en étant conscient que ça ne marchera pas à 100 % et que les administrateurs système et réseau doivent en être conscients.


Téléchargez le RFC 8900


L'article seul

RFC 8899: Packetization Layer Path MTU Discovery for Datagram Transports

Date de publication du RFC : Septembre 2020
Auteur(s) du RFC : G. Fairhurst, T. Jones (University of Aberdeen), M. Tuexen, I. Ruengeler, T. Voelker (Muenster University of Applied Sciences)
Chemin des normes
Réalisé dans le cadre du groupe de travail IETF tsvwg
Première rédaction de cet article le 12 septembre 2020


Ce RFC qui vient d'être publié décrit une méthode pour découvrir la MTU d'un chemin sur l'Internet (Path MTU, la découverte de la MTU du chemin se nommant PMTUD pour Path MTU Discovery). Il existe d'autres méthodes pour cette découverte, comme celle des RFC 1191 et RFC 8201, qui utilisent ICMP. Ici, la méthode ne dépend pas d'ICMP, et se fait avec les datagrammes normaux de la couche 4 (ou PL, pour Packetization Layer). Elle étend la technique du RFC 4821 à d'autres protocoles de transport que TCP (et SCTP).

Mais, d'abord, pourquoi est-ce important de découvrir la MTU maximale du chemin ? Parce que, si on émet des paquets qui sont plus gros que cette MTU du chemin, ces paquets seront jetés, au grand dam de la communication. En théorie, le routeur qui prend cette décision devrait fragmenter le paquet (IPv4 seulement), ou bien envoyer à l'expéditeur un message ICMP Packet Too Big mais ces fragments, et ces messages ICMP sont souvent bloqués par de stupides pare-feux mal gérés (ce qu'on nomme un trou noir). On ne peut donc pas compter dessus, et ce problème est connu depuis longtemps (RFC 2923, il y a vingt ans, ce qui est une durée insuffisante pour que les administrateurs de pare-feux apprennent leur métier, malgré le RFC 4890). La seule solution fiable est de découvrir la MTU du chemin, en envoyant des paquets de plus en plus gros, jusqu'à ce que ça ne passe plus, montrant ainsi qu'on a découvert la MTU.

Le RFC donne aussi quelques raisons plus subtiles pour lesquelles la découverte classique de la MTU du chemin ne marche pas toujours. Par exemple, s'il y a un tunnel sur le trajet, c'est le point d'entrée du tunnel qui recevra le message ICMP Packet Too Big, et il peut faillir à sa tâche de le faire suivre à l'émetteur du paquet. Ou bien, en cas de routage asymétrique, le message ICMP peut être jeté car il n'y a pas de route de retour.

Et ce n'est pas tout que le message ICMP revienne à l'émetteur, encore faut-il que celui-ci l'accepte. Les messages ICMP n'étant pas authentifiés, une machine prudente va essayer de les valider, en examinant le paquet originel contenu dans le message ICMP, et en essayant de voir s'il correspond à du trafic en cours. Si le routeur qui émet le message ICMP n'inclut pas assez du paquet original (malgré ce que demande le RFC 1812) pour que cette validation soit possible, le message ICMP « Vous avez un problème de taille » sera jeté par l'émetteur du paquet original. Et il peut y avoir d'autres problèmes, par exemple avec le NAT (cf. RFC 5508). Autant de raisons supplémentaires qui font que la découverte de la MTU du chemin ne peut pas compter sur les messages ICMP.

Notre RFC utilise le terme de couche de découpage en paquets (Packetization Layer, PL). Il désigne la couche où les données sont séparées en paquets qui seront soumis à IP. C'est en général la couche de transport (protocoles TCP, DCCP, SCTP, etc) mais cela peut aussi être fait au-dessus