3 points par GN⁺ 2025-05-24 | 1 commentaires | Partager sur WhatsApp
  • L’autrice a longtemps été rebutée par le protocole ACME en raison de sa complexité et des risques liés à son implémentation
  • Les clients ACME existants contenaient souvent du code risqué sur le plan de la sécurité ou difficile à comprendre, ce qui la rendait réticente à les exécuter elle-même
  • Mais la baisse de qualité et la hausse des prix chez le registraire Gandi l’ont amenée à implémenter elle-même un outil de renouvellement de certificats
  • Après de nombreux essais et erreurs, elle a finalement réussi à mettre au point un outil qui obtient directement des certificats via Let's Encrypt
  • La seconde moitié de l’article explique en détail le fonctionnement réel du protocole ACME ainsi que des détails d’implémentation bas niveau autour de JSON, base64 et des signatures

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

Contexte et élément déclencheur

  • Début 2023, elle expliquait encore pourquoi elle continuait à conserver un certificat à l’ancienne, mais en 2025 elle partage les raisons pour lesquelles elle a finalement abandonné cette approche
  • Son rejet du protocole ACME remontait à 2018, notamment à cause de technologies web complexes et de méthodes d’encodage obscures
  • Elle jugeait la plupart des clients ACME difficiles à considérer comme fiables, et trop risqués à faire tourner avec des privilèges root
  • Après le rachat de Gandi par un fonds d’investissement, la qualité a baissé et les prix ont augmenté, si bien qu’il n’y avait plus de raison de conserver l’ancien certificat

Début de l’implémentation maison

  • Plutôt que d’utiliser un outil existant, elle a commencé à implémenter elle-même de petites fonctions utilitaires, une par une
  • Elle a d’abord enveloppé jansson, une bibliothèque JSON pour le C, afin de pouvoir l’utiliser en C++
  • Elle a étudié plusieurs bibliothèques pour générer un JWK (structure de clé), mais la plupart ne l’ont pas aidée, et elle a décidé de l’implémenter elle-même
  • Elle s’est arrêtée puis remise au travail à plusieurs reprises, avançant progressivement en assemblant de petits composants

Environnement de test et mise en production

  • Pour éviter d’interagir directement avec les serveurs réels de Let's Encrypt, elle a utilisé dans un environnement isolé un serveur ACME de test appelé "pebble"

  • Après de nombreux échecs, elle a terminé un premier outil capable de prendre un CSR en entrée et d’émettre un certificat, et

    • les tests sur le serveur de staging de Let's Encrypt ont réussi
    • cela a aussi fonctionné en production
    • le système a été déployé sur le vrai site web

Explication détaillée du protocole ACME

  • Génération d’une clé RSA et création d’un CSR (Certificate Signing Request) incluant le CN et les SAN
  • Analyse du JSON fourni par l’URL de répertoire ACME pour en extraire les endpoints newNonce, newAccount, newOrder, etc.
  • Extraction du modulus et de l’exposant public depuis la clé privée, puis conversion en encodage base64url adapté au web
  • Création du JWK, puis signature RSA SHA256 avec la charge utile JSON
  • Récupération d’un Nonce via une requête HTTP HEAD, puis envoi d’une requête POST signée pour créer le compte
  • L’en-tête de réponse Location n’est pas utilisé comme une vraie redirection, mais comme URL d’identifiant de compte

La complexité du protocole ACME

  • Alors qu’il ne s’agit en apparence que d’émettre un certificat, cela implique malgré tout :
    • hachage SHA256, base64 web, JSON imbriqué dans du JSON, signature RSA
    • requêtes HEAD, identification du compte via l’en-tête Location, besoin d’un Nonce à usage unique, etc.
  • Elle précise qu’elle n’en est même pas encore au stade du traitement des commandes de certificats, de la preuve de possession du domaine (comme les enregistrements TXT), ni de la validation finale
  • Elle souligne aussi une certaine souplesse du standard, certains clients semblant fonctionner même avec une implémentation incorrecte de l’encodage de publicExponent

Conclusion

  • ACME est extrêmement complexe, et son implémentation manuelle demande énormément d’essais, d’erreurs et d’efforts
  • Malgré cela, elle partage le fait qu’elle a réussi à abandonner le certificat à l’ancienne pour passer à une approche entièrement automatisée
  • Elle ajoute en plaisantant que cette complexité est peut-être une façon de garantir l’emploi de quelqu’un

