1 points par GN⁺ 2025-10-25 | 1 commentaires | Partager sur WhatsApp
  • Un développeur partage son parcours technique et mental après avoir implémenté lui-même un compilateur ASN.1 (dasn1) en langage D
  • Le projet vise l’implémentation de x.509 et de TLS 1.3, avec prise en charge du traitement complexe de l’encodage DER d’ASN.1
  • L’article détaille la difficulté structurelle d’ASN.1, la complexité d’implémentation des spécifications x.680 à x.683, ainsi que l’usage de la métaprogrammation en D
  • Il explique concrètement comment des fonctionnalités de D comme static import, les mixin templates, typeof() et alias this ont été utiles pour la génération de code ainsi que la conception de l’AST et de l’IR
  • L’auteur conclut qu’« ASN.1 est douloureux, mais très formateur », et décrit avec franchise les difficultés concrètes et les satisfactions liées à la création d’un compilateur

Vue d’ensemble du projet et motivations

  • L’auteur développe actuellement Juptune, un framework d’E/S asynchrones basé sur D, et avait besoin de gérer lui-même l’encodage ASN.1 DER pour implémenter TLS
    • Pour parser la structure des certificats x.509 de TLS, il fallait comprendre le mode de représentation complexe des données en ASN.1
  • Ce projet a commencé comme un défi personnel, à la fois pour apprendre et pour le plaisir, et il a déjà permis de parser avec succès certains certificats
  • ASN.1 est un ancien standard des années 1990, mais il reste encore utilisé dans de nombreux systèmes modernes comme TLS, SNMP ou LDAP
  • L’auteur remarque qu’« ASN.1 est largement utilisé dans le monde, mais la plupart des développeurs ignorent jusqu’à son existence »

Qu’est-ce qu’ASN.1 ?

  • ASN.1 (Abstract Syntax Notation One) est un langage pour définir et encoder des structures de données, en quelque sorte un « ancêtre de Protocol Buffers »
  • Le standard se compose de la notation (x.680 à x.683) et des règles d’encodage (BER, CER, DER, PER, XER, JER, etc.)
    • BER : format TLV de base, avec prise en charge des longueurs infinies
    • CER : variante restreinte de BER, utilisant toujours des longueurs infinies
    • DER : sous-ensemble déterministe de BER, utilisé comme standard en cryptographie
    • PER/OER : encodage compressé au niveau du bit
    • XER/JER : encodage basé sur XML et JSON
  • Cette diversité d’encodages rend l’ensemble complexe, mais lui donne aussi une grande souplesse et extensibilité

La complexité de la notation ASN.1

  • Le standard de base d’ASN.1 est x.680, et les spécifications d’extension (x.681 à x.683) sont rédigées dans un style académique particulièrement ardu
  • Une implémentation est possible avec x.680 seul, mais les nombreuses règles de transformation sémantique et variantes syntaxiques en rendent l’implémentation difficile
  • x.681 définit le système des Information Object Class et prend en charge une syntaxe d’initialisation spécifique
    • Exemple : CALLED &name [WHO IS &age YEARS OLD]
  • x.682 définit les Table Constraint, et x.683 les types paramétrés
    • Un concept proche des génériques en D, capable de recevoir à la fois des types et des valeurs comme paramètres

Des fonctionnalités ASN.1 intéressantes

  • Système de contraintes : il permet de préciser directement, dans la définition d’un type, une plage de valeurs ou une taille
    • Exemple : UInt8 ::= INTEGER (0..255)
    • Prise en charge des opérateurs SIZE, UNION(|) et INTERSECTION(^)
  • Système de gestion de version : OBJECT IDENTIFIER permet de distinguer clairement les versions d’un module
    • Exemple : id-pkix1-implicit(19) vs id-mod-pkix1-implicit-02(59)
    • Cela permet d’identifier clairement les modules sans conflit de nom

Pourquoi le langage D est avantageux pour la génération de code

  • Le static import de D évite les conflits de noms et permet de conserver tels quels les noms de types ASN.1
  • La recherche locale au module (.Type1) permet de limiter explicitement la résolution des symboles
  • typeof() permet d’inférer automatiquement les types, évitant une gestion manuelle lors de la génération de code
  • L’autorisation de la virgule finale (trailing comma) simplifie la génération de code
  • Grâce à la concaténation de constantes à la compilation, il est possible de composer des chaînes même dans des fonctions @nogc

