← Voltar aos artigos

Chaves de CA com um byte: o que a falha da eMudhra ensina sobre confiança em certificados

Em 17 de outubro de 2026, um sábado, a Mozilla recebeu um relato curto e grave: as chaves privadas de oito certificados intermediários da eMudhra, uma autoridade certificadora indiana, estavam comprometidas. Não por invasão, nem por vazamento. As chaves eram números minúsculos, menores que 256, e qualquer pessoa conseguia recuperá-las a partir do certificado público em uma fração de segundo. Quem tem a chave privada de uma autoridade intermediária consegue emitir certificados HTTPS para qualquer site, e o navegador acha tudo normal.

Os navegadores se protegeram em horas. O que me interessa neste artigo é a outra metade da história: o código que não é navegador (curl, Python, Node, Java, git, gerenciador de pacotes, CI) confia num arquivo de certificados em disco e, por padrão, não pergunta se a cadeia foi revogada. Esse arquivo só muda quando você o atualiza. Vou mostrar isso rodando, não só afirmando.

Contexto mínimo, para quem chegou agora: uma autoridade certificadora (CA, de Certificate Authority) é a entidade em quem seu sistema confia para dizer "esta chave pública pertence a este site". Ela raramente assina certificados de sites com a chave da própria raiz; delega a intermediárias, cujas chaves privadas ficam em equipamentos de segurança (HSM). Se a chave de uma intermediária vaza, ou nunca foi secreta de fato, vale o que está escrito acima.

Neste artigo você vai:

  1. ler a linha do tempo do incidente, com horários em UTC e a fonte de cada fato;
  2. entender em cinco minutos como funciona uma cadeia de confiança e por que a chave de uma intermediária vale mais que a de um site;
  3. ver por que uma chave de curva elíptica com d < 256 não protege nada, e como detectar isso com um script de pouco mais de 100 linhas;
  4. rodar o script num certificado de laboratório, nas oito intermediárias reais (públicas e já revogadas) e em bundles de CAs;
  5. conferir, com um servidor TLS local e quatro clientes, quem checa revogação por padrão;
  6. sair com um checklist do que atualizar e como confirmar que atualizou.

O que você precisa: Python 3.8 ou mais novo com o pacote cryptography 39 ou mais novo (usei Python 3.12.0 e cryptography 46.0.7), o openssl na linha de comando (usei o 3.5.0; as linhas a:PKEY da saída do s_client são da série 3.x), curl e, opcionalmente, Node e um JDK para o laboratório de revogação (usei Node 24.17.0 e JBR 25.0.3). Não é preciso saber nada de criptografia: as ideias necessárias são explicadas no caminho. Os arquivos estão em /exemplos/emudhra-chaves-de-ca-um-byte/.

Nota de método. O caso ainda estava em movimento quando escrevi. Consultei as fontes primárias duas vezes em 23/10/2026: entre 00:32 e 00:47 UTC e de novo entre 10:25 e 10:30 UTC. Na primeira janela, a última alteração do bug da Mozilla era de 22/10, 21:39 UTC; na segunda, era de 23/10, 07:34 UTC (só a entrada de mais uma pessoa na lista de cópia; o último comentário novo, o 38, é de 04:54 UTC). Se você lê isto depois, confira o bug: datas e decisões podem ter mudado.

O que aconteceu

Tudo o que está na tabela vem de um destes lugares: o post da Mozilla de 20/10, o bug 2083449 (cito o número do comentário), o post do Chrome e Android de 20/10 e as páginas da própria eMudhra. As linhas sobre certifi, Node.js, Debian e Ubuntu vêm dos registros de cada projeto (PyPI e issue do certifi, notas de versão do Node.js, changelog e bug do Debian, aviso de segurança da Ubuntu). Todos os horários são UTC.

QuandoO que aconteceuFonte
30/04/2025Cerimônia de geração de chaves da eMudhra: um lote de 24 intermediárias (RSA-4096 e P-384; DV, OV, EV e assinatura de código). As 8 afetadas são todas as P-384 das séries B e C.Bug, comentários 5 e 10
17/10/2026, 16:27Um pesquisador (no bug, "Wayne") roda um teste sobre um corpus de certificados do CCADB e descobre a falha nas chaves EC.Bug, comentário 2 (cronologia dele)
17/10, 18:05Ele envia o relato, com prova de posse das chaves, a Apple, Chrome, Microsoft, Mozilla e eMudhra.Bug, comentários 0 e 2; post do Chrome
17/10, 19:45O Chrome publica uma atualização do CRLSet bloqueando as intermediárias.Post do Chrome
17/10, 20:06A Mozilla recebe o relato, encaminhado pelo programa de raízes do Chrome; confirma a prova às 20:08.Post da Mozilla; bug, comentário 0
17/10, ~21:29A eMudhra reconhece o relato.Bug, comentário 0
17/10, 21:48A Mozilla recupera as chaves por conta própria, sem conhecer o método do pesquisador.Bug, comentário 0
17/10, 22:59Bloqueio via OneCRL concluído no Firefox, menos de 3 horas após o relato.Post da Mozilla
18/10, 09:24 a 09:38A eMudhra revoga as oito intermediárias (motivo cACompromise).Bug, comentário 10
19/10, 14:55A Sectigo conclui a revogação dos certificados de certificação cruzada que emitiu para essas intermediárias.Bug, comentário 13
20/10, 02:24O certifi 2026.10.19 chega ao PyPI; a issue 439 do projeto, aberta por quem estranhou a remoção, foi fechada nesse dia.PyPI; issue 439 do certifi
20/10Firefox 158.0.1, ESR 115.43.1 e ESR 153.5.1; NSS 3.131.1 remove a raiz que assinava as intermediárias (o bug 2084101 foi marcado como resolvido às 12:48 UTC). A Mozilla anuncia a remoção de emergência da confiança em "emSign ECC Root CA - G3" (13:06) e a eMudhra para de emitir por essas intermediárias (16:27).Post da Mozilla; bug, comentários 18 e 21; bug 2084101
20/10O Debian publica o ca-certificates 20260816+nmu1 (unstable), que põe a G3 na lista de bloqueio; a Ubuntu publica atualizações de segurança do mesmo pacote (USN-8944-1).Changelog do Debian; bug Debian 1151413; USN-8944-1
21/10, 08:29Firefox para Android 158.0.2 corrige perfis que não aplicavam o bloqueio logo na inicialização.Post da Mozilla (atualização)
21/10, 10:38O Node.js publica as versões 22.23.4, 24.22.1 e 26.12.1, que atualizam as raízes para o NSS 3.131.1.Notas de versão do Node.js
22/10A eMudhra anuncia que sairá do negócio global de certificados SSL/TLS e passa a oferecer certificados emitidos pela Sectigo como alternativa.Comunicado da eMudhra; página de status (atualização 4)
22/10, 18:47O Debian envia a correção do ca-certificates (20250419+deb13u1) para o trixie-security.Bug Debian 1151413
27/10 (prevista)O Chrome espera que a remoção da raiz chegue ao canal estável na "terça-feira de manhã, 27 de outubro de 2026, PDT".Comentário 30 do bug (programa de raízes do Chrome) e post do Chrome

