3 puntos por GN⁺ 2025-05-24 | 1 comentarios | Compartir por WhatsApp
  • La autora sentía rechazo durante años hacia el protocolo ACME por su complejidad y los riesgos de implementación
  • Los clientes ACME existentes solían tener código riesgoso desde el punto de vista de la seguridad o difícil de entender, así que evitaba ejecutarlos por su cuenta
  • Pero, debido al deterioro de la calidad y el aumento de precios de Gandi, su registrador de dominios, terminó implementando su propia herramienta de renovación de certificados
  • Tras muchísimos intentos y errores, logró completar con éxito una herramienta para emitir certificados directamente mediante Let's Encrypt
  • En la segunda mitad del texto explica en detalle el funcionamiento real del protocolo ACME y detalles de implementación de bajo nivel como JSON, base64 y firmas

Why I no longer have an old-school cert on my https site

Contexto y motivación

  • A comienzos de 2023 explicó por qué seguía manteniendo un certificado a la antigua, pero ahora, en 2025, comparte por qué terminó abandonando ese enfoque
  • Su rechazo hacia el protocolo ACME venía desde 2018, y las tecnologías web complejas y los métodos de codificación enrevesados eran una gran barrera
  • Consideraba que la mayoría de los clientes ACME tenían código difícil de confiar y que era peligroso hacerlos correr con privilegios de root
  • Después de que Gandi fuera adquirida por un fondo de capital privado, la calidad bajó y los precios subieron, así que ya no había razón para seguir manteniendo el certificado anterior

El inicio de la implementación propia

  • En vez de usar herramientas existentes, fue implementando por su cuenta pequeñas funciones utilitarias, una por una
  • Empezó por encapsular jansson, una biblioteca JSON para C, para poder usarla desde C++
  • Revisó varias bibliotecas para generar JWK (estructuras de clave), pero casi ninguna ayudó, así que decidió implementarlo por sí misma
  • En el camino se detuvo y volvió a empezar varias veces, conectando poco a poco componentes pequeños de manera incremental

Entorno de pruebas y aplicación real

  • Para no tocar directamente los servidores reales de Let's Encrypt, usó en un entorno aislado un servidor ACME de pruebas llamado "pebble"

  • Después de numerosos fracasos, logró completar una herramienta inicial que recibe un CSR y emite un certificado, y

    • la probó con éxito en el servidor de staging de Let's Encrypt
    • también funcionó en producción
    • y ya quedó aplicada al sitio web real

Explicación detallada del protocolo ACME

  • Genera una clave RSA y crea un CSR (Certificate Signing Request) que incluye CN y SAN
  • Analiza el JSON de la URL del directorio ACME para extraer endpoints como newNonce, newAccount y newOrder
  • Extrae de la clave privada el módulo y el exponente público, y los convierte a codificación base64url apta para la web
  • Tras crear el JWK, firma con RSA SHA256 junto con el payload JSON
  • Obtiene un Nonce con una petición HTTP HEAD y luego envía la solicitud firmada por POST para crear la cuenta
  • El encabezado Location de la respuesta no se usa como redirección real, sino como URL identificadora de la cuenta

La complejidad del protocolo ACME

  • A pesar de tratarse solo de emitir un certificado, intervienen cosas como
    • hash SHA256, base64web, JSON dentro de JSON y firmas RSA
    • peticiones HEAD, identificación de cuenta mediante el encabezado Location, necesidad de un Nonce de un solo uso, etc.
  • Menciona que todavía ni siquiera ha llegado a cubrir el pedido del certificado, la prueba de propiedad del dominio (como registros TXT) ni la finalización de la validación
  • También señala cierta flexibilidad excesiva del estándar, ya que algunos clientes funcionan incluso con una implementación incorrecta de la codificación de publicExponent

Conclusión

  • ACME es extremadamente complejo y requiere una enorme cantidad de prueba, error y esfuerzo para implementarlo directamente
  • Aun así, comparte que logró abandonar el certificado a la antigua y pasar con éxito a un esquema completamente automatizado
  • También remata con la broma de si toda esta complejidad no será, quizá, una estructura diseñada para garantizarle trabajo a alguien