Exemples d’implémentation exploitant les fonctionnalités de D

Nœuds d’AST basés sur des mixin templates

  • La fonctionnalité mixin template de D est utilisée pour définir les nœuds de l’arbre syntaxique ASN.1 (AST)
    • Chaque type de nœud (List, Container, OneOf) est réutilisé sous forme de template
    • Cela simplifie le code par copie à la compilation, plutôt que via une hiérarchie d’héritage complexe

API à base de templates et vérification à la compilation

  • Le nœud Container contient plusieurs sous-nœuds et effectue une vérification des types à la compilation
    • Un accès sûr est possible sous la forme node.getNode!Asn1TagDefaultNode
  • Le nœud OneOf stocke l’un de plusieurs types et prend en charge le pattern matching via la fonction match
    • Comme tous les handlers de type doivent être définis, cela garantit une sécurité à la compilation

Utilisation du package expérimental de gestion mémoire de D

  • std.experimental.allocator est utilisé pour créer et libérer des objets dans un environnement @nogc
    • Un allocateur personnalisé est composé à partir d’éléments comme Region et StatsCollector
    • Mais le module reste à l’état expérimental depuis déjà dix ans

Fonctionnalité alias this

  • alias this est utilisé pour permettre à des structures wrapper de se comporter comme leur champ interne
    • Exemple : un cast concis comme cast(Asn1ValueReferenceIr)item

version(unittest)

  • Le mot-clé version(unittest) permet de définir des fonctions réservées aux tests, exclues du build final

Harness de test avec template + with()

  • La logique de test commune est template-ifiée, et l’instruction with() permet d’écrire un code de test concis
    • On peut appeler T() au lieu de Harness.T()

Principales difficultés rencontrées pendant l’implémentation

Syntaxe des séquences de valeurs (Value Sequence Syntax)

  • Les multiples formes de syntaxe de valeur commençant par {} sont ambiguës selon le contexte
    • La complexité est telle qu’un commentaire dans le parseur dit en substance : « ce n’est pas amusant »
  • Comme l’analyse syntaxique et l’analyse sémantique ont été séparées, le traitement est devenu encore plus difficile

Manque de clarté de la spécification

  • Certaines règles, comme le fait qu’un tag doive être traité comme EXPLICIT dans des cas particuliers, correspondent à des comportements non explicitement documentés
  • Le mode de gestion des versions de module n’est pas non plus clairement défini

Nécessité de tripler l’implémentation des contraintes

  1. pour la validation syntaxique
  2. pour la validation des valeurs
  3. pour la génération de code à l’exécution
  • La gestion de UNION et INTERSECTION rend aussi la construction des messages d’erreur complexe

L’illusion des nœuds IR immuables

  • L’auteur pensait qu’une fois l’AST converti en IR, aucune modification ne serait nécessaire,
    mais certains processus de transformation sémantique comme AUTOMATIC TAGS exigent en réalité de modifier les données

La complexité omniprésente d’ASN.1

  • x.509 n’utilise qu’une syntaxe ancienne et reste donc relativement simple, mais les spécifications récentes imposent d’implémenter x.681 à x.683
    • C’est l’une des raisons pour lesquelles ASN.1 est très peu utilisé en dehors des domaines académiques ou commerciaux spécialisés

Le problème de ANY DEFINED BY

  • ANY DEFINED BY est une structure dont le type change selon la valeur d’un autre champ
    • dasn1 ne l’implémente pas et le remplace par un intrinsic personnalisé Dasn1-Any
    • Un traitement manuel reste nécessaire lors du décodage réel

Surcharge d’informations

  • Entre ASN.1, x.68x, x.690, Juptune et d’autres projets menés en parallèle, il était difficile de garder le contexte du codebase en tête

