1 puntos por GN⁺ 2025-10-25 | 1 comentarios | Compartir por WhatsApp
  • Un desarrollador comparte el recorrido técnico y mental que vivió al implementar por su cuenta un compilador de ASN.1 (dasn1) en el lenguaje D
  • El proyecto busca la implementación de x.509 y TLS 1.3, y soporta el complejo procesamiento de codificación DER de ASN.1
  • El texto aborda en detalle la complejidad estructural de ASN.1, la dificultad de implementar las especificaciones x.680~x.683 y cómo aprovechar la metaprogramación de D
  • Explica de forma concreta cómo funciones de D como static import, mixin template, typeof(), alias this fueron útiles para la generación de código y el diseño de AST/IR
  • El autor resume que “ASN.1 es doloroso, pero se aprende muchísimo”, y transmite con honestidad las dificultades reales y la recompensa de crear un compilador

Panorama general del proyecto y motivación

  • El autor está desarrollando Juptune, un framework de E/S asíncrona basado en D, y para implementar TLS necesitaba procesar directamente la codificación DER de ASN.1
    • Para parsear la estructura de los certificados x.509 de TLS, era necesario entender la compleja forma en que ASN.1 representa los datos
  • Este proyecto comenzó como un reto personal por aprendizaje y diversión, y de hecho ya llegó a la etapa de parsear correctamente algunos certificados
  • Aunque ASN.1 es un estándar antiguo de los años 90, todavía se usa ampliamente en sistemas modernos como TLS, SNMP y LDAP
  • El autor menciona que “ASN.1 se usa por todas partes, pero la mayoría de los desarrolladores ni siquiera sabe que existe”

¿Qué es ASN.1?

  • ASN.1 (Abstract Syntax Notation One) es un lenguaje para definir y codificar estructuras de datos, una especie de “antepasado de Protocol Buffers”
  • El estándar se compone de la notación (x.680~x.683) y las reglas de codificación (BER, CER, DER, PER, XER, JER, etc.)
    • BER: formato TLV básico, con soporte para longitud indefinida
    • CER: variante restringida de BER, que siempre usa longitud indefinida
    • DER: subconjunto determinista de BER, usado como estándar en criptografía
    • PER/OER: codificación comprimida a nivel de bits
    • XER/JER: codificación basada en XML y JSON
  • La gran variedad de codificaciones lo vuelve complejo, pero también le da alta flexibilidad y extensibilidad

La complejidad de la notación ASN.1

  • El estándar base de ASN.1 es x.680, y las especificaciones extendidas (x.681~x.683) están escritas con un estilo académico extremadamente difícil de leer
  • Se puede implementar solo con x.680, pero la gran cantidad de reglas de transformación semántica y variaciones sintácticas hace que la implementación sea complicada
  • x.681 define el sistema de Information Object Class y admite una sintaxis propia de inicialización
    • Ejemplo: CALLED &name [WHO IS &age YEARS OLD]
  • x.682 define Table Constraint, y x.683 define tipos parametrizados
    • Es un concepto parecido a los genéricos de D, ya que puede recibir tanto tipos como valores como parámetros

Funciones interesantes de ASN.1

  • Sistema de restricciones (Constraint): permite indicar directamente el rango o tamaño de los valores al definir un tipo
    • Ejemplo: UInt8 ::= INTEGER (0..255)
    • Soporta operadores SIZE, UNION(|), INTERSECTION(^)
  • Sistema de versionado: mediante OBJECT IDENTIFIER se pueden distinguir con claridad las versiones de los módulos
    • Ejemplo: id-pkix1-implicit(19) vs id-mod-pkix1-implicit-02(59)
    • Permite identificar módulos con claridad sin choques de nombres

Por qué el lenguaje D favorece la generación de código

  • El static import de D evita conflictos de nombres y permite conservar tal cual los nombres de tipos de ASN.1
  • La búsqueda local al módulo mediante .Type1 limita con claridad la resolución de símbolos
  • Con typeof() se puede inferir automáticamente el tipo, lo que evita tener que administrarlo manualmente al generar código
  • El soporte de coma final (trailing comma) simplifica la generación de código
  • Gracias a la composición de constantes en tiempo de compilación, es posible combinar cadenas incluso dentro de funciones @nogc

Casos de implementación aprovechando funciones de D

Nodos AST basados en mixin templates

  • Usó la función mixin template de D para definir nodos del árbol sintáctico de ASN.1 (AST)
    • Cada tipo de nodo (List, Container, OneOf) se reutiliza como plantilla
    • En lugar de una herencia compleja, se simplifica mediante copia de código en tiempo de compilación

API basada en templates y validación en tiempo de compilación

  • El nodo Container incluye varios subnodos y realiza validación de tipos en tiempo de compilación
    • Se puede acceder de forma segura con expresiones como node.getNode!Asn1TagDefaultNode
  • El nodo OneOf almacena uno entre varios tipos y soporta pattern matching con la función match
    • Como obliga a definir manejadores para todos los tipos, garantiza seguridad en tiempo de compilación