1 commentaires

 
GN⁺ 2025-05-24
Commentaire Hacker News
  • Je suis le lead technique de l’équipe SRE/infra de Let’s Encrypt, donc je passe beaucoup de temps à réfléchir à ce genre de problèmes
    JSON Web Signature est vraiment un format délicat, et l’API ACME est aussi extrêmement attachée au côté RESTful
    Si c’était moi qui l’avais conçue, je ne l’aurais pas faite comme ça
    Je pense que ce résultat vient à la fois de la volonté de l’IETF de réutiliser un maximum de standards IETF et d’un design produit par comité
    Avec quelques bibliothèques pour JSON, JWS et HTTP, ça va déjà beaucoup mieux, mais en C en particulier, même ces bibliothèques ne sont pas simples à utiliser
    Le langage des RFC est lui-même complexe et renvoie souvent à d’autres documents, donc nous travaillons aussi sur un client interactif et une documentation dédiée pour aider sur ce point

    • J’ai du mal à comprendre l’idée que JSON Web Signature serait un format délicat
      Je travaille souvent avec des choses compliquées comme ASN.1, Kerberos ou la PKI, et je ne trouve pas JWS particulièrement difficile
      Même en l’implémentant directement en code, ça me paraît bien plus simple que S/MIME, CMS ou Kerberos
      Il faudrait expliquer plus précisément en quoi JWS est « délicat »
      Si le problème est plutôt du côté des JWT, le point central me semble surtout être que la manière dont un user agent HTTP doit recevoir ou demander un JWT n’est pas vraiment standardisée

    • J’ai vu quelqu’un dire que « si on veut émettre plus de 3 certificats, il faut payer », mais je l’utilise depuis cinq ans sans jamais avoir reçu de facture, donc ça ressemble à un malentendu ou à une information erronée

  • En parlant du fait de traiter « e=AQAB » au lieu de « e=65537 », explication que la cause vient des limites de JSON pour manipuler correctement les nombres
    Si on passe une très grande valeur comme 4723476276172647362476274672164762476438 à un parseur JSON, la plupart vont la tronquer silencieusement en entier 64 bits ou en float, ou au mieux renvoyer une erreur
    Un langage comme Common Lisp la gérerait bien, mais en pratique peu de gens développent dans ce genre d’environnement
    Donc, pour transporter de grands nombres de façon fiable en JSON, il vaut presque mieux les convertir en tableaux d’octets encodés en base64
    Même si tout semble fonctionner sans problème, c’est une source classique de failles de sécurité, donc traiter tous les nombres d’un protocole ainsi peut se défendre
    L’inconvénient, c’est qu’on perd alors la lisibilité humaine de JSON, et personnellement je pense qu’un S-Expression standardisé aurait été un meilleur choix
    Mais le monde a choisi JSON

    • Si on ne comprend pas pourquoi le monde a choisi JSON, j’ai l’impression qu’on choisit délibérément de l’ignorer
      JSON est facile à écrire, modifier et lire à la main pour la plupart des données
      À l’inverse, un Canonical S-Expression impose d’ajouter la longueur devant chaque élément, ce qui est extrêmement pénible à faire manuellement
      Pour écrire un S-Expression, il faut compter les caractères à la main et mettre à jour tous les préfixes, ce qui est très fastidieux
      Contrairement à ce qu’on pourrait croire, c’est cette facilité d’édition manuelle qui a permis à JSON de survivre
      Au passage, le parseur JSON de Ruby gère bien les grands nombres

    • Dans une appli C#, j’ai déjà subi un bug où le serializer JSON émettait un BigInt comme un nombre, puis JS le recevait et l’interprétait silencieusement de travers
      Je suis toujours surpris de voir que l’overflow soit le comportement standard plutôt qu’une erreur
      Depuis, j’ai pris l’habitude de toujours traiter comme des chaînes les nombres qui dépassent 32 bits

    • La comparaison entre {"e":"AQAB"} et {"e":65537} se tient, mais si on compare avec {"e":"65537"}, le résultat est lui aussi identique pour tous les parseurs JSON
      Que ce soit un nombre ou une chaîne, la conversion est explicite
      Bien sûr, si la valeur est trop grande pour tenir dans un double, cela devient un problème du langage ou du parseur, mais c’est distinct de la représentation elle-même

    • Le problème de JSON n’est pas le format lui-même, mais le fait que les parseurs ont été conçus au départ pour une correspondance avec les types JS
      Certains parseurs peuvent mieux s’en sortir, mais dans ce cas on perd la portabilité de JSON
      Convertir en Base64 pose le même problème aussi (puisque ce n’est pas standard)
      On peut faire du parsing personnalisé avec replacer et reviver, mais rien ne garantit cette fonctionnalité partout
      Au fond, l’hypothèse selon laquelle JSON doit être interprété par un parseur standard est la vraie source des erreurs
      Même si on appelait ça autrement que JSON, les gens l’enverraient quand même dans un parseur dès lors que ça y ressemble

    • En Go, le type json.Number permet de décoder les nombres en chaînes sans perte
      Présentation de l’un de mes types decimal à précision arbitraire préférés https://github.com/ncruces/decimal?tab=readme-ov-file#decimal-arithmetic
      À moitié en plaisantant, je ne vois pas bien en quoi un S-Expression serait meilleur ici
      Même parmi les Lisp, certains ne prennent pas en charge l’arithmétique à précision arbitraire

  • Je trouvais étrange la raison pour laquelle l’auteur se montrait si critique envers ACME et plusieurs clients
    Cela ne ressemblait pas à une simple question de compétence d’usage, donc j’ai supposé qu’il y avait une aversion plus générale envers le concept d’ACME ou tout l’écosystème autour
    Nous l’utilisons aussi sur quelques sites basés sur LE depuis 2019 et avons essayé plusieurs clients ACME
    Par exemple, Crypt-LE convenait bien à notre usage, et comme le64 ne suffisait pas pour l’intégration avec Sectigo ACME, nous avons aussi testé certbot, lego, posh-acme et d’autres
    Au final, nous avons utilisé certbot après avoir corrigé un problème d’environnement GHA, et posh-acme était bien aussi
    En relisant, il m’est apparu que le ton tranchant de l’auteur visait non pas ACME ou les clients, mais la spécification elle-même
    L’idée d’ACME est bonne, mais son implémentation et son application concrète sont décevantes, telle est la conclusion

    • Je pense avoir un point de vue proche de celui de l’auteur
      Citation de son propos selon lequel « beaucoup de clients existants sont du code dangereux, et je ne leur fais pas assez confiance pour les exécuter avec les droits root sur mon serveur »
      Dans des opérations sensibles du point de vue de la sécurité, cette prudence me paraît justifiée

    • Pour ceux qui avaient du mal à saisir le ton du billet original, partage de liens vers d’anciens billets donnant davantage de contexte

    • Beaucoup de gens n’aiment pas faire tourner sur leur serveur quelque chose qu’ils ne comprennent pas, et je partage en partie ce sentiment
      Mais la sécurité est un jeu du chat et de la souris, donc elle évolue forcément en permanence, et il faut bien suivre
      Heureusement, ACME laisse la liberté d’écrire son propre client
      Il n’est pas obligatoire d’utiliser certbot, et ce n’est pas non plus une architecture qui verrouille vos ressources comme un TPM

  • Pour quelqu’un qui veut implémenter un client ACME depuis zéro, retour d’expérience indiquant que lire directement les RFC (et les documents JOSE associés) est plus accessible qu’on pourrait le croire
    Ayant lui-même fait une implémentation directe, il partage aussi un billet expliquant le flux ACME v2 https://www.arnavion.dev/blog/2019-06-01-how-does-acme-v2-work/
    Cela ne remplace pas la RFC officielle, mais ce billet peut servir de référence de navigation, comme un organigramme et un index par type d’opération

    • Un client ACME a aussi été implémenté directement comme projet final d’un cours de sécurité au MIT https://css.csail.mit.edu/6.858/2023/labs/lab5.html

    • Moquerie sur cette étrange réalité où, au lieu de lire soigneusement les manuels, on peut gagner davantage de points Internet en publiant sur Hacker News un billet qui explique tout le processus en anglais

  • Remerciements à l’auteur pour avoir souligné la complexité croissante des protocoles d’infrastructure web
    Ces standards ne sont pas seulement une charge pour les développeurs qui doivent simplement utiliser un outil ou un client, ils fonctionnent aussi comme une « barrière réglementaire » qui finit par réserver la capacité d’exploiter Internet aux grandes entreprises déjà en place
    ACME seul n’est pas une barrière d’entrée infranchissable, mais l’accumulation de ce genre de contraintes finit par former un mur

    • Optimisme exprimé sur le fait qu’il existe des implémentations open source de tous ces protocoles, et que les progrès de l’IA devraient continuer à abaisser ce genre de barrières
  • OpenBSD dispose d’un client ACME très simple et léger, inclus dans l’OS de base
    Il aurait été créé parce que les alternatives existantes étaient trop lourdes et contraires à la philosophie Unix
    Dommage que l’auteur ne semble pas avoir examiné cette piste
    Avec un peu d’effort, il devrait probablement être portable aussi vers d’autres OS

    • À l’inverse, je pense que ce client OpenBSD illustre plutôt le fait que la philosophie OpenBSD ne comprend pas vraiment pourquoi la sécurité est devenue si complexe
      Ce client s’installe et s’utilise sur la machine concernée, avec une séparation visant à empêcher les composants de s’influencer mutuellement
      Mais le protocole ACME lui-même permet une séparation complète (air-gapping) : le serveur web, le demandeur de certificat et le serveur DNS peuvent parfaitement vivre dans des environnements distincts
      Si l’on n’utilise pas le client intégré OpenBSD, c’est peut-être plus complexe, mais du point de vue des principes de conception de sécurité, c’est supérieur
      « Il suffit d’installer OpenBSD » n’est qu’une solution de facilité

    • Mention de uacme (https://github.com/ndilieto/uacme)
      C’est du code C léger, adopté comme alternative fiable après avoir trop souffert des problèmes de dépendances du client Python de LE

    • Retour d’expérience de quelqu’un qui utilise directement le client ACME d’OpenBSD et indique qu’il fonctionne très bien

  • La recommandation de « générer une clé privée RSA 4096 bits » ne ferait surtout que ralentir les visiteurs, alors que la sécurité réelle reste au niveau de 2048 bits
    Mieux vaut insister sur l’usage de certificats leaf en RSA 2048 bits

    • Question posée : le 4096 bits n’offre-t-il pas davantage de résistance face à de la capture passive suivie d’un déchiffrement futur ?
      Interrogation aussi sur le point de savoir si la sécurité du certificat intermédiaire influe sur ce type d’attaque asynchrone

    • L’hébergeur web ne prenant en charge que les clés RSA, quelqu’un utilise exprès du RSA 4096 bits pour pousser à ajouter plus vite la prise en charge des clés EC

  • Faire ce genre de choses soi-même permet certes de progresser, mais le ton du texte donne surtout l’impression que l’auteur exprime sa frustration envers le protocole ou le processus de mise en place de Let’s Encrypt
    On peut pourtant automatiser suffisamment avec une bibliothèque ACME légère (https://github.com/jmccl/acme-lw entre autres), donc on peut se demander pourquoi rendre cela aussi pénible

    • Affirmation catégorique que SSL est vraiment un « amas brûlant et figé de chaos »
      Tous les problèmes de champs plats ou de bitfields viennent de l’héritage historique d’ASN.1/X.509, avec une complexité mathématique sérieuse, et toutes les bibliothèques et logiciels restent prisonniers des limites techniques des années 80
      Il y a eu une dernière occasion de nettoyer ce chaos lors de l’arrivée de Let’s Encrypt ou de HTTP/2, mais en pratique une ACME CA peut déjà fonctionner avec des scripts shell, OpenSSL et un peu d’alcool, et la compatibilité avec les logiciels existants bloquait toute vraie refonte
  • Partage d’une expérience montrant que la pression pour passer à HTTPS ne cesse d’augmenter
    Par exemple, dans WhatsApp, les liens HTTP ne peuvent désormais plus être ouverts

    • Suggestion d’utiliser du proxy et du caching pour réduire la charge de trafic, ce qui peut être une bonne approche pour de petits serveurs

    • Insistance sur le fait que, même si ACME est complexe, c’est toujours largement préférable à l’absence de prise en charge de TLS

  • « Clés RSA, condensat SHA256, signature RSA, du base64 qui n’est en fait pas vraiment du base64, concaténation de chaînes, du JSON dans du JSON, usage de l’en-tête Location comme identifiant au lieu d’une redirection 301, requêtes HEAD pour une seule valeur d’en-tête, et une requête séparée juste pour obtenir un nonce avant chaque requête »
    « Et il reste encore des étapes encore plus compliquées : créer l’ordre de certificat, gérer les autorisations et les challenges, faire l’empreinte de clé, construire l’enregistrement TXT, etc. »
    Message de soutien disant que le niveau de complexité est presque difficile à croire, et remerciant pour le partage de ce récapitulatif