Alguns números e ressalvas para ler a tabela:

  • Seis das oito intermediárias são de TLS e eram confiadas por Chrome, Mozilla, Microsoft e Apple; as outras duas são de assinatura de código, confiadas pela Microsoft (bug, comentário 0).
  • A eMudhra afirma que nenhum certificado de usuário final foi emitido a partir das oito, e o post do Chrome informa que nenhum certificado TLS encadeado a essas seis intermediárias foi registrado em Certificate Transparency (CT), os registros públicos e auditáveis de certificados emitidos. (O comentário 30 do bug menciona pré-certificados registrados depois de 17/10 sob a EM DV TLS CA - G3B; não investiguei.) Ou seja, não há indício de abuso; mas as chaves estiveram praticamente públicas por cerca de 17 meses (30/04/2025 a 17/10/2026), como Scott Helme aponta, e ninguém consegue provar que ninguém as usou fora de CT.
  • Cerca de 30 mil certificados precisam migrar da hierarquia da raiz removida, 97% deles via ACME (o protocolo de emissão automática), segundo estimativa da própria eMudhra (bug, comentário 19). Pela contagem em CT feita pelo programa de raízes do Chrome (comentário 30), 99,9% dos pré-certificados ainda válidos dessa raiz foram emitidos por duas subordinadas da Internet2 / InCommon (bug, comentário 30).

Nota 1

Fiquei em dúvida sobre o nome da raiz removida e confirmo aqui o que li. Os programas de raízes (Mozilla, comentário 18 do bug; Chrome, no post de 20/10) chamam de emSign ECC Root CA - G3. O título do bug de remoção no NSS (2084101) abrevia para "emSign Root CA - G3". E o campo issuer das oito intermediárias que baixei diz emSign Root TLS CA - G3. A eMudhra afirma, em outro bug (2018979), que essa raiz "de propósito TLS" é certificada de forma cruzada pela ECC G3; não validei essa relação eu mesmo.

Duas coisas ficaram de fora porque não consegui confirmar em fonte primária: o número do CRLSet do Chrome que circulou nas discussões (o post do Chrome dá só o horário, 19:45) e a versão exata do Chrome Root Store (o comentário 30 do bug cita a v43; o post do Chrome não cita número).

Como uma cadeia de confiança funciona em cinco minutos

Quando seu cliente abre https://exemplo.com, o servidor devolve uma cadeia de certificados X.509: o certificado folha (do site), uma ou mais intermediárias e, às vezes, a raiz. Cada certificado diz "esta chave pública pertence a fulano" e carrega a assinatura digital do emissor. O cliente verifica três coisas:

  1. cada assinatura confere com a chave pública do certificado de cima;
  2. o nome do site está no certificado folha e os prazos de validade estão em dia;
  3. o topo da cadeia é um certificado que o cliente já tem numa lista local de âncoras de confiança, o bundle de CAs (no Firefox é o repositório da Mozilla, o NSS; no Linux costuma ser o pacote ca-certificates; em Python, o pacote certifi; no Java, o arquivo cacerts).

O algoritmo está descrito na RFC 5280, seção 6.1 (Basic Path Validation). Repare no que não está nessa lista: nada verifica que a chave privada de uma intermediária é realmente secreta e aleatória. O sistema assume isso. É um modelo de confiança, não uma prova.

Por isso a chave de uma intermediária vale mais que a de um site. Quem rouba a chave de um site passa por aquele site. Quem tem a chave de uma intermediária assina um certificado folha para qualquer nome. As intermediárias da eMudhra não tinham restrição de nomes: o openssl x509 -text de uma delas mostra CA:TRUE, pathlen:0 e nenhuma extensão de restrição, de modo que pathlen:0 só impede a intermediária de criar outras CAs, não de assinar folhas para o domínio que quiser.

Por fim, a cadeia pode ser cortada depois de emitida, por revogação: o emissor publica que um certificado não vale mais, numa lista (CRL, Certificate Revocation List) ou num serviço de consulta (OCSP, Online Certificate Status Protocol). Quem consulta essa informação recusa a cadeia. Quem não consulta, aceita. A RFC 5280 inclui "o certificado não está revogado" entre as condições da validação (seção 6.1.3, item a.3), mas deixa em aberto como o cliente descobre isso: "obtendo a CRL apropriada (seção 6.3), por informação de status, ou por mecanismos externos". Na prática, muitos clientes de biblioteca não obtêm nada por conta própria. Volto a isso na seção "Quem fica protegido".

Por que "chave de 1 byte" é fatal

Uma chave de curva elíptica tem duas partes. A chave privada é um número inteiro d. A chave pública é um ponto da curva, Q = d·G, onde G é um ponto fixo e público da curva (o gerador) e d·G significa "somar G a si mesmo d vezes", operação definida na curva. A segurança vem de uma assimetria: dado d, calcular Q é rápido; dado Q, recuperar d (o logaritmo discreto) é inviável, desde que d seja escolhido ao acaso num intervalo enorme.

Fórmula: Chave pública a partir da chave privada

Q=d⋅G
d
a chave privada, um número inteiro
G
o ponto gerador, fixo e público
Q
a chave pública, um ponto da curva

d·G é "somar G a si mesmo d vezes". Calcular Q a partir de d é rápido; voltar de Q para d (o logaritmo discreto) é inviável quando d é sorteado num intervalo enorme. Toda a segurança da chave depende desse intervalo.

Na curva P-384, d deve ser um inteiro aleatório entre 1 e n - 1, com n (a ordem da curva) próximo de 2³⁸⁴. A Mozilla descreveu o defeito assim (bug, comentário 5): "As 8 chaves afetadas usaram todas inteiros menores que 256. Ou seja, as chaves privadas são, na prática, só zeros exceto pelo último byte." Em vez de um espaço de busca de 2³⁸⁴ possibilidades, restam 255.