La réalité de la fabrication d’un compilateur

  • Des milliers de visiteurs de nœuds, du code répétitif et des implémentations aux différences infimes : un travail long, pénible et souvent monotone
  • Mais chaque étape apporte aussi un fort sentiment d’accomplissement et un réel apprentissage
  • L’auteur revient sur l’expérience en disant : « personne ne l’utilisera probablement, mais j’ai acquis une vraie expérience de compilateur »
  • Il conclut enfin sur une plaisanterie : « ne faites pas d’ASN.1, ça change une vie »

Conclusion

  • Malgré une année de travail, dasn1 n’est pas encore terminé,
    mais ce projet a permis à l’auteur de comprendre en profondeur le potentiel du langage D et la complexité d’ASN.1
  • Il termine avec humour en rêvant du jour où il pourra écrire sur son CV « compilateur ASN.1 + expérience d’implémentation TLS 1.3 »,
    tout en revenant avec lucidité sur la progression du développeur et la réalité du secteur

1 commentaires

 
GN⁺ 2025-10-25
Réactions sur Hacker News
  • En résumé, l’auteur voulait parler d’ASN.1, du langage D et des compilateurs eux-mêmes.
    Mais comme il n’a pas trouvé de format cohérent, il a rassemblé ses réflexions dans un article de blog.
    Le résultat n’est peut-être pas totalement abouti, mais le sujet se prête mal à un traitement bref, donc on peut lui pardonner.

    • L’exemple d’intersection (intersection example) ne semble pas fonctionner comme prévu.
      Mathématiquement, {0} ∪ ({2} ∩ {4,5,6,7,8}) = {0}, donc au final une seule valeur est autorisée.
    • Parler du langage D, c’est presque invoquer Walter Bright.
      Personnellement, j’aime beaucoup D, mais en pratique Go et Rust sont bien plus largement utilisés.
    • J’ai moi aussi déjà manipulé des données ASN.1, surtout pour des travaux liés aux certificats, et c’était pénible.
      Je compatis profondément avec les galères de l’auteur.
    • J’ai vraiment pris plaisir à lire cet article.
      J’adore D, même si je l’ai laissé de côté depuis longtemps.
      J’ai aussi déjà travaillé sur des parseurs et des implémentations de protocoles, donc ça m’a d’autant plus intéressé.
    • Un blog reste l’espace de son auteur, donc j’espère qu’il continuera à écrire à sa manière.
  • « OMG ASN.1 », quel sujet réjouissant.
    Je me souviens de l’époque où Internet grandissait et où l’IETF faisait évoluer les protocoles.
    À l’époque, les entreprises ne s’intéressaient pas à Internet, et l’université ainsi que l’IETF menaient la danse.
    Mais quand les entreprises ont compris qu’il y avait de l’argent à gagner, les Protocol Wars ont commencé.
    ASN.1 est un produit de cette guerre et un exemple de la collision entre culture d’entreprise et culture académique.
    On pourrait comparer l’entreprise à une « culture de la recette » et le monde académique à une « culture de la fonction ».
    Cette différence de mentalité a aussi des résonances avec la culture de développement de l’IA aujourd’hui.

    • En regardant autrefois le film Father of the Bride, j’ai été sidéré d’y entendre parler de réseau X.25.
      Et quand je pense qu’on aurait pu finir avec un système d’adresses du type « CN=wikipedia, OU=org, C=US » au lieu d’Internet, ça fait froid dans le dos.
    • Je me suis dit que « OMG ASN.1 » ferait un excellent nom pour mon prochain groupe.
    • Une partie du propos est juste, mais qualifier les principaux acteurs d’« entreprises » est un peu imprécis.
      En réalité, c’étaient surtout l’ITU et l’ISO.
      Puis, à la fin des années 1990, il y a eu une autre « guerre des protocoles », et cette fois l’IETF a perdu.
    • Cette guerre a aussi correspondu au début de la marchandisation toxique d’Internet (en-shittification).
      L’ISO visait la perfection et avançait lentement, tandis que l’IETF allait vite avec une logique de « on corrigera plus tard ».
      Résultat, des protocoles se sont figés avec leurs défauts.
      Et le fait que les implémentations ASN.1 en C des années 1990 aient été médiocres n’a rien arrangé.
    • Le point essentiel, c’est que cette perspective « entreprise » était au fond une perspective mainframe.
  • Il existe un proverbe turc qui dit : « Ce n’est pas quelque chose qu’un être humain devrait utiliser ! »
    J’aimerais en faire la devise d’une philosophie de design.
    Et comme dans Game of Thrones : « Celui qui rend le jugement doit lui-même manier l’épée »,
    ceux qui écrivent les specs devraient implémenter eux-mêmes les parseurs.
    Si l’approbation d’une spec exigeait la soumission conjointe d’un parseur fonctionnel et de tests, la qualité serait bien meilleure.

  • J’aime vraiment beaucoup le langage D.
    Je suis en train d’implémenter mon propre éditeur de texte de style vim en ne dépendant que de Raylib.
    Les points forts de D sont les suivants :

    • on peut écrire des unit tests n’importe où
    • les blocs version(unittest) permettent de gérer facilement le code réservé aux tests
    • le support du langage pour les enum, union, assert, la programmation par contrat, etc. est excellent
      En consultant la documentation ou en demandant à ChatGPT, j’ai toujours pu trouver une solution élégante.
    • D est pour moi un langage doux-amer.
      Sur le plan de la philosophie de conception, il frôle la perfection, mais si ses outils et son écosystème avaient été au niveau de Rust ou Go, il aurait eu bien plus de succès.
    • Les fonctionnalités de D sont bonnes, mais le langage a tendance à devenir de plus en plus verbeux (noisy).
      La bibliothèque standard Phobos accumulait trop de petites irritations, au point que j’ai fini par abandonner.
      Une nouvelle version, Phobos V3, est en préparation, mais comme les effectifs sont réduits, j’oscille entre espoir et inquiétude.
  • « Est-ce que j’ai déjà dit qu’ASN.1 était complexe ? »
    Le schéma comme le format des données sont complexes, mais cette complexité est en grande partie ignorable.
    Je n’utilise pas la notation de schéma ASN.1 et j’ai écrit directement une implémentation DER en C.
    DER est selon moi le seul encodage standard réellement valable.
    J’ai aussi créé mes propres formats d’encodage comme DSER, SDSER et TER.
    Des structures comme ANY DEFINED BY restent encore très utiles,
    et j’ai même ajouté une fonctionnalité non standard, OBJECT IDENTIFIER RELATIVE TO, pour obtenir un encodage plus efficace.

  • J’ai moi aussi déjà écrit un compilateur ASN.1.
    Je n’ai implémenté qu’une partie des fonctionnalités de X.681 à X.683, mais j’ai permis de décoder récursivement un certificat complet en un seul appel au codec.
    ASN.1 n’est pas juste une syntaxe simple, c’est un système de types puissant.
    C’est sous-estimé, mais c’est une technologie vraiment impressionnante.

  • J’ai autrefois créé un compilateur ASN.1 pour Swift.
    C’était le projet ASN1Codable, qui s’appuyait sur libasn1 de Heimdal
    pour convertir ASN.1 en AST JSON et ainsi simplifier le parsing.

    • Dans le README de libasn1, on sent une aversion à peine voilée pour ASN.1.
      Le « transformons ça en JSON » sonne au fond comme le cri d’un développeur blessé 😄
  • Étrangement, travailler sur ASN.1 me paraît agréable.
    J’aimerais un jour écrire moi-même un compilateur ASN.1 pour Rust.
    Les implémentations Rust actuelles reposent surtout sur des macros derive ou du chaînage manuel, ce que je trouve frustrant.

  • En général, quand on implémente un standard, on réalise 80 % des fonctionnalités en 20 % du temps,
    mais les 20 % restants d’ASN.1 peuvent prendre toute une vie.

  • J’ai autrefois étendu le parseur ASN.1 de la base de code de Netscape pour prendre en charge PKCS#12.
    J’ai regretté d’avoir appris les standards RSA et les définitions ASN.1 un peu trop en profondeur,
    mais j’ai un grand respect pour la persévérance et le léger masochisme de l’auteur du blog.

    • Avec une telle expérience, j’imagine qu’il doit y avoir pas mal d’anecdotes de développement dignes d’un champ de bataille.