#!/intro

A 30 de setembro de 2021, o certificado de raiz DST Root CA X3, da IdenTrust, utilizado pela Let’s Encrypt para manter compatibilidade com sistemas que ainda não reconheciam a raiz ISRG Root X1, expirou.

A data de expiração era conhecida e a Let’s Encrypt tinha preparado antecipadamente a transição para a ISRG Root X1. Apesar de os certificados dos servidores continuarem válidos, algumas ligações TLS começaram a falhar com erros de certificado expirado.

Nos sistemas que nunca tinham recebido a ISRG Root X1, o resultado era previsível. A expiração da única raiz reconhecida eliminava o caminho de confiança disponível. Outros clientes já possuíam a ISRG Root X1 no respetivo repositório de confiança e, mesmo assim, rejeitavam os certificados da Let’s Encrypt.

A cadeia de certificação predefinida incluía o certificado da ISRG Root X1 assinado pela DST Root CA X3. Algumas implementações seguiam esse caminho até ao certificado expirado, em vez de terminarem na ISRG Root X1 instalada localmente.

O certificado do servidor era válido. A raiz necessária existia no cliente. A ligação falhava porque a implementação construía um caminho de confiança que terminava na raiz errada.

O incidente expôs a diferença entre a cadeia de certificação apresentada pelo servidor e o caminho de confiança construído pelo cliente.


> sintomas

A expiração não provocou uma indisponibilidade uniforme.

Os sítios continuavam acessíveis através da maioria dos navegadores e sistemas operativos atualizados. Em paralelo, algumas aplicações, interfaces de programação, equipamentos de rede e sistemas integrados deixaram de conseguir estabelecer ligações TLS aos mesmos serviços.

O comportamento podia variar no próprio equipamento. Um sítio abria normalmente no navegador, enquanto uma aplicação instalada no mesmo sistema recusava a ligação. Cada componente podia utilizar uma biblioteca TLS e um repositório de confiança diferentes.

Nos clientes afetados, o erro referia frequentemente a expiração da DST Root CA X3, apesar de o certificado do servidor continuar dentro do respetivo período de validade, corresponder ao nome solicitado e possuir uma assinatura válida.

O certificado expirado surgia no caminho de confiança construído pelo cliente.

Esta diferença explicava por que razão o mesmo servidor era aceite por alguns clientes e rejeitado por outros. A cadeia de certificação apresentada era a mesma. Mudava a implementação responsável por construir e validar o caminho de confiança até uma raiz reconhecida.


> transicao

Quando a Let’s Encrypt iniciou a emissão de certificados, a ISRG Root X1 ainda não estava presente nos principais sistemas operativos, navegadores e bibliotecas.

A distribuição de uma nova raiz demora vários anos. Mesmo depois da sua inclusão nos principais programas de confiança, permanecem em funcionamento dispositivos que já não recebem atualizações e que nunca reconhecerão essa raiz.

Para obter compatibilidade desde o início, a Let’s Encrypt utilizou a DST Root CA X3, uma raiz da IdenTrust que já se encontrava amplamente distribuída.

Até maio de 2021, a cadeia de certificação predefinida para certificados RSA utilizava um certificado da intermédia R3 assinado pela DST Root CA X3:

Certificado do servidor
└── R3

A partir desses certificados, os clientes construíam um caminho de confiança que terminava na DST Root CA X3 instalada localmente:

Certificado do servidor
└── R3
    └── DST Root CA X3
        âncora de confiança

Os clientes não precisavam de conhecer a ISRG Root X1. Bastava que confiassem na DST Root CA X3.

Entretanto, a ISRG Root X1 foi sendo adicionada aos repositórios de confiança dos sistemas modernos. Tornou-se então possível apresentar uma cadeia de certificação associada diretamente à raiz da Let’s Encrypt:

Certificado do servidor
└── R3

Neste caso, o cliente construía o seguinte caminho de confiança:

Certificado do servidor
└── R3
    └── ISRG Root X1
        âncora de confiança

Este caminho eliminava a dependência da DST Root CA X3, mas deixava de funcionar em sistemas que ainda não reconheciam a ISRG Root X1.

O problema era particularmente relevante nas versões do Android anteriores à 7.1.1. Uma quantidade significativa desses dispositivos permanecia ativa, mas já não recebia atualizações que incluíssem a nova raiz.

A adoção exclusiva da cadeia associada à ISRG Root X1 tornaria os sítios da Let’s Encrypt inacessíveis nesses equipamentos.

Para prolongar a compatibilidade, a IdenTrust emitiu um certificado da ISRG Root X1 assinado pela DST Root CA X3. Esse certificado cruzado permitia ligar a infraestrutura da Let’s Encrypt a uma raiz já existente nos sistemas antigos.

A partir de 4 de maio de 2021, a cadeia de certificação predefinida passou a incluir:

Certificado do servidor
└── R3
    └── ISRG Root X1
        assinada pela DST Root CA X3