Uso del paquete experimental de administración de memoria de D

  • Con std.experimental.allocator implementó creación y liberación de objetos en un entorno @nogc
    • Combinó componentes como Region y StatsCollector para construir asignadores personalizados
    • Eso sí, sigue siendo experimental incluso después de 10 años

Función alias this

  • Mediante alias this, hizo que structs contenedores se comporten como si fueran su campo interno
    • Ejemplo: permite casts concisos como cast(Asn1ValueReferenceIr)item

version(unittest)

  • La palabra clave version(unittest) permite definir funciones exclusivas para pruebas, que no se incluyen en el build real

Harness de pruebas con templates + with()

  • La lógica común de pruebas se parametriza con templates y, usando la sentencia with(), se logra escribir pruebas más concisas
    • Se puede llamar T() en lugar de Harness.T()

Principales dificultades durante la implementación

Sintaxis de secuencia de valores (Value Sequence Syntax)

  • Varias formas de sintaxis de valores que empiezan con {} son ambiguas según el contexto
    • El parser incluso tiene un comentario del tipo “esto no es divertido”, reflejando lo enredado del asunto
  • Como separó el análisis sintáctico del análisis semántico, la dificultad de procesarlo aumentó

Falta de claridad en la especificación

  • Existen comportamientos no indicados explícitamente en la documentación, como reglas donde cierto tag debe tratarse como EXPLICIT bajo determinadas condiciones
  • Tampoco está claramente definido el modo de gestionar versiones de módulos

Necesidad de implementar las restricciones por triplicado

  1. Para validación sintáctica
  2. Para validación de valores
  3. Para generación de código en tiempo de ejecución
  • Al manejar UNION e INTERSECTION, también se vuelve compleja la construcción de mensajes de error

La ilusión de nodos IR inmutables

  • Pensó que después de convertir el AST a IR ya no haría falta modificar nada,
    pero procesos de transformación semántica como AUTOMATIC TAGS exigieron cambiar los datos

La complejidad total de ASN.1

  • x.509 usa solo una sintaxis antigua y por eso es relativamente simple, pero los estándares modernos requieren implementar x.681~x.683
    • Por eso ASN.1 casi no se usa fuera de ámbitos académicos o comerciales muy específicos

El problema de ANY DEFINED BY

  • ANY DEFINED BY es una estructura cuyo tipo cambia según el valor de otro campo
    • dasn1 no lo implementa y lo reemplaza por un intrínseco personalizado Dasn1-Any
    • Al decodificar de verdad, hace falta manejarlo manualmente

Sobrecarga de información

  • Al llevar en paralelo ASN.1, x.68x, x.690, Juptune y otros frentes, era difícil mantener el contexto del codebase

La realidad de crear un compilador

  • Miles de visitantes de nodos, código repetitivo e implementaciones con diferencias mínimas hacen que sea un trabajo tedioso y agotador
  • Aun así, en cada etapa hubo una gran sensación de logro y aprendizaje
  • El autor recuerda que “probablemente nadie lo use, pero sí obtuve experiencia real construyendo un compilador”
  • Cierra el texto con la broma: “no hagan ASN.1, te cambia la vida”

Conclusión

  • A pesar de un año de trabajo, dasn1 sigue incompleto, pero fue una oportunidad para entender a fondo el potencial del lenguaje D y la complejidad de ASN.1
  • Con la esperanza de algún día poder poner en su CV “experiencia implementando un compilador de ASN.1 + TLS 1.3”,
    el texto concluye repasando con humor el crecimiento del desarrollador y la realidad de la industria

