3 pontos por GN⁺ 2025-05-24 | 1 comentários | Compartilhar no WhatsApp
  • A autora tinha resistência ao protocolo ACME há anos por causa da complexidade e dos riscos de implementação
  • Os clientes ACME existentes frequentemente continham código inseguro ou difícil de entender, então ela evitava executá-los por conta própria
  • Mas, com a queda de qualidade e o aumento de preços da registradora Gandi, ela acabou implementando sua própria ferramenta de renovação de certificados
  • Depois de inúmeras tentativas e erros, conseguiu concluir com sucesso uma ferramenta para emitir certificados diretamente via Let's Encrypt
  • Na parte final do texto, ela explica em detalhes o funcionamento real do protocolo ACME e os detalhes de implementação de baixo nível, como JSON, base64 e assinaturas

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

Contexto e motivação

  • No início de 2023, ela explicou por que continuava mantendo um certificado old-school, mas em 2025 compartilha por que decidiu abandonar esse modelo
  • A resistência ao protocolo ACME existia desde 2018, e tecnologias web complexas e esquemas de codificação difíceis eram uma grande barreira
  • A maioria dos clientes ACME parecia código difícil de confiar, e ela julgava arriscado executá-los com privilégios de root
  • Depois que a Gandi foi adquirida por private equity, a qualidade caiu e os preços subiram, então não havia mais razão para manter o certificado antigo

Início da implementação própria

  • Em vez de usar ferramentas existentes, ela começou implementando diretamente pequenas funções utilitárias, uma por uma
  • Começou pelo trabalho de encapsular a biblioteca JSON para C chamada jansson para poder usá-la em C++
  • Avaliou várias bibliotecas para gerar JWK (estrutura de chave), mas a maioria não ajudou, então decidiu implementar por conta própria
  • No meio do caminho, parou e recomeçou várias vezes, conectando gradualmente os pequenos componentes

Ambiente de testes e aplicação real

  • Para não mexer diretamente nos servidores reais da Let's Encrypt, usou em um ambiente isolado o servidor ACME de testes chamado "pebble"

  • Depois de muitos fracassos, concluiu uma ferramenta inicial que recebe um CSR e emite um certificado, e

    • teste bem-sucedido no servidor de staging da Let's Encrypt
    • sucesso também em produção
    • aplicação concluída no site real

Explicação detalhada do protocolo ACME

  • Gera uma chave RSA e cria um CSR (Certificate Signing Request) incluindo CN e SAN
  • Faz o parsing do JSON da URL de diretório do ACME para extrair endpoints como newNonce, newAccount e newOrder
  • Extrai da chave privada o modulus e o public exponent e os converte para codificação base64url adequada para a web
  • Depois de gerar o JWK, aplica assinatura RSA SHA256 junto com o payload JSON
  • Obtém um Nonce com uma requisição HTTP HEAD e depois envia a requisição assinada via POST para criar a conta
  • O cabeçalho Location da resposta não é um redirecionamento real, mas sim usado como URL identificadora da conta

A complexidade do protocolo ACME

  • Apesar de ser apenas emissão de certificado, ainda envolve
    • hash SHA256, base64web, JSON dentro de JSON, assinatura RSA
    • requisição HEAD, identificação da conta via cabeçalho Location, necessidade de Nonce de uso único etc.
  • Ela observa que ainda nem chegou a tratar de pedido de certificado, prova de posse do domínio (como registro TXT), conclusão da validação e afins
  • Alguns clientes chegam a funcionar mesmo implementando a codificação do publicExponent de forma errada, o que também aponta para uma certa frouxidão do padrão

Conclusão

  • O ACME é extremamente complexo, e implementá-lo manualmente exige enorme tentativa e erro, além de muito esforço
  • Mesmo assim, ela compartilha que conseguiu abandonar o certificado old-school e migrar com sucesso para um modelo totalmente automatizado
  • Também acrescenta em tom de brincadeira que talvez toda essa complexidade exista para garantir o emprego de alguém