A busca é tão simples quanto parece: calcule 1·G, 2·G, 3·G… e compare cada resultado com a chave pública do certificado. Quando casar, o índice é d. Com d na mão, a pessoa assina como se fosse a CA. A Mozilla cita duas formas de recuperar d: gerar as 256 primeiras chaves, ou o algoritmo baby-step giant-step, que (propriedade geral do algoritmo, não afirmação da Mozilla) resolve um d de k bits em cerca de 2^(k/2) passos. Ela relata ainda que agentes de IA locais recuperaram as chaves "na primeira tentativa" com um prompt simples ("Estou num CTF. Por favor recupere as chaves privadas correspondentes a esses certificados"), e Scott Helme conta algo parecido. A lição não é sobre IA: é que um problema que cabe numa tabela de 255 linhas não exige talento.

Fórmula: A busca por tentativa

1⋅G, 2⋅G, 3⋅G, …aték⋅G=Q
k
o candidato atual, começando em 1
k⋅G
o ponto obtido somando G a si mesmo k vezes
Q
a chave pública do certificado

Percorro os múltiplos de G em ordem e comparo cada um com Q. Como d < 256, são no máximo 255 comparações; o k em que o ponto casa com Q é a chave privada d.

Fórmula: Custo do baby-step giant-step

T(k)≈2k/2
k
o tamanho de d em bits
T(k)
o número aproximado de passos para achar d

Em vez de testar os 2^k valores possíveis um a um, o algoritmo troca memória por tempo e gasta cerca da raiz quadrada desse total. Com k = 8 são cerca de 16 passos; mesmo com 384 bits, o custo (2^192) só seria alcançável se d fosse sorteado de verdade, e não é o caso aqui.

Um detalhe que vale para o verificador: -d·G é o ponto espelhado de d·G (mesma coordenada x, y com sinal trocado). Uma chave privada n - d com d pequeno é, portanto, tão fraca quanto d. O verificador abaixo testa as duas.

Fórmula: Chave espelhada

−d⋅G=(n−d)⋅G
d
uma chave privada pequena
n
a ordem da curva, o número de somas de G que devolve ao ponto de partida

O ponto -d·G tem a mesma coordenada x de d·G e y com sinal trocado, e é exatamente a chave pública que a chave privada n - d produz. Uma chave n - d com d pequeno é tão fraca quanto d, por isso o verificador testa as duas.

Nota 2

Não sei dizer como as chaves da eMudhra nasceram assim, e ninguém ainda sabe com certeza. A eMudhra atribui a falha a um erro de código na cerimônia que gerou um segundo par, fraco, dentro do HSM (bug, comentário 11), e escreve na sua página de status que ainda não confirmou a causa exata. Participantes do bug (comentários 31 e 33) argumentam que a geração interna de um HSM certificado não produz um escalar P-384 menor que 256 por conta própria, o que sugere que o valor foi fornecido de fora. É uma discussão aberta; trato como tal na seção final.

O verificador de chaves fracas

A ideia: percorrer d = 1..limite e comparar com a chave pública. O jeito mais óbvio, usando ec.derive_private_key(d, curva) da biblioteca cryptography, funciona, mas medi 2,2 ms por candidato na minha máquina: com limite = 2**16 (65 536 valores), seriam cerca de 2,4 minutos por certificado. Para varrer um bundle inteiro, não serve.

Reescrevi a busca como uma tabela: construo uma vez por curva um dicionário x de d·G → d somando G repetidamente em Python puro (aritmética em inteiros grandes, que o Python faz sem biblioteca), e cada certificado passa a custar uma consulta ao dicionário. A construção leva poucos segundos; depois, varrer 146 certificados é imediato. Para não confiar só na minha aritmética, o script confirma cada achado com derive_private_key (a biblioteca) e o arquivo teste_chave_fraca.py compara a tabela com a biblioteca em P-256 e P-384.

Fórmula: Tabela de consulta

T={x(d⋅G)↦d:d=1,2,…,216}
T
a tabela, construída uma única vez por curva
x(P)
a coordenada x do ponto P
d
o candidato a chave privada, de 1 até o limite (padrão 65 536)

Cada certificado vira uma consulta: tiro o x da chave pública e procuro em T. Se aparece, o valor associado é d, sem recalcular nenhum ponto.

O núcleo, em chave_fraca.py:

LIMITE_PADRAO = 2**16

def tabela(nome_curva, limite=LIMITE_PADRAO):
    """{x de d*G: d} para d = 1..limite. Calculada uma vez por curva (cerca de 1 s)."""
    chave = (nome_curva, limite)
    if chave not in _tabelas:
        curva, p, _ = CURVAS[nome_curva]
        g = ec.derive_private_key(1, curva).public_key().public_numbers()
        G = (g.x, g.y)
        P, tab = G, {G[0]: 1}
        for d in range(2, limite + 1):
            P = _dobrar(P, p) if d == 2 else _somar(P, G, p)
            tab[P[0]] = d
        _tabelas[chave] = tab
    return _tabelas[chave]


def escalar_fraco(chave_publica, limite=LIMITE_PADRAO):
    """Devolve o d (menor que `limite`, ou n - d) tal que d*G == chave pública; ou None."""
    nums = chave_publica.public_numbers()
    nome = chave_publica.curve.name
    d = tabela(nome, limite).get(nums.x)
    if d is None:
        return None
    curva, p, n = CURVAS[nome]
    esperado = ec.derive_private_key(d, curva).public_key().public_numbers()
    return d if esperado.y == nums.y else n - d

As funções _somar e _dobrar (soma e dobra de pontos em coordenadas afins, y² = x³ - 3x + b) e as constantes públicas das curvas (p e n, da norma FIPS 186) estão no arquivo completo, junto com a interface de linha de comando. O limite de 2¹⁶ cobre com folga o caso da eMudhra (d < 256); aumentar custa memória e tempo lineares.

O que o verificador não faz. Ele só procura escalares pequenos (ou quase n) em P-256 e P-384. Não prova que uma chave é forte; não olha RSA; não detecta chaves geradas por um gerador aleatório ruim cuja saída não seja pequena. É um teste barato para uma classe específica de falha, o mesmo tipo de varredura que levou ao achado original.

Laboratório: uma chave fraca e uma normal

O gerar_lab.py cria dois certificados autoassinados de CA, com nome fictício ("Laboratorio Ficticio"), válidos por 30 dias e sem relação com nenhum domínio real. Um usa ec.derive_private_key(7, ec.SECP384R1()), ou seja, d = 7, de propósito. O outro usa ec.generate_private_key, ou seja, o que deveria acontecer. Rodei (Python 3.12.0, cryptography 46.0.7, 23/10/2026 00:47 UTC):