1 comentarios

 
GN⁺ 2025-10-25
Comentarios de Hacker News
  • En resumen, quería hablar de ASN.1, del lenguaje D y del compilador en sí
    Pero como no encontraba un formato consistente, junté esas ideas relacionadas en una entrada de blog
    No está del todo pulido, pero es un tema difícil de tratar brevemente, así que espero comprensión

    • Parece que el ejemplo de intersección (intersection example) no funciona como se pretendía
      Matemáticamente, {0} ∪ ({2} ∩ {4,5,6,7,8}) = {0}, así que al final solo se permite un único valor
    • Cuando sacas el tema del lenguaje D, es como invocar a Walter Bright
      En lo personal me gusta muchísimo D, pero siendo realistas, Go y Rust se usan mucho más
    • Yo también he trabajado con datos ASN.1, y en particular todo lo relacionado con certificados fue doloroso
      Entiendo profundamente el sufrimiento del autor
    • De verdad disfruté mucho leer el texto
      Amo D, pero lo he tenido abandonado desde hace tiempo
      Como antes trabajé con parsers e implementación de protocolos, me resultó todavía más interesante
    • Al final el blog es tu propio espacio, así que ojalá siga adelante a tu manera
  • “OMG ASN.1”, qué tema tan agradable de ver
    Recuerdo la época en que internet estaba creciendo y la IETF hacía avanzar los protocolos
    En ese entonces a las empresas no les interesaba internet, y el liderazgo lo llevaban la academia y la IETF
    Pero cuando las empresas se dieron cuenta de que había dinero ahí, empezó la Protocol Wars
    ASN.1 es producto de esa guerra y un ejemplo del choque entre la cultura corporativa y la cultura académica
    Se podría decir que las empresas representan una “cultura de recetas” y la academia una “cultura de funciones”
    Esa diferencia de mentalidad también ofrece pistas sobre la cultura de desarrollo de IA actual

    • Una vez estaba viendo la película Father of the Bride y me sorprendió que apareciera una conversación sobre redes X.25
      Pensar que podríamos haber terminado con un sistema de direcciones como “CN=wikipedia, OU=org, C=US” en lugar de internet da escalofríos
    • Me dieron ganas de ponerle “OMG ASN.1” a mi próxima banda
    • Parte de la historia es correcta, pero describir al actor principal como “las empresas” es algo impreciso
      En realidad, los protagonistas eran ITU e ISO
      Después, a fines de los 90, hubo otra “guerra de protocolos”, y esta vez la IETF perdió
    • Esta guerra también fue parte del proceso de comercialización temprana de internet (en-shittification)
      ISO buscaba la perfección y por eso se movía lento, mientras que la IETF avanzaba rápido con la actitud de “ya lo arreglamos después”
      Como resultado, surgió el problema de que los protocolos quedaran endurecidos demasiado pronto
      También fue un problema que las implementaciones ASN.1 para C en los años 90 fueran pésimas
    • El punto clave es que la perspectiva empresarial era, en el fondo, una perspectiva de mainframe
  • Hay un dicho turco que dice: “¡Esto no es algo para que lo use una persona!”
    Me gustaría adoptarlo como lema de filosofía de diseño
    Y, como en la frase de Game of Thrones de que “quien dicta la sentencia debe blandir la espada”,
    quien escribe la especificación debería implementar personalmente el parser
    Si una especificación solo pudiera aprobarse junto con un parser funcional y sus pruebas, la calidad probablemente mejoraría muchísimo

  • Me encanta el lenguaje D
    Estoy implementando por mi cuenta un editor de texto estilo vim que solo depende de Raylib
    Las ventajas de D son las siguientes

    • Puedes escribir unit test en cualquier parte
    • Con bloques version(unittest) es fácil manejar código exclusivo para pruebas
    • El soporte del lenguaje para enum, union, assert y programación por contratos es excelente
      Siempre que consultaba la documentación o le preguntaba a ChatGPT, terminaba encontrando una solución elegante
    • D es para mí un lenguaje agridulce
      Desde la filosofía de diseño está cerca de ser perfecto, pero si sus herramientas y ecosistema estuvieran al nivel de Rust o Go, habría tenido mucho más éxito
    • Las funciones de D son buenas, pero cada vez da más la impresión de ser un lenguaje ruidoso (noisy)
      La biblioteca estándar Phobos tiene demasiadas pequeñas incomodidades y al final la abandoné
      La nueva versión, Phobos V3, está en marcha, pero como hay poca gente trabajando en ella, tengo esperanza y preocupación a la vez
  • “¿Cuándo dije que ASN.1 fuera complejo?”
    Tanto el esquema como el formato de datos son complejos, pero la mayor parte de esa complejidad puede ignorarse
    Yo no usé la notación de esquemas ASN.1, sino que escribí directamente una implementación de DER en C
    DER me parece la única codificación estándar que realmente vale la pena usar
    Además, también hice formatos de codificación propios como DSER, SDSER y TER
    Estructuras como ANY DEFINED BY siguen siendo útiles,
    y para una codificación eficiente incluso agregué una función no estándar llamada OBJECT IDENTIFIER RELATIVE TO

  • Yo también he hecho un compilador ASN.1
    Aunque solo implementé parte de X.681 a X.683, logré que con una sola llamada al códec se pudiera decodificar recursivamente un certificado completo
    ASN.1 no es solo una gramática simple, sino un sistema de tipos potente
    Está subestimado, pero de verdad es una tecnología increíble

  • Hace tiempo hice un compilador ASN.1 para Swift
    En el proyecto ASN1Codable, aprovechando libasn1 de Heimdal,
    convertí ASN.1 a un JSON AST para simplificar el parsing

    • En el README de libasn1 se percibe una aversión apenas disimulada hacia ASN.1
      Eso de “convirtámoslo a JSON” suena, al final, como el grito de un desarrollador herido 😄
  • Curiosamente, trabajar con ASN.1 se siente divertido
    Algún día me gustaría hacer mi propio compilador ASN.1 para Rust
    Las implementaciones actuales en Rust en su mayoría dependen de macros derive o de encadenamiento manual, y eso me deja con ganas de más

  • En general, al implementar un estándar se completa el 80% de las funciones en el 20% del tiempo,
    pero el 20% restante de ASN.1 podría tomar toda una vida

  • Hace tiempo amplié el parser ASN.1 del código base de Netscape para dar soporte a PKCS#12
    Me arrepentí de haber aprendido demasiado a fondo el estándar RSA y las definiciones ASN.1,
    pero le tengo respeto a la persistencia y un poco de masoquismo del autor del blog

    • Con una experiencia así, seguro hay muchas anécdotas de desarrollo dignas de guerra