1 comentários

 
GN⁺ 2025-05-24
Comentários do Hacker News
  • Sou tech lead da equipe de SRE/infra do Let’s Encrypt e penso bastante sobre esses problemas
    JSON Web Signature é realmente um formato complicado, e a API ACME também leva o fato de ser RESTful muito a sério
    Se eu tivesse projetado isso diretamente, não teria feito dessa forma
    Acho que esse formato surgiu em parte da intenção da IETF de reutilizar muitos padrões da própria IETF, além de um certo design por comitê
    Com algumas bibliotecas de JSON, JWS e HTTP já melhora bastante, mas o problema é que, especialmente em C, até essas bibliotecas não são fáceis de usar
    A própria linguagem das RFCs é complexa e muitas vezes referencia outros documentos, então estamos trabalhando separadamente em um cliente interativo e em documentação para ajudar nisso

    • Não entendo muito bem a afirmação de que JSON Web Signature é um formato complicado
      Eu lido bastante com coisas complexas como ASN.1, Kerberos e PKI, e não acho que JWS seja um formato tão difícil assim
      Mesmo escrevendo tudo diretamente em código, ainda me parece muito mais simples do que S/MIME, CMS, Kerberos e afins
      Seria preciso explicar melhor em que sentido JWS é “complicado”
      Se o problema for JWT, acho que o ponto mais central é que não está bem definido de forma padronizada como um user agent HTTP deve receber ou solicitar JWT

    • Vi alguém dizer que “para emitir mais de 3 certificados você precisa pagar”, mas usei isso nos últimos 5 anos e nunca recebi uma cobrança; isso parece mal-entendido ou informação incorreta

  • Ao falar sobre tratar como “e=AQAB” em vez de “e=65537”, explicam que isso vem da característica do JSON de não lidar bem com números
    Se você passar para um parser JSON um valor muito grande como 4723476276172647362476274672164762476438, a maioria dos parsers simplesmente vai truncar silenciosamente para um inteiro de 64 bits ou para float, ou, com sorte, vai gerar erro
    Em linguagens como Common Lisp isso seria tratado corretamente, mas na prática não há tanta gente desenvolvendo nesse tipo de ambiente
    Por isso, para transmitir números grandes com segurança em JSON, talvez seja melhor convertê-los em arrays de bytes via base64
    Mesmo quando parece que tudo funciona sem problemas, isso acaba sendo a origem de vários problemas de segurança, então faz sentido tratar todos os números do protocolo dessa forma
    A desvantagem é que isso destrói a legibilidade humana do JSON, e pessoalmente acho que S-Expression padronizado teria sido uma escolha muito melhor
    Mas o mundo escolheu JSON

    • Se alguém não entende por que o mundo escolheu JSON, acho que está ignorando isso de propósito
      JSON permite que, para a maior parte dos dados, pessoas escrevam, editem e leiam tudo manualmente com facilidade
      Já o Canonical S-Expression exige informação de comprimento antes de cada elemento, então o trabalho manual fica bem incômodo
      Para escrever S-Expression, você precisa contar caracteres um por um e ajustar os prefixos, o que é bem irritante
      Ao contrário do que se imaginaria, essa facilidade de escrita e edição manual é a razão de o JSON ter sobrevivido
      Aliás, o parser JSON do Ruby lida bem com números grandes

    • Já sofri com um bug em que o serializer JSON de um app C# emitia BigInt como número, o JS recebia isso e interpretava tudo errado em silêncio
      Ainda me surpreende que overflow seja o comportamento padrão em vez de erro
      Desde então, criei o hábito de sempre tratar como string qualquer número maior que 32 bits

    • A comparação entre {"e":"AQAB"} e {"e":65537} faz algum sentido, mas se comparar com {"e":"65537"}, o resultado processado por qualquer parser JSON também será igual
      Seja número ou string, a conversão é clara
      Claro, se o valor for grande demais para caber em um double, aí já existe um problema da linguagem ou do parser em si, mas isso é separado da forma de representação

    • Acho que o problema do JSON não está no formato em si, mas no fato de os parsers terem sido criados originalmente para mapear tipos do JS
      Alguns parsers até conseguem lidar bem com isso, mas nesse caso a portabilidade do JSON se perde
      Converter para Base64 gera o mesmo problema (porque foge do padrão)
      É possível fazer parsing customizado com replacer e reviver, mas não há garantia de que esse recurso exista em todos os ambientes
      No fim, a própria premissa de interpretar JSON com um parser padrão é a origem do erro
      Se isso se chamasse outro formato em vez de JSON, talvez o problema diminuísse, mas, se parecer JSON, as pessoas ainda vão querer jogar direto no parser

    • A linguagem Go consegue decodificar números para string sem perda usando o tipo json.Number
      Foi apresentado um dos meus tipos de decimal arbitrário quase “favoritos”: https://github.com/ncruces/decimal?tab=readme-ov-file#decimal-arithmetic
      Meio de brincadeira, não vejo muito bem por que S-Expression seria melhor nesse caso
      Até entre LISPs há casos sem suporte a aritmética de precisão arbitrária

  • Achei curioso o motivo de o autor ter sido tão crítico em relação ao ACME e a vários clientes
    Não parece ser apenas uma questão de falta de habilidade de uso, então imaginei que houvesse uma antipatia maior com o próprio conceito de ACME ou com todo o ecossistema em volta
    Nós também o adotamos em alguns sites com base no LE desde 2019 e, nesse período, testamos vários clientes ACME
    Por exemplo, o Crypt-LE serviu bem para nosso uso, e, ao tentar integrar com o ACME da Sectigo, como o le64 não bastava, usamos várias opções como certbot, lego e posh-acme
    No fim, acabamos corrigindo um problema de ambiente GHA no certbot e usando assim, e o posh-acme também foi bom
    Relendo, percebi que o tom afiado do autor não era contra o ACME ou os clientes, mas contra a própria especificação
    A conclusão é que a ideia do ACME é boa, mas a implementação e a aplicação prática decepcionam

    • Acho que tenho uma visão parecida com a do autor
      Cita a fala do autor de que “muitos clientes existentes são código perigoso e não são confiáveis o bastante para eu executá-los como root no meu servidor”
      Em tarefas sensíveis à segurança, acho esse tipo de cautela bastante razoável

    • Foi compartilhado um link para posts antigos que ajudam a contextualizar para quem teve dificuldade de entender o tom do texto original

    • Muita gente simplesmente não gosta de rodar no servidor algo que não entende, e eu também simpatizo com essa ideia
      Mas a área de segurança é um jogo de gato e rato, então, por natureza, ela está sempre mudando, e no fim não há como escapar de acompanhar isso
      Felizmente, o ACME dá liberdade para criar seu próprio cliente
      Não é obrigatório usar certbot, nem é uma estrutura que bloqueia seus recursos como um TPM

  • Para quem pretende implementar um cliente ACME do zero, compartilharam a experiência de que ler diretamente as RFCs (e documentos relacionados, como JOSE) é mais fácil do que parece
    A pessoa implementou isso por conta própria e também escreveu um texto resumindo o fluxo do ACME v2: https://www.arnavion.dev/blog/2019-06-01-how-does-acme-v2-work/
    Não substitui a RFC oficial, mas esse resumo pode ser útil como fluxograma e índice por tipo de operação

    • Também houve quem implementasse um cliente ACME como projeto final da disciplina de segurança do MIT: https://css.csail.mit.edu/6.858/2023/labs/lab5.html

    • Faz uma ironia com a estranha realidade em que, em vez de ler os manuais com calma, é mais vantajoso postar no Hacker News um texto explicando todo o processo em inglês para ganhar mais pontos de internet

  • Agradecem ao autor por apontar que a complexidade dos protocolos de infraestrutura web continua aumentando
    A ideia é que esses padrões não são apenas um peso para desenvolvedores que “só precisam usar” uma ferramenta ou cliente, mas acabam funcionando como uma espécie de “barreira regulatória”, estruturando a internet de modo que, no fim, só grandes empresas já estabelecidas consigam cumprir os requisitos para operá-la
    ACME sozinho talvez não seja uma barreira de entrada intransponível, mas tudo isso vai se acumulando e acaba virando um muro

    • Expressa otimismo de que esses protocolos têm implementações open source e que, com o avanço da IA, essas barreiras tendem a diminuir aos poucos
  • No OpenBSD existe um cliente ACME bem simples e leve incluído no sistema base
    Ouvi dizer que ele foi criado porque as alternativas existentes eram pesadas demais e contrariavam a filosofia Unix
    É uma pena que o autor aparentemente não tenha considerado essa opção
    Provavelmente, com um pouco de esforço, daria para portar isso para outros sistemas também

    • Por outro lado, acho que esse cliente do OpenBSD é justamente um caso de a filosofia do OpenBSD não entender por que segurança fica tão complexa
      Esse cliente foi feito para ser instalado e usado naquela máquina específica, com uma estrutura separada para que os componentes não interfiram uns nos outros
      Mas o protocolo ACME em si permite separação total (air-gapping), então o servidor web, o solicitante do certificado e o servidor DNS podem ficar em ambientes diferentes sem problema
      Se você não usar o cliente integrado do OpenBSD, talvez fique mais complexo, mas do ponto de vista de princípios de projeto de segurança isso é superior
      “Basta instalar OpenBSD e pronto” é só o caminho mais fácil

    • Também citam o uacme (https://github.com/ndilieto/uacme)
      É código C leve, e depois de muito sofrer com problemas na bateria por causa do cliente Python do LE, passaram a usá-lo com estabilidade como alternativa

    • Alguém relata que usa diretamente o cliente ACME do OpenBSD e que ele funciona muito bem

  • A recomendação de “gerar uma chave privada RSA de 4096 bits” na verdade só causa perda de desempenho para os visitantes, enquanto a segurança prática continua no nível de 2048 bits
    Enfatizam que é melhor usar um certificado leaf de 2048 bits

    • Pergunta se 4096 bits não daria mais resistência a captura passiva e descriptografia futura
      Também questiona se a segurança do certificado intermediário influencia ataques assíncronos

    • Como a hospedagem web só suportava chaves RSA, alguém passou a usar propositalmente RSA de 4096 bits para pressionar por suporte mais rápido a chaves EC

  • Fazer esse tipo de trabalho manualmente até ajuda a evoluir tecnicamente, mas o tom do texto do autor parece mais de irritação com o protocolo ou com o processo de implantação do Let’s Encrypt
    Dá para automatizar bem isso até com bibliotecas ACME leves (https://github.com/jmccl/acme-lw, por exemplo), então fica a dúvida de por que tornar tudo tão sofrido

    • SSL é realmente uma “bagunça quente e fossilizada”
      Os problemas de flags e bitfields são todos herança histórica do ASN.1/X.509, há uma complexidade matemática séria envolvida, e todas as bibliotecas e softwares continuam presos às limitações tecnológicas dos anos 80
      Houve uma última chance de limpar essa bagunça na adoção do LetsEncrypt ou na chegada do HTTP/2, mas, na prática, uma ACME CA pode ser montada só com shell script, OpenSSL e bebida, além de a compatibilidade com software legado atrapalhar qualquer salto maior
  • Compartilham a experiência de que a pressão para migrar tudo para HTTPS só aumenta
    Por exemplo, links HTTP no WhatsApp agora já não podem mais ser abertos

    • Sugere que usar proxy e cache pode reduzir a carga de tráfego, o que é uma boa saída para servidores pequenos

    • Reforça que, por mais complexo que o ACME seja, ele ainda é muito melhor do que não ter suporte a TLS

  • “Chave RSA, digest SHA256, assinatura RSA, base64 que na prática não é base64, concatenação de strings, JSON dentro de JSON, usar o header Location como identificador em vez de redirecionamento 301, requisição HEAD para um único valor de header, necessidade de uma requisição separada só para nonce antes de cada requisição, e assim por diante”
    “E ainda faltam etapas mais complexas, como criar a order do certificado, lidar com autorizações e challenges, thumbprint da chave, montar registro TXT etc.”
    Dizem que é uma complexidade quase inacreditável e agradecem por terem compartilhado esse resumo