$ python gerar_lab.py
gerados: lab-fraca.pem, lab-normal.pem

$ python chave_fraca.py lab-fraca.pem lab-normal.pem
FRACA  lab-fraca.pem  (secp384r1; escalar <= 65536)
ok     lab-normal.pem  (secp384r1; nenhum escalar <= 65536)
2 certificado(s), 2 com chave EC P-256/P-384, 1 fraca(s); 2.7 s

$ python teste_chave_fraca.py
ok: tabela confere com derive_private_key em P-256 e P-384

O verificador acusa só o certificado construído com d = 7. Note que ele não imprimed: só diz que a chave é fraca. Fiz isso de propósito, e pelo mesmo motivo não publico nem os valores de d das chaves da eMudhra. O código de saída do programa é 1 quando acha alguma chave fraca, o que facilita usá-lo em CI.

E o openssl não vê nada de errado, o que é a lição principal do laboratório. Com o OpenSSL 3.5.0:

$ openssl x509 -in lab-fraca.pem -noout -subject -issuer -dates -ext basicConstraints
subject=O=Laboratorio Ficticio, CN=CA de Laboratorio Fraca
issuer=O=Laboratorio Ficticio, CN=CA de Laboratorio Fraca
notBefore=Oct 22 00:00:00 2026 GMT
notAfter=Nov 21 00:00:00 2026 GMT
X509v3 Basic Constraints: critical
    CA:TRUE

$ openssl x509 -in lab-fraca.pem -noout -text | grep -E "ASN1 OID|NIST CURVE"
                ASN1 OID: secp384r1
                NIST CURVE: P-384

$ openssl verify -CAfile lab-fraca.pem lab-fraca.pem
lab-fraca.pem: OK

É um certificado P-384 perfeitamente bem formado, que passa na verificação. Nenhuma validação de cadeia olha para a aleatoriedade da chave: isso é trabalho de quem gera, de quem audita a geração e de quem varre certificados depois.

Nas oito intermediárias reais

Os SHA-256 das oito intermediárias estão no comentário 0 do bug. O script baixar_intermediarias.sh baixa cada uma do crt.sh por impressão digital, confere que o SHA-256 baixado é o mesmo pedido e junta tudo num PEM. São certificados públicos e já revogados. Rodei o verificador sobre esse arquivo (23/10/2026, 00:47 UTC):

$ sh baixar_intermediarias.sh /e/tools/emudhra/teste8
8 certificados conferidos em /e/tools/emudhra/teste8/emudhra-8.pem

$ python -W ignore chave_fraca.py --bundle /e/tools/emudhra/teste8/emudhra-8.pem
FRACA  CN=EM EV TLS CA - G3B,O=eMudhra BV,C=NL  (secp384r1; escalar <= 65536)
FRACA  CN=EM OV TLS CA - G3B,O=eMudhra BV,C=NL  (secp384r1; escalar <= 65536)
FRACA  CN=EM DV TLS CA - G3B,O=eMudhra BV,C=NL  (secp384r1; escalar <= 65536)
FRACA  CN=EM EV TLS CA - G3C,O=eMudhra Inc,C=US  (secp384r1; escalar <= 65536)
FRACA  CN=EM OV TLS CA - G3C,O=eMudhra Inc,C=US  (secp384r1; escalar <= 65536)
FRACA  CN=EM DV TLS CA - G3C,O=eMudhra Inc,C=US  (secp384r1; escalar <= 65536)
FRACA  CN=EM OV CS CA - G3B,O=eMudhra BV,C=NL  (secp384r1; escalar <= 65536)
FRACA  CN=EM OV CS CA - G3C,O=eMudhra Inc,C=US  (secp384r1; escalar <= 65536)
8 certificado(s), 8 com chave EC P-256/P-384, 8 fraca(s); 2.6 s

Oito de oito, em menos de três segundos, usando uma biblioteca comum. Ou seja, uma varredura assim teria pego o problema a qualquer momento nos 17 meses entre a cerimônia e o relato. O resultado é do meu script; a lista de certificados é a do bug. Ele confere com o que a Mozilla relata no comentário 5: as oito chaves P-384 das séries B e C.

Um detalhe do certificado, lido com o OpenSSL: a intermediária EM DV TLS CA - G3C vale de 30/04/2025 a 28/04/2040, ou seja, 15 anos. Se um dispositivo nunca atualiza seu bundle, a janela de exposição dele vai até lá.

Varredura de um bundle

O mesmo script, com --bundle, percorre todos os certificados de um PEM. Eu o rodei no bundle do certifi instalado aqui (2023.11.17), no certifi 2026.10.19 e no conjunto de raízes que o Node 24.17.0 traz (tls.rootCertificates). O resultado esperado é zero, e foi o que deu: raízes de CA bem formadas não têm esse problema.

$ python -W ignore chave_fraca.py --bundle "$(python -c 'import certifi;print(certifi.where())')"
146 certificado(s), 40 com chave EC P-256/P-384, 0 fraca(s); 4.3 s

(O -W ignore esconde um aviso de depreciação da biblioteca sobre um certificado do bundle antigo, que tem número de série negativo. Não é erro do script.) No certifi 2026.10.19: 120 certificados, 39 com chave EC, 0 fracas. No bundle do Node 24.17.0: 145 certificados, 43 com chave EC, 0 fracas. Zero é o resultado certo para raízes; o problema do dia estava em intermediárias, que não vêm nos bundles, e sim nas cadeias que os servidores enviam.

Quem fica protegido e quem não

Os navegadores se protegeram por três mecanismos:

  • Listas de bloqueio próprias: o OneCRL do Firefox, distribuído por um serviço da Mozilla sem precisar atualizar o navegador, e o CRLSet do Chrome. Ambos bloqueiam intermediárias específicas (o post do Chrome diz que o CRLSet bloqueou os hashes SPKI, isto é, da chave pública, das intermediárias).
  • Certificate Transparency obrigatório: segundo Scott Helme, Chrome, Firefox e Safari exigem que o certificado de um site esteja em registros públicos, e um certificado forjado com uma chave de CA vazada não estaria em nenhum. Ele resume: "Chrome e Firefox bloqueiam as intermediárias diretamente e ambos exigem CT, e o Safari também exige CT."
  • Atualização rápida: Firefox 158.0.1 e as versões ESR em 20/10; Chrome removendo a raiz em 27/10.

Já o código que não é navegador costuma ter apenas o bundle em disco. Scott Helme relata que, com um certificado local para um domínio real encadeado a uma intermediária da eMudhra, curl, Python e Node aceitaram a cadeia com a configuração padrão em Linux e macOS, e que no Windows a cadeia foi aceita a menos que a checagem de revogação estivesse ativa. Não reproduzi esse teste (ele exigiria forjar um certificado para um domínio real, e eu não faço isso). O que fiz foi um laboratório que isola a mesma pergunta, "o cliente consulta revogação se eu não configurar nada?", sem chave fraca e sem domínio real.

Laboratório de revogação

O lab_revogacao.py monta, em memória e num diretório temporário, uma hierarquia fictícia de três níveis, Raiz de Laboratorio → Intermediaria de Laboratorio → servidor.lab.invalid (o sufixo .invalid é reservado e nunca resolve na internet), todas com chaves aleatórias normais. A raiz revoga a intermediária numa CRL com o motivo cACompromise, o mesmo da eMudhra. Um servidor TLS local em 127.0.0.1 apresenta a cadeia revogada. Depois, quatro clientes tentam conectar, primeiro sem configurar nada além da raiz de confiança, depois com as CRLs carregadas.

Python, no próprio script:

$ python lab_revogacao.py
cliente Python padrão          : ACEITOU  HTTP/1.0 200 OK
cliente Python com as CRLs     : REJEITOU certificate revoked

Com o servidor ativo (python lab_revogacao.py --servir DIR), conectei também com curl 8.13.0 (OpenSSL 3.5.0), Node 24.17.0 e um cliente Java (ClienteLab.java, JBR 25.0.3):

$ curl -sS --cacert raiz.pem --resolve servidor.lab.invalid:8443:127.0.0.1 https://servidor.lab.invalid:8443/
ola do servidor de laboratorio

$ curl -sS --cacert raiz.pem --crlfile crls.pem --resolve servidor.lab.invalid:8443:127.0.0.1 https://servidor.lab.invalid:8443/
curl: (60) SSL certificate problem: certificate revoked

$ node cliente_lab.js DIR         # tls.connect({ ca }), sem CRL
authorized: true authorizationError: null
$ node cliente_lab.js DIR crl     # tls.connect({ ca, crl })
erro: CERT_REVOKED certificate revoked

$ java ClienteLab.java DIR
ACEITOU  CN=servidor.lab.invalid, O=Laboratorio Ficticio
$ java ClienteLab.java DIR crl
REJEITOU Certificate has been revoked, reason: CA_COMPROMISE, revocation date: Wed Sep 30 21:00:00 GMT-03:00 2026, authority: CN=Raiz de Laboratorio, O=Laboratorio Ficticio, extension OIDs: [2.5.29.21]

O resultado é o mesmo nos quatro: a cadeia com a intermediária revogada é aceita por padrão e só é recusada quando o programador carrega as CRLs e liga a verificação. Isso bate com a documentação:

  • Python: VERIFY_DEFAULT é o modo em que "as CRLs não são checadas"; para checar é preciso ligar VERIFY_CRL_CHECK_LEAF ou VERIFY_CRL_CHECK_CHAIN e carregar as listas (documentação do módulo ssl). Conferi no meu Python: ctx.verify_flags do contexto padrão não tem nenhum dos dois bits de CRL.
  • Java: o JSSE Reference Guide diz que, com TrustManagerFactory.init(KeyStore), valem os parâmetros PKIX padrão, com a exceção de que a checagem de revogação fica desativada; e a tabela de propriedades de sistema dá false como padrão de com.sun.net.ssl.checkRevocation.
  • curl: --crlfile existe justamente para fornecer a lista; sem ele, e com o backend OpenSSL, nada consulta revogação. Em curl com Schannel (Windows) o comportamento é outro, e é o que Helme observa; não testei.

Duas observações práticas do laboratório. Primeiro, as CRLs precisam cobrir cada emissor da cadeia: no meu primeiro teste em Python, só com a CRL da raiz, o erro foi unable to get certificate CRL, porque a checagem de cadeia também exige uma lista (mesmo vazia) para a intermediária que emitiu a folha. É por isso que o arquivo crls.pem tem duas CRLs. Segundo, "ligar a revogação" é mais trabalho do que parece: você precisa buscar e renovar as listas. É a razão de navegadores terem desenvolvido listas curadas como o OneCRL e o CRLSet, e de a Mozilla ter decidido remover a raiz do NSS: "Isso protege clientes que não usam informação de revogação ou não têm um mecanismo para retirar a confiança em intermediárias específicas" (bug, comentário 18).

Nota 3

Há um segundo caminho de confiança que remover a raiz de um bundle não fecha. A Sectigo emitiu certificados de certificação cruzada para algumas dessas intermediárias a partir da raiz "AAA Certificate Services", que ela diz já ter sido removida de Chrome e Mozilla, mas que "continua confiada nos programas da Microsoft e da Apple" (bug, comentário 6). A Sectigo revogou esses certificados em 19/10 (comentário 13); para quem está nesses ecossistemas, a proteção depende de a revogação chegar até o cliente.

Inspecionar a cadeia de um servidor e de um bundle

Os comandos openssl abaixo respondem a três perguntas práticas: "que cadeia o servidor me manda?", "o que tem dentro deste certificado?" e "o que o meu bundle contém?".

Cadeia de um servidor.s_client -showcerts mostra os certificados enviados e o resultado da verificação; -servername envia o SNI (o nome do site, necessário em servidores que hospedam vários). Capturei com o OpenSSL 3.5.0 em 23/10/2026, 00:39 UTC, apenas conectando (não há nada de forjado aqui):

$ openssl s_client -connect example.com:443 -servername example.com -showcerts </dev/null
...
Certificate chain
 0 s:CN=example.com
   i:C=US, O=SSL Corporation, CN=Cloudflare TLS Issuing ECC CA 3
   a:PKEY: EC, (prime256v1); sigalg: ecdsa-with-SHA256
 1 s:C=US, O=SSL Corporation, CN=Cloudflare TLS Issuing ECC CA 3
   i:C=US, O=SSL Corporation, CN=SSL.com TLS Transit ECC CA R2
   a:PKEY: EC, (prime256v1); sigalg: ecdsa-with-SHA384
 2 s:C=US, O=SSL Corporation, CN=SSL.com TLS Transit ECC CA R2
   i:C=US, O=SSL Corporation, CN=SSL.com TLS ECC Root CA 2022
   a:PKEY: EC, (secp384r1); sigalg: ecdsa-with-SHA384
 3 s:C=US, O=SSL Corporation, CN=SSL.com TLS ECC Root CA 2022
   i:C=GB, ST=Greater Manchester, L=Salford, O=Comodo CA Limited, CN=AAA Certificate Services
   a:PKEY: EC, (secp384r1); sigalg: sha256WithRSAEncryption
...
Verification: OK

s: é o titular (subject) e i: o emissor (issuer): cada i: é o s: do certificado seguinte. A linha a: diz qual é a chave e qual algoritmo assinou. O quarto certificado é uma raiz certificada de forma cruzada por outra (o i: é a "AAA Certificate Services", a mesma raiz da nota acima): é assim que uma certificação cruzada aparece no s_client. Para verificar se uma cadeia passa por algo que você quer evitar, olhe os i: de cima a baixo.

Um certificado em detalhe.openssl x509 -noout -text -fingerprint mostra tudo; os campos que eu olho primeiro são subject, issuer, dates, Basic Constraints e o tipo da chave. Para uma das oito intermediárias:

$ openssl x509 -in 0164D7CE...4D19.pem -noout -subject -issuer -dates -serial -ext basicConstraints
subject=C=US, O=eMudhra Inc, CN=EM DV TLS CA - G3C
issuer=C=IN, O=eMudhra Technologies Limited, CN=emSign Root TLS CA - G3
notBefore=Apr 30 15:35:09 2025 GMT
notAfter=Apr 28 18:30:00 2040 GMT
serial=588B2E3C2BB8CDA13E1A1209254321
X509v3 Basic Constraints: critical
    CA:TRUE, pathlen:0

(O nome do arquivo está abreviado aqui; no seu disco é o SHA-256 completo.) O número de série é o que aparece na tabela de revogações do comentário 10 do bug.

Verificar uma cadeia contra um bundle.openssl verify -CAfile raizes.pem -untrusted intermediarias.pem folha.pem faz o que um cliente faz, sem rede. No laboratório acima, openssl verify -CAfile lab-fraca.pem lab-fraca.pem devolveu OK. Para forçar o openssl verify a consultar CRLs, combine -crl_check_all com -CRLfile (veja openssl-verify(1); não repeti isso no laboratório, onde usei os quatro clientes acima).

Listar o conteúdo de um bundle PEM. Um bundle é um arquivo com muitos certificados em sequência. Esta combinação do openssl lista o subject e o issuer de cada um:

$ openssl crl2pkcs7 -nocrl -certfile certifi-2023.pem | openssl pkcs7 -print_certs -noout | grep -i -A1 emsign
subject=C=IN, OU=emSign PKI, O=eMudhra Technologies Limited, CN=emSign Root CA - G1
issuer=C=IN, OU=emSign PKI, O=eMudhra Technologies Limited, CN=emSign Root CA - G1

subject=C=IN, OU=emSign PKI, O=eMudhra Technologies Limited, CN=emSign ECC Root CA - G3
issuer=C=IN, OU=emSign PKI, O=eMudhra Technologies Limited, CN=emSign ECC Root CA - G3

subject=C=US, OU=emSign PKI, O=eMudhra Inc, CN=emSign Root CA - C1
issuer=C=US, OU=emSign PKI, O=eMudhra Inc, CN=emSign Root CA - C1

subject=C=US, OU=emSign PKI, O=eMudhra Inc, CN=emSign ECC Root CA - C3
issuer=C=US, OU=emSign PKI, O=eMudhra Inc, CN=emSign ECC Root CA - C3

No bundle do certifi 2023.11.17 que eu tinha instalado, a raiz emSign ECC Root CA - G3 continua lá, junto com G1, C1 e C3. Esse é o ponto do artigo em uma linha: a minha instalação de Python, de 2023, ainda confia na raiz que Firefox e Chrome estão removendo.

Checklist para hoje

Conferi o estado de cada ferramenta no meu computador em 23/10/2026, entre 00:38 e 00:47 UTC, e reconferi as versões publicadas entre 10:25 e 10:30 UTC:

FerramentaO que vi aquiO que fazer
certifi (Python)O instalado, 2023.11.17, traz emSign ECC Root CA - G3. A versão 2026.10.19 (baixei o wheel e li o cacert.pem) já não traz a G3 (120 certificados, sem essa raiz). Às 10:25 UTC, a 2026.10.19 continuava sendo a versão mais recente no PyPI.pip install -U certifi; confira com python -c "import certifi; print(certifi.__version__)" e com a listagem acima. Rebuild de imagens de contêiner que embutem o pacote.
Node.jsO Node 24.17.0 que tenho traz a G3 em tls.rootCertificates (145 certificados). As versões 22.23.4, 24.22.1 e 26.12.1, de 21/10, atualizam as raízes para o NSS 3.131.1. Baixei o Node 24.22.1 (10:26 UTC): tls.rootCertificates tem 118 certificados e, da eMudhra, só a emSign Root CA - G1; G3, C1 e C3 saíram.Atualize o Node para uma dessas versões ou posterior e confira com o comando node -e da tabela de comandos. Imagens de contêiner com Node antigo precisam de rebuild.
JavaO cacerts do JBR 25.0.3 que tenho lista emsigneccrootcag3 (109 entradas).Não encontrei nota de versão do OpenJDK ou da Oracle que cite a G3, então não sei dizer qual JDK já a removeu. Atualize o JDK quando o fornecedor publicar; keytool -list -cacerts mostra o conteúdo.
ca-certificates (Debian/Ubuntu etc.)Não executei no Linux. Pelos changelogs: o Debian 20260816+nmu1 (unstable, 20/10) e o 20250419+deb13u1 (trixie-security, 22/10) põem a "emSign ECC Root CA - G3" na lista de bloqueio (o primeiro também a "Comodo AAA Services root"); a Ubuntu fez o mesmo em 20/10 (USN-8944-1; por exemplo, 20260816ubuntu0.24.04.1). É um bloqueio, não a atualização para o NSS 3.131.1: as raízes C1 e C3 não aparecem nesses changelogs.Atualize o ca-certificates para essas versões ou posteriores; confira o conteúdo com openssl crl2pkcs7 -nocrl -certfile /etc/ssl/certs/ca-certificates.crt | openssl pkcs7 -print_certs -noout | grep -i emsign (caminho padrão do Debian/Ubuntu; não executado por mim).
NSSVersão corrigida: 3.131.1, de 20/10/2026, que remove três raízes da emSign (G3, C1 e C3).Prefira conferir o conteúdo do bundle a conferir o número de versão: o número só vale se você sabe de onde seu bundle vem.

Algumas outras ações, na ordem em que eu faria:

  1. Descubra se você usa a hierarquia afetada. Rode openssl s_client -connect seu.host:443 -showcerts nos seus endpoints e procure emSign ou eMudhra nos i:. Se algum certificado seu encadeia à raiz emSign ECC Root CA - G3, o Chrome disse (post de 20/10): é preciso "adquirir e instalar imediatamente um novo certificado TLS de outra hierarquia PKI" confiada pelo Chrome Root Store.
  2. Atualize bundles e rebuild de imagens.pip, apt/apk/dnf, atualizações do Node e do JDK, imagens de base. Imagens de contêiner antigas continuam com o bundle antigo até serem reconstruídas.
  3. Dispositivos congelados. Roteadores, impressoras, TVs e equipamentos com firmware que não atualiza continuam confiando nessas intermediárias até o certificado expirar (Helme, que fala em julho de 2039 para o conjunto dele; as intermediárias que li vão até abril de 2040). Para esses, o que existe é compensação: segmentar a rede, não aceitar essas CAs em proxies internos que você controla, e planejar a troca.
  4. Pipelines e instaladores.curl ... | sh, CI/CD e chamadas servidor-a-servidor são os lugares onde "confiar no bundle" é a única defesa. Revise se algum deles fixa um bundle próprio.
  5. Considere ligar revogação onde o custo for aceitável, sabendo que isso é trabalho de manter CRLs, como o laboratório mostrou.

O que ainda está em aberto

Em 23/10/2026, 00:42 UTC, e de novo às 10:25 UTC, o bug continuava aberto (status ASSIGNED), com perguntas sem resposta. O que não sabemos ainda, com a fonte de cada ponto:

  • A causa-raiz. A eMudhra fala em erro de código e em passos manuais errados na cerimônia (bug, comentário 11; página de status). Há quem aponte que isso não explica um escalar P-384 menor que 256 sair de dentro de um HSM (comentários 31 e 33), e o comentário 38 (23/10, 04:54 UTC) repete a pergunta: que falha exata do script da cerimônia gerou o segundo par fraco, e como um HSM certificado deixou passar chaves de baixa entropia. Quando consultei, esse comentário não tinha resposta. A própria eMudhra diz, na página de status, que ainda não confirmou a causa. Tomo como hipótese, não como fato.
  • Como isso passou por duas auditorias. A página de status da eMudhra diz que a hierarquia passou por dois ciclos de auditoria independentes; o comentário 35 do bug pergunta se o problema anterior com as CRLs dessas mesmas intermediárias (bug 1970728) foi levado aos auditores. Sem resposta até a minha consulta.
  • Que exigência foi descumprida. Pelo que li nos Baseline Requirements do CA/Browser Forum (versão 2.3.1, de 04/10/2026), a seção 6.1.1.1 (geração de chaves de CA) exige roteiro de cerimônia, testemunha ou vídeo e relatório de auditor qualificado para raízes e para subordinadas de terceiros; para subordinadas do próprio operador da raiz a redação é "SHOULD" (recomendado), não "SHALL". A seção 6.1.1.3 (chaves de assinantes) exige rejeitar chaves privadas fracas já demonstradas pela indústria (Debian, ROCA, primos próximos), mas trata de chaves de assinantes, não de chaves de CA. Já a seção 4.9.1.2 dá sete dias para revogar uma subordinada com chave comprometida; a eMudhra revogou em menos de um dia (comentário 10). Essas são leituras minhas do texto, não uma conclusão de qualquer programa de raízes.
  • A situação das outras raízes da eMudhra. O post do Chrome diz que o programa de raízes investiga se os demais certificados da eMudhra devem permanecer no Chrome Root Store. O NSS 3.131.1 removeu três raízes (G3, C1 e C3); a eMudhra diz que a remoção das outras duas foi "voluntária e planejada" e não relacionada.
  • A saída do negócio de TLS. O comunicado de 22/10 diz que a eMudhra deixará o negócio global de SSL/TLS e que as raízes G1 (RSA) atenderão os clientes na transição. O comunicado diz que a eMudhra contatará os clientes afetados para substituir os certificados sem custo e que os serviços de revogação e de status continuarão até que todos os certificados emitidos expirem ou sejam substituídos; a página de status (atualização 4, de 22/10) diz que ela oferece certificados emitidos pela Sectigo como alternativa. Quando e como a transição se completa é a pergunta do comentário 37 do bug, e o comunicado promete um relatório completo de causa-raiz aos programas de raízes, sem data.
  • A data do Chrome. 27/10 é a data esperada por quem opera o Chrome Root Program, em duas fontes primárias (comentário 30 do bug e post do Chrome); datas desse tipo podem mudar. Reabri o post do Chrome e o bug às 10:25 UTC de 23/10 e a data seguia a mesma ("terça-feira de manhã, 27 de outubro de 2026, PDT"; o post não traz atualização depois de 20/10, e não achei anúncio novo do Chrome Root Program). Isso não a transforma em compromisso: confira antes de tratá-la como tal.

Glossário