1 comentarios

 
GN⁺ 2025-05-24
Comentarios en Hacker News
  • Soy el líder técnico del equipo de SRE/infra de Let’s Encrypt, así que pienso mucho en este tipo de problemas
    JSON Web Signature es un formato realmente complicado, y la API de ACME también está muy comprometida con ser RESTful
    Si yo la hubiera diseñado, no la habría hecho así
    Creo que el trasfondo de que esta estructura terminara así se debe en parte a la intención del IETF de reutilizar muchos estándares del IETF y en parte al diseño por comité
    Tener aunque sea unas cuantas librerías de JSON, JWS y HTTP ya mejora mucho las cosas, pero el problema es que, sobre todo en C, ni siquiera esas librerías son fáciles de usar
    El propio lenguaje de los RFC es complejo y además suele remitir a muchos otros documentos, así que estamos trabajando por separado en un cliente interactivo y en documentación para ayudar con eso

    • No termino de entender eso de que JSON Web Signature sea un formato complicado
      Yo trato mucho con cosas complejas como ASN.1, Kerberos y PKI, y no me parece que JWS sea un formato especialmente difícil
      Incluso si uno tuviera que implementarlo directamente en código, me sigue pareciendo mucho más fácil que S/MIME, CMS o Kerberos
      Hace falta explicar mejor en qué parte JWS es “complicado”
      Si el problema es JWT, me parece más central que no esté bien definido de forma estándar cómo un user agent HTTP debe recibirlo o solicitarlo

    • Vi que alguien decía que “si quieres emitir más de 3 certificados tienes que pagar”, pero yo lo he usado durante los últimos 5 años y nunca me ha llegado una factura; me parece un malentendido o información incorrecta

  • Al hablar de la parte donde se usa “e=AQAB” en lugar de “e=65537”, se explica que la causa es que JSON no maneja bien los números
    Si pasas a un parser JSON un valor muy grande como 4723476276172647362476274672164762476438, la mayoría de los parsers JSON lo truncarán silenciosamente a un entero de 64 bits o a un float, o con suerte darán error
    Un lenguaje como Common Lisp probablemente lo manejaría bien, pero en la práctica no mucha gente desarrolla en ese tipo de entorno
    Por eso, si quieres transmitir números grandes de forma confiable en JSON, hasta parece mejor convertirlos a un arreglo de bytes en base64
    Aunque parezca que no pasa nada, este tipo de comportamiento es la raíz de varios problemas de seguridad, así que me parece válido tratar así todos los números del protocolo
    Eso sí, este enfoque tiene la desventaja de que se pierde la legibilidad humana de JSON, y personalmente creo que una S-Expression estandarizada habría sido una opción mucho mejor
    Pero el mundo eligió JSON

    • Si no entiendes por qué el mundo eligió JSON, creo que lo estás ignorando a propósito
      JSON se puede escribir, editar y leer manualmente con mucha facilidad para la mayoría de los datos
      En cambio, Canonical S-Expression requiere anteponer información de longitud a cada elemento, así que editarlo a mano es demasiado tedioso
      Para escribir una S-Expression tienes que contar caracteres uno por uno y corregir los prefijos, lo cual es muy molesto
      Contrario a lo que se esperaría, creo que esa facilidad para escribir y modificar manualmente es la razón por la que JSON sobrevivió
      Por cierto, el parser JSON de Ruby maneja bien los números grandes

    • En una app de C# sufrí un bug donde el serializer JSON emitía un BigInt como número, JS lo recibía y lo interpretaba mal en silencio
      Todavía me sorprende que el comportamiento estándar sea desbordar en vez de dar error
      Desde entonces, tengo la costumbre de tratar siempre como cadenas todos los números mayores a 32 bits

    • La comparación entre {"e":"AQAB"} y {"e":65537} tiene sentido, pero si lo comparas con {"e":"65537"}, también en ese caso todos los parsers JSON producirán el mismo resultado
      Ya sea número o cadena, la conversión es clara
      Claro, si el valor es tan grande que ni siquiera cabe en un double, entonces eso ya es un problema del lenguaje o del parser, pero me parece independiente de la forma de representación

    • Creo que el problema de JSON no está en el formato en sí, sino en que los parsers se hicieron originalmente para mapear a tipos de JS
      Aunque algunos parsers puedan manejarlo bien, si haces eso se pierde la portabilidad de JSON
      Si lo conviertes a Base64 pasa el mismo problema (porque ya no sigue el estándar)
      Se puede hacer parsing personalizado con replacer y reviver, pero no está garantizado que esa función exista en todos los entornos
      Al final, el origen del error es asumir que JSON se interpreta con un parser estándar
      Si en vez de llamarlo JSON se llamara de otra manera, estos problemas disminuirían, pero la gente igual intentaría pasarlo al parser tal cual si se ve como JSON

    • Go tiene el tipo json.Number, con el que puedes decodificar números como cadenas sin pérdida
      Comparto uno de mis tipos decimal arbitrarios favoritos https://github.com/ncruces/decimal?tab=readme-ov-file#decimal-arithmetic
      Medio en broma, no me queda claro por qué S-Expression sería mejor en este caso
      Incluso entre los LISP hay casos que no soportan aritmética de precisión arbitraria

  • Me resultó curioso por qué el autor adoptó una postura tan crítica hacia ACME y varios clientes
    No parece simplemente un tema de falta de habilidad, así que supuse que había cierta antipatía hacia el propio concepto de ACME o hacia las herramientas de su ecosistema
    Nosotros también lo aplicamos desde 2019 en algunos sitios basados en LE, y en ese tiempo probamos varios clientes ACME
    Por ejemplo, Crypt-LE nos funcionó bien para nuestro caso, y al intentar integrarlo con Sectigo ACME, como le64 no bastaba, probamos varias cosas como certbot, lego y posh-acme
    Al final usamos certbot corrigiendo un problema del entorno GHA, y posh-acme también nos pareció bueno
    Releyéndolo, el tono filoso del autor no iba tanto contra ACME o los clientes, sino contra la propia especificación
    La conclusión es que la idea de ACME es buena, pero su implementación y su aplicación en la práctica son decepcionantes

    • Creo que tengo una perspectiva parecida a la del autor
      Cita la frase del autor: “muchos clientes existentes son código peligroso y no confío en ejecutarlos con privilegios de root en mi servidor”
      Me parece una actitud razonable en tareas sensibles de seguridad

    • Para quien tuvo dificultad en entender el tono del texto original, se comparten enlaces a publicaciones anteriores que dan contexto

    • Hay mucha gente a la que no le gusta ejecutar en su servidor algo que no entiende, y yo también simpatizo con esa idea
      Pero la seguridad es, por naturaleza, un juego del gato y el ratón, así que no queda otra que seguir cambiando
      Por suerte, ACME te da la libertad de crear tu propio cliente
      No estás obligado a usar certbot, ni es una estructura que te bloquee tus recursos como TPM

  • Si uno quiere implementar un cliente ACME desde cero, comparte la experiencia de que leer directamente los RFC (y los documentos relacionados de JOSE, etc.) es más fácil de lo que parece
    Incluso lo implementó por su cuenta y escribió una guía para entender el flujo de ACME v2, que comparte aquí https://www.arnavion.dev/blog/2019-06-01-how-does-acme-v2-work/
    No sustituye al RFC oficial, pero sirve bien como referencia tipo diagrama de flujo e índice por modalidad

    • Incluso hubo una tarea final de un curso de seguridad del MIT que consistía en implementar un cliente ACME https://css.csail.mit.edu/6.858/2023/labs/lab5.html

    • Se satiriza esa extraña realidad en la que, en vez de leer el manual paso por paso, puedes ganar más puntos de internet publicando en Hacker News un texto explicando todo el proceso en inglés

  • Agradecen al autor por señalar que la complejidad de los protocolos de infraestructura web sigue aumentando
    Piensan que estos estándares no solo son una carga para desarrolladores que simplemente tienen que usar herramientas o clientes, sino que además actúan como una “barrera regulatoria”, estructurando las cosas de forma que al final solo las grandes empresas establecidas puedan cumplir los requisitos para operar en internet
    Puede que ACME por sí solo no sea una barrera de entrada imposible de cruzar, pero al final todo se va acumulando hasta formar una pared

    • Expresa optimismo diciendo que todos estos protocolos tienen implementaciones open source y que, gracias al avance de la IA, estas barreras también irán bajando poco a poco
  • En OpenBSD hay un cliente ACME muy simple y liviano incluido en el sistema base
    Entiendo que lo hicieron porque las alternativas existentes eran demasiado pesadas y no seguían la filosofía Unix
    Da pena que el autor no haya considerado esa opción
    Probablemente también podría portarse a otros sistemas operativos con un poco de esfuerzo

    • Creo que este cliente de OpenBSD es más bien un caso de la filosofía OpenBSD sin entender por qué la seguridad se ha vuelto tan compleja
      Este cliente se instala y se usa solo en esa máquina, y mediante una estructura separada hace que cada componente no afecte a los demás
      Pero el propio protocolo ACME permite una separación completa (air-gapping), de modo que el servidor web, el solicitante del certificado y el servidor DNS pueden estar en entornos distintos sin problema
      Si no usas el cliente integrado de OpenBSD, quizá sea más complejo, pero desde la perspectiva de principios de diseño de seguridad me parece superior
      “Solo instala OpenBSD y listo” no deja de ser simplemente el camino fácil

    • También se menciona uacme (https://github.com/ndilieto/uacme)
      Es código C liviano, y después de sufrir constantemente con el cliente Python de LE por problemas de energía, lo usaron de forma estable como reemplazo

    • Comparten la experiencia de que usan directamente el cliente ACME de OpenBSD y funciona muy bien

  • La recomendación de “generar una clave privada RSA de 4096 bits” en realidad solo empeora la velocidad para los visitantes, y la seguridad práctica sigue estando al nivel de 2048 bits
    Se enfatiza que es mejor usar un certificado leaf de 2048 bits

    • Pregunta si 4096 bits no sería más resistente frente a captura pasiva y descifrado futuro
      También le da curiosidad si la seguridad de los certificados intermedios afecta a los ataques asincrónicos

    • Como su webhost solo soporta claves RSA, usa RSA de 4096 bits a propósito para empujarlos a que den soporte a claves EC más rápido

  • Hacer este trabajo por cuenta propia sí te hace mejorar, pero el tono del texto del autor da la impresión de que está desahogando su frustración con el protocolo o con el proceso de despliegue de Let’s Encrypt
    Incluso con una librería ACME liviana (https://github.com/jmccl/acme-lw, por ejemplo) se puede automatizar suficientemente; se preguntan por qué hacerlo de una forma tan sufrida

    • Afirma tajantemente que SSL es realmente un “montón caliente y petrificado de caos”
      Los problemas de flags y bitfields vienen todos del legado histórico de ASN.1/X.509, la complejidad matemática es seria, y todas las librerías y el software siguen atados a las limitaciones tecnológicas de los años 80
      Hubo una última oportunidad de ordenar este caos cuando llegó Let’s Encrypt o cuando apareció HTTP/2, pero en la práctica una ACME CA puede quedar armada con shell scripts, OpenSSL y alcohol, y además la compatibilidad con el software existente también era un problema, así que no hubo salto real
  • Comparte que la presión para migrar cada vez más a HTTPS sigue aumentando
    Por ejemplo, en WhatsApp ya no se pueden abrir enlaces HTTP

    • Sugiere que usando proxy y caché se puede reducir la carga de tráfico, y que es un buen método para servidores pequeños

    • Enfatiza que, por muy complejo que sea ACME, sigue siendo muchísimo mejor que no tener soporte TLS

  • “Clave RSA, digest SHA256, firma RSA, base64 que en realidad no es base64, concatenación de cadenas, JSON dentro de JSON, usar el encabezado Location como identificador en vez de un redireccionamiento 301, una petición HEAD solo para obtener un valor de encabezado, una petición separada para el nonce en cada solicitud, etc.”
    “Y todavía faltan pasos aún más complejos como crear la orden del certificado, manejar autorizaciones y challenges, key thumbprints, construir registros TXT, etc.”
    Termina con un mensaje de apoyo diciendo que la complejidad es casi imposible de creer y agradeciendo que hayan compartido el resumen