Num dispositivo que ainda confiasse exclusivamente na DST Root CA X3, o caminho de confiança podia ser construído da seguinte forma:

Certificado do servidor
└── R3
    └── ISRG Root X1
        assinada pela DST Root CA X3
        └── DST Root CA X3
            âncora de confiança

A Let’s Encrypt manteve também uma cadeia alternativa que não incluía o certificado cruzado:

Certificado do servidor
└── R3

Nos clientes atualizados, essa cadeia permitia construir diretamente um caminho de confiança até à ISRG Root X1.

A cadeia predefinida procurava preservar a compatibilidade com sistemas que ainda dependiam da DST Root CA X3. A cadeia alternativa destinava-se aos clientes capazes de confiar diretamente na raiz da Let’s Encrypt.

A diferença não estava apenas no número de certificados apresentados. Estava nos caminhos de confiança que cada cliente conseguia construir a partir deles.


> validacao

O servidor não determina sozinho o caminho de confiança utilizado numa ligação TLS.

Durante a negociação, apresenta o certificado do serviço e os certificados intermédios que constituem a cadeia de certificação. O cliente combina esses elementos com os certificados presentes no seu repositório e procura construir um caminho de confiança até uma raiz reconhecida.

A cadeia de certificação apresentada pelo servidor não determina necessariamente o único caminho de confiança possível.

Um cliente moderno que recebesse o certificado cruzado da ISRG Root X1 e já possuísse essa raiz no repositório de confiança podia terminar a validação localmente:

Certificado do servidor
└── R3
    └── ISRG Root X1
        âncora de confiança local

Não precisava de continuar até à DST Root CA X3.

A presença do certificado da ISRG Root X1 assinado pela DST Root CA X3 não obrigava o cliente a utilizar a raiz antiga. A chave pública e a identidade correspondiam à ISRG Root X1 que o sistema já reconhecia como âncora de confiança.

Determinadas implementações não faziam essa seleção.

As versões 1.0.x do OpenSSL privilegiavam, por omissão, os certificados da cadeia não confiável apresentada pelo servidor. Ao encontrarem o certificado cruzado da ISRG Root X1, continuavam a construção do caminho de confiança até à DST Root CA X3:

Certificado do servidor
└── R3
    └── ISRG Root X1
        assinada pela DST Root CA X3
        └── DST Root CA X3
            expirada

Depois de 30 de setembro, esse caminho era rejeitado.

O problema ocorria mesmo quando a ISRG Root X1 já estava presente no repositório de confiança local.

Este detalhe é central.

O cliente não falhava por desconhecer a nova raiz. Falhava porque a estratégia de construção selecionava o certificado cruzado apresentado pelo servidor e produzia um caminho de confiança que terminava na raiz expirada.

O erro não estava na assinatura do certificado nem na validade do certificado final. Estava no algoritmo de construção e seleção do caminho de confiança.

No OpenSSL 1.1.0, a procura prioritária no repositório de confiança passou a ser o comportamento predefinido. A implementação conseguia selecionar a ISRG Root X1 instalada localmente e terminar a validação sem prosseguir até à DST Root CA X3.

A diferença entre as versões não estava nos certificados disponíveis. Estava na ordem pela qual procuravam os elementos necessários para construir o caminho de confiança.

Por essa razão, atualizar apenas o repositório de raízes nem sempre resolvia a incompatibilidade. Um sistema podia possuir a ISRG Root X1 e continuar a falhar porque a biblioteca TLS não a selecionava como âncora.


> compatibilidade

A expiração afetou grupos de clientes por razões diferentes.

Os sistemas que confiavam exclusivamente na DST Root CA X3 deixaram de validar certificados da Let’s Encrypt porque não possuíam outra âncora de confiança. Era a consequência normal de manter um repositório desatualizado depois da expiração da raiz da qual dependiam.

Esses clientes não construíam incorretamente o caminho de confiança. Simplesmente não dispunham de outro caminho válido.

Os clientes baseados em versões antigas do OpenSSL apresentavam uma situação diferente. Podiam confiar na ISRG Root X1 e, ainda assim, rejeitar a ligação quando recebiam a cadeia de certificação destinada a preservar a compatibilidade com Android.

A mesma cadeia resolvia, portanto, um problema e expunha outro.

Nas versões antigas do Android, a data de expiração dos certificados utilizados como âncoras de confiança não era aplicada da mesma forma. Como a DST Root CA X3 continuava instalada no sistema, a respetiva chave podia ser utilizada para validar o certificado cruzado da ISRG Root X1 mesmo depois de 30 de setembro de 2021.

Este comportamento permitiu que dispositivos Android anteriores à versão 7.1.1 continuassem a aceitar certificados da Let’s Encrypt.

Num cliente baseado em OpenSSL 1.0.x, o caminho que terminava na raiz expirada produzia o resultado oposto.