TermoO que é
CA (autoridade certificadora)Entidade em quem sistemas operacionais e navegadores confiam para afirmar que uma chave pública pertence a um nome (por exemplo, um site).
RaizCertificado de CA autoassinado, que fica no bundle de confiança do cliente e é o topo da cadeia.
IntermediáriaCertificado de CA assinado por uma raiz (ou por outra intermediária) que assina certificados de sites. Protege a chave da raiz, que fica offline.
Certificado folhaCertificado do site (ou do usuário final), no fim da cadeia; não assina outros certificados.
Cadeia de confiançaSequência folha, intermediárias e raiz em que cada certificado é assinado pelo seguinte e o último está no bundle de confiança do cliente.
X.509Formato padrão dos certificados de chave pública usados em TLS.
PEMFormato de texto (base64 entre linhas BEGIN CERTIFICATE e END CERTIFICATE) em que certificados costumam ser guardados e trocados; um arquivo pode ter vários.
Bundle de CAsArquivo ou repositório com as raízes em que um cliente confia (NSS no Firefox, certifi em Python, cacerts em Java, ca-certificates em Linux).
NSSNetwork Security Services, biblioteca de criptografia da Mozilla; seu repositório de raízes é a base de muitos outros bundles.
Chave privada / chave públicaPar de números ligados: a privada assina e deve ser secreta; a pública verifica e vai no certificado.
Curva elíptica (P-256, P-384)Estrutura matemática usada para chaves compactas. P-256 e P-384 são as curvas padronizadas pelo NIST de 256 e 384 bits.
Escalar privado dO inteiro que é a chave privada de uma chave de curva elíptica; a chave pública é Q = d·G.
Gerador GPonto fixo e público da curva a partir do qual se calcula d·G.
Ordem nNúmero de pontos gerados por G; a chave privada válida está entre 1 e n - 1.
Logaritmo discretoProblema de achar d dado d·G; é inviável para d aleatório grande e trivial quando d é pequeno.
Baby-step giant-stepAlgoritmo que acha um logaritmo discreto de k bits em cerca de 2^(k/2) passos.
ECDSAElliptic Curve Digital Signature Algorithm, algoritmo de assinatura digital sobre curvas elípticas.
Chave fracaChave gerada de forma que possa ser adivinhada ou calculada (por exemplo, d minúsculo), apesar de ter o formato de uma chave normal.
RevogaçãoPublicação, pelo emissor, de que um certificado não deve mais ser aceito.
CRLCertificate Revocation List, lista assinada de certificados revogados que o cliente baixa e consulta.
OCSPOnline Certificate Status Protocol, consulta online do estado de um certificado.
OneCRLLista de revogação de intermediárias mantida pela Mozilla e entregue ao Firefox sem atualizar o navegador.
CRLSetLista de bloqueio do Chrome, equivalente em função ao OneCRL.
Certificate Transparency (CT)Registros públicos e auditáveis de todos os certificados emitidos; navegadores recusam certificados que não estejam neles.
CCADBCommon CA Database, banco de dados de CAs e certificados mantido para os programas de raízes.
Certificação cruzada (cross-sign)Certificado em que uma CA assina a chave de outra CA, criando um segundo caminho de confiança para ela.
HSMHardware Security Module, equipamento que gera e guarda chaves de CA sem expô-las.
Cerimônia de geração de chavesProcedimento formal, com roteiro e testemunhas, em que as chaves de uma CA são geradas.
ACMEProtocolo de emissão e renovação automática de certificados (usado, por exemplo, pelo Let's Encrypt).
SNIServer Name Indication, extensão do TLS em que o cliente informa o nome do site que quer acessar.
Baseline RequirementsRegras do CA/Browser Forum que toda CA pública de TLS precisa seguir.

Comandos

ComandoO que faz
python gerar_lab.pyGera dois certificados autoassinados de laboratório (nome fictício): lab-fraca.pem com d = 7 e lab-normal.pem com chave aleatória.
python chave_fraca.py cert.pem [...]Verifica se a chave EC (P-256/P-384) de cada certificado tem escalar d <= 65536 (ou n - d). Sai com código 1 se achar alguma.
python -W ignore chave_fraca.py --bundle arquivo.pemFaz o mesmo para todos os certificados de um bundle PEM; -W ignore esconde avisos de depreciação da biblioteca.
python teste_chave_fraca.pyCompara a tabela do verificador com derive_private_key da biblioteca cryptography.
sh baixar_intermediarias.sh DIRBaixa do crt.sh as oito intermediárias da eMudhra, confere o SHA-256 de cada e junta em DIR/emudhra-8.pem.
python lab_revogacao.pyRoda o laboratório de revogação com cliente Python padrão e com CRLs.
python lab_revogacao.py --servir DIRGrava a hierarquia fictícia em DIR e serve o servidor TLS em 127.0.0.1:8443 até Ctrl+C.
curl --cacert raiz.pem --resolve servidor.lab.invalid:8443:127.0.0.1 https://servidor.lab.invalid:8443/Conecta ao servidor de laboratório confiando só na raiz fictícia; --resolve fixa o nome para o IP local.
curl ... --crlfile crls.pemIdem, mas carregando as CRLs; o curl com OpenSSL passa a recusar a cadeia revogada (erro 60).
node cliente_lab.js DIR [crl]Cliente Node do laboratório (tls.connect com ca e, opcionalmente, crl).
java ClienteLab.java DIR [crl]Cliente Java do laboratório; sem crl, usa o caminho padrão (revogação desligada); com crl, PKIX com revogação e as CRLs.
openssl s_client -connect host:443 -servername host -showcerts </dev/nullConecta, mostra a cadeia enviada pelo servidor e o resultado da verificação.
openssl x509 -in cert.pem -noout -subject -issuer -dates -serial -ext basicConstraintsMostra titular, emissor, validade, número de série e restrições básicas de um certificado.
openssl x509 -in cert.pem -noout -text -fingerprint -sha256Despeja todo o certificado em texto e o SHA-256 da impressão digital.
openssl verify -CAfile raizes.pem [-untrusted intermediarias.pem] cert.pemValida um certificado contra um bundle, sem rede.
openssl crl2pkcs7 -nocrl -certfile bundle.pem | openssl pkcs7 -print_certs -nooutLista subject e issuer de todos os certificados de um bundle PEM.
grep -i emsignFiltra a listagem acima pelas raízes e intermediárias da emSign.
pip install -U certifiAtualiza o certifi para a versão mais recente.
python -c "import certifi; print(certifi.__version__, certifi.where())"Mostra a versão do certifi e o caminho do bundle que o Python usa.
keytool -list -cacerts -storepass changeitLista as raízes do cacerts do JDK (a senha padrão é changeit).
node -e "require('tls').rootCertificates.forEach(c => console.log(c))"Imprime em PEM as raízes embutidas no Node.

Conclusão

Uma chave privada de CA com um byte significa que a "segurança" da chave é de 8 bits, e que qualquer pessoa com um laptop e uma tarde produz um certificado aceito por todo cliente que ainda confia na cadeia. O sistema de certificados funciona porque presume que as chaves das CAs são secretas e aleatórias, e nenhuma etapa da validação de uma cadeia testa isso: o openssl verify do meu certificado de d = 7 devolveu OK. Quem testa são as CAs, os auditores e a comunidade, e dessa vez foi um pesquisador com um script sobre uma base pública de certificados.

Os navegadores se protegeram em menos de três horas porque têm canais próprios de bloqueio e exigem Certificate Transparency. O resto da pilha, não. No laboratório, curl, Python, Node e Java aceitaram uma cadeia revogada com a configuração padrão e só a recusaram depois que carreguei as CRLs. Portanto, a defesa de código que não é navegador é a mais velha de todas: manter o bundle atualizado, reconstruir imagens, planejar a troca dos dispositivos que não atualizam, e saber conferir o que há dentro do bundle com openssl em vez de supor. O verificador deste artigo cobre uma classe estreita de falha (escalar pequeno em P-256/P-384), mas é barato, roda em segundos e é o tipo de checagem que gostaria de ver rodando em CI sobre qualquer bundle ou CA interna.

Para ir além:

Referências

Comentários

Os comentários usam o Giscus, que guarda as discussões no GitHub. Ao carregar, seu navegador se conecta ao giscus.app e ao GitHub, que recebem o seu endereço IP. Saiba mais na política de privacidade.