A cadeia de certificação predefinida foi escolhida para maximizar a compatibilidade global. Não existia, contudo, uma única cadeia capaz de produzir um caminho de confiança válido em todas as implementações.

A inclusão do certificado cruzado preservava os dispositivos Android antigos, mas era incompatível com alguns clientes baseados em OpenSSL. A sua remoção evitava o caminho até à raiz expirada, mas excluía os sistemas que ainda não confiavam na ISRG Root X1.

O operador do servidor tinha de escolher os clientes que precisava de suportar.


> mitigacao

A correção estrutural estava no cliente.

Os sistemas que não possuíam a ISRG Root X1 precisavam de atualizar o respetivo repositório de confiança. Sem essa raiz, a expiração da DST Root CA X3 eliminava o único caminho disponível.

Nos sistemas que já confiavam na ISRG Root X1, mas utilizavam OpenSSL 1.0.x, existiam várias possibilidades.

A solução preferencial era atualizar para OpenSSL 1.1.0 ou posterior, onde a procura prioritária no repositório de confiança já constituía o comportamento predefinido.

No OpenSSL 1.0.2, a aplicação podia ativar explicitamente:

X509_V_FLAG_TRUSTED_FIRST

Esta opção fazia com que o algoritmo procurasse primeiro os emissores no repositório de confiança antes de utilizar os certificados da cadeia apresentada pelo servidor.

Nas ferramentas de linha de comandos, o mesmo comportamento podia ser solicitado através de:

-trusted_first

A limitação era operacional. Muitas aplicações de terceiros não expunham esta opção e teriam de ser alteradas para configurar explicitamente os parâmetros de validação.

Outra possibilidade consistia em remover a DST Root CA X3 expirada do repositório utilizado pelo cliente e garantir a presença da ISRG Root X1. Sem a raiz antiga disponível, deixava de ser possível terminar o caminho de confiança nesse ponto.

Esta alteração dependia do sistema operativo e do mecanismo usado para gerir os certificados de confiança. Não devia ser reduzida à remoção manual de um ficheiro sem confirmar como o repositório era construído e atualizado.

Quando não era possível alterar os clientes, o servidor podia deixar de apresentar o certificado cruzado e utilizar a cadeia alternativa associada diretamente à ISRG Root X1.

Os clientes baseados em OpenSSL 1.0.x que já confiassem nessa raiz conseguiam então construir o seguinte caminho:

Certificado do servidor
└── R3
    └── ISRG Root X1
        âncora de confiança

A solução tinha um custo. Os dispositivos que continuavam dependentes da DST Root CA X3 deixavam de conseguir estabelecer a ligação.

A decisão não podia ser tomada apenas com base no certificado instalado no servidor. Exigia conhecer as bibliotecas TLS, os repositórios de confiança e os sistemas operativos utilizados pelos clientes reais do serviço.

Num sítio público, os navegadores modernos absorviam grande parte desta complexidade. Numa interface de programação, numa integração entre sistemas ou numa rede de equipamentos, o operador podia não controlar os clientes nem conhecer todas as implementações existentes.

Testar o certificado num navegador atualizado demonstrava apenas que esse navegador conseguia construir um caminho de confiança válido.

Não demonstrava compatibilidade com os restantes consumidores do serviço.


> conclusao

A expiração da DST Root CA X3 não constituiu uma falha criptográfica. Tornou visíveis diferenças que já existiam na forma como os clientes construíam caminhos de confiança a partir da mesma cadeia de certificação.

A ausência da ISRG Root X1 em sistemas sem atualização era uma consequência previsível. Esses clientes continuavam dependentes de uma raiz cujo período de validade tinha terminado.

O caso tecnicamente relevante encontrava-se nos clientes que já reconheciam a nova raiz, mas não conseguiam utilizá-la perante a cadeia de certificação com assinatura cruzada. A confiança necessária existia no sistema. A implementação construía, contudo, um caminho de confiança que terminava na raiz expirada.

A assinatura cruzada tinha sido criada para assegurar compatibilidade. Permitiu à Let’s Encrypt obter aceitação antes da distribuição generalizada da sua própria raiz e prolongou depois o suporte para versões antigas do Android.

Essa compatibilidade não era neutra. A mesma cadeia de certificação produzia resultados diferentes consoante a capacidade do cliente para construir e selecionar um caminho de confiança.

A validação TLS não se reduz ao certificado final nem à cadeia de certificação apresentada pelo servidor. Depende das âncoras instaladas, das regras da biblioteca e do caminho de confiança construído pelo cliente.

A compatibilidade de uma PKI deve, por isso, ser avaliada nos clientes que efetivamente consomem o serviço. Isso inclui as bibliotecas TLS, as versões utilizadas, os repositórios de confiança, as opções de validação e a capacidade de atualização dos equipamentos.

O servidor apresenta a cadeia de certificação.

A confiança é construída no cliente.

> status: trusted
> exit 0