TLS no Linux: diagnosticar a cadeia de confiança sem desativar a validação
Escopo histórico: esta análise conserva os snapshots e runtimes identificados abaixo. A referência atual de instalação e runtime está nas páginas próprias deste site; a tabela histórica não é uma lista das versões atualmente publicadas.
Guia técnico com evidência pública conferida em 17/10/2026. Os comandos administrativos exigem revisão e aprovação do responsável pelo ambiente; não foram executados nesta publicação.
Uma falha ao validar o certificado HTTPS do servidor pode impedir a comunicação fiscal mesmo com um A1 válido. A identidade do servidor, a cadeia de autoridades certificadoras, o nome DNS e as datas dos certificados precisam ser aceitos pelo motor que faz a conexão. Trocar a biblioteca PHP, Node ou Python não corrige, por si só, uma raiz ausente no ambiente desse motor.
O que foi comprovado e o que permanece incerto
Relatório público sanitizado, com comandos, hashes, versões e limites: baixar JSON ou baixar texto. SHA-256 do JSON: 160036ba3dde616c3345f4bc126c70527bac9d5239e391b73b11cfd10978b5df. O relatório não contém A1, XML, identificadores nem estados de operações fiscais.
O relatório público registra probes históricos, executados em 16/10/2026 no mesmo ambiente Ubuntu 24.04 sob WSL, com o runtime 0.1.2. O bundle consultado era /etc/ssl/certs/ca-certificates.crt. A ausência da raiz ICP-Brasil V10 bloqueou esses probes; após a correção histórica autorizada, acrescentando somente a raiz aprovada, os mesmos probes passaram. Nenhum novo probe SEFAZ, alteração de truststore ou reinício de serviço foi executado na revisão desta documentação.
| Evidência histórica | Antes, 2026-10-16 23:48:15 UTC | Depois, 2026-10-16 23:56:07 UTC |
|---|---|---|
| OpenSSL estrito, sistema e bibliotecas do runtime | Saída 1, erro 19: self-signed certificate in certificate chain | Saída 0, verificação 0, TLS 1.2 |
| curl sem certificado cliente | Saída 60 | Saída 0, HTTP 403 |
| Inventário de CAs do bundle | 121, sem a raiz V10 | 122; uma raiz adicionada, nenhuma removida e as anteriores preservadas |
O HTTP 403 sem A1 é uma resposta HTTP após a conexão TLS; não é autorização fiscal. Uma linha isolada Verify return code: 0 também não prevalece sobre um erro de verificação ou uma saída diferente de zero: confira o processo completo, -verify_return_error, hostname e SNI. Esses resultados demonstram a confiança daqueles probes; não estabelecem a causa nem o resultado de entrega de uma operação anterior da aplicação.
A plataforma verificada era Linux x64/glibc, Ubuntu 24.04 sob WSL. O OpenSSL do sistema e as bibliotecas do runtime eram 3.0.13 (30 Jan 2024); o pacote registrado era libssl3t64:amd64 3.0.13-0ubuntu3.16. A revisão ACBr era 48590; os metadados de construção indicavam Free Pascal 3.2.2 e Lazarus 3.0. Não foi comprovada uma versão semântica separada para o worker. As versões do worker/OpenSSL em Windows e macOS não foram verificadas por esse relatório; não devem ser inferidas da linguagem hospedeira ou de condicionais no código-fonte.
A SEFAZ-PE publicou, em 05/06/2023, um aviso de atualização para a raiz AC Raiz Brasileira V10 e a emissora AC SOLUTI SSL EV G4, com uma cadeia disponível em seu domínio oficial. O aviso da SEFAZ-PE é uma referência para essa cadeia específica; outras UFs, endpoints e renovações podem usar cadeias diferentes.
A exceção original e o retorno de rede de uma tentativa anterior não foram preservados integralmente pelo caminho nativo auditado. Portanto, a causa exata daquela tentativa continua sem comprovação. Um probe público TLS isolado, mesmo quando passa após acrescentar a raiz, não recupera essa evidência descartada nem prova que a tentativa falhou antes do envio.
A classificação de resultado incerto descreve apenas o instante daquele retorno. Ela não permite inferir a situação fiscal posterior, que exige reconciliação por consulta e pelos protocolos preservados no fluxo autorizado. Um teste TLS posterior não autoriza reenvio automático.
Versões às quais esta análise se aplica
| Cliente consultado | Runtime nativo | Plataformas do cliente |
|---|---|---|
PHP alpha, fonte 0e467bf4af7eba4848596d630554624049dd6a7d | 0.1.2, fonte eea82662eb60ea05b5dd164cf293438a202760a7 | Linux x64/glibc |
Node, fontes dos runtimes 0.1.2 e 0.2.0 | eea82662eb60ea05b5dd164cf293438a202760a7 e 5ff66828eb70f942608517f1e11719272b2786b8 | Linux e Windows x64 |
Python 0.1.0a2, fonte d10b326542303414bb0c870f9539281f1e8abb57, PR #2 draft | 0.2.0, fonte 5ff66828eb70f942608517f1e11719272b2786b8 | Linux x64/glibc e Windows x64 |
O pin PHP e o pin Python identificam esses artefatos históricos. Na data desta análise, o snapshot Python estava em revisão no PR #2, sem publicação PyPI. Essa descrição não representa o estado atual das releases nem anuncia suporte Windows para PHP.
O código TLS é idêntico nas fontes dos runtimes 0.1.2 e 0.2.0. Sem um parâmetro interno de teste, o Linux escolhe o primeiro arquivo existente, sem mesclar: /etc/ssl/certs/ca-certificates.crt, /etc/pki/tls/certs/ca-bundle.crt, /etc/ssl/ca-bundle.pem. No Windows, exporta a loja ROOT acessível ao processo para um PEM temporário e continua usando OpenSSL. Esse comportamento explica por que duas plataformas podem confiar em cadeias diferentes; não prova que Node/Python apresentaram a falha observada no caso PHP.
O worker verifica a cadeia, a validade e o hostname; o wrapper exige SNI e X509_check_host. A configuração do runtime é íntegra e não deve ser editada para contornar a falha. openssl version -a e curl --version abaixo identificam as ferramentas do sistema, que podem usar versões/backend diferentes das bibliotecas distribuídas com o worker.
Identifique qual componente abriu a conexão
Nas versões auditadas, as operações fiscais são executadas por um motor nativo Pascal. O processo PHP, Node ou Python é a interface para esse motor. O código NativeTLS.pas auditado usa a cadeia do sistema no Linux; no Windows, obtém os certificados do repositório ROOT e produz um PEM temporário. O ambiente mínimo dos workers não repassa as variáveis de CA dos processos hospedeiros.
Assim, alterar uma variável de CA do Node, uma configuração do requests/Python ou a configuração OpenSSL do PHP não demonstra que o motor nativo passou a confiar na cadeia. Confira a versão do SDK, a versão/hash do runtime, a plataforma e o código auditado. Não use funções internas nem suponha que uma opção interna CAFile seja uma API pública.
Esse risco também pode atingir Node e Python quando seus motores usam o mesmo mecanismo no ambiente Linux afetado. Isso não significa que todos os servidores Linux, todas as versões ou todos os endpoints estejam com defeito. Um teste do navegador ou do cliente HTTP da linguagem pode usar um truststore diferente do worker.
O que a comparação dos runtimes permite concluir
O relatório compara os artefatos Linux 0.2.0 e 0.2.1: 252 arquivos permaneceram idênticos e 12 arquivos de licença foram acrescentados, sem alteração ou remoção de arquivos anteriores. O SHA-256 do worker nessas duas versões é 210c2a96339017579c7c81faa3f755b386e47475ea4e2c9ba7108fb577326c65; o worker 0.1.2 tinha SHA-256 5cf11071f8b06e5ee05dd9ef114a9b7f7060949e5a0836728390b4ae4a67db68.
NativeTLS.pas, libssl.so.3 e libcrypto.so.3 conferem byte a byte entre os runtimes Linux 0.1.2, 0.2.0 e 0.2.1. Isso sustenta a comparação da implementação TLS. Não houve novo probe público com o runtime 0.2.1 nesta auditoria; igualdade de arquivos não é um novo teste de conexão nem comprova uma operação fiscal. A versão 0.2.1 não acrescentou exportação de SOAP/RetWS completo nem uma nova unidade NativeDiagnostics.
O manifesto versionado da distribuição Linux 0.2.1 ancora a referência pública dos artefatos. Esse commit de referência não é tratado como prova do commit usado no build: o pacote npm não disponibilizou gitHead. Os hashes do pacote, do manifesto e dos arquivos auditados constam do relatório baixável.
Primeiro preserve o estado da operação fiscal
Se o retorno indicar RESULTADO_INDETERMINADO, timeout ou falha depois do marco de envio, suspenda o reenvio e siga o protocolo de recuperação documentado para a operação. Não gere outra numeração para contornar a incerteza. Uma consulta posterior também deve ter seu horário, ambiente e retorno preservados; uma resposta isolada não substitui a reconciliação da tentativa original.
Quando o diagnóstico original estiver disponível, diferencie TLS_CONFIG_INVALIDA (por exemplo, bundle inexistente ou repositório ROOT indisponível/vazio) de TLS_SERVIDOR_INVALIDO (recusa genérica que pode envolver conexão, cadeia, validade ou hostname). O segundo código não identifica exclusivamente uma CA ausente. Nas versões auditadas, o worker pode marcar o início de envio antes da chamada ACBr e, em certos caminhos, converter a exceção posterior em resultado indeterminado sem conservar sua mensagem. Não há como recuperar, por uma API pública, um texto que já foi descartado.
Preserve apenas os dados que o contrato daquela versão entrega, em armazenamento restrito: código, motivo, etapa e correlação local, quando disponíveis. PHP pode manter detalhes em FiscalResult::data; Python expõe codigo/x_motivo, que não aparecem necessariamente em to_dict(); no frame Node, observe codigo/xMotivo. Evite dumps integrais: respostas e logs podem conter XML, chaves fiscais, identificação do emitente e outros dados privados. Antes de compartilhar um diagnóstico, remova esses dados, A1, senhas, tokens e caminhos privados.
Diagnóstico sem A1 e sem emitir documentos
Execute no mesmo sistema de arquivos/contêiner/distribuição em que o motor roda. O Windows, uma distribuição WSL e cada imagem de contêiner podem ter repositórios de confiança distintos. Não reinicie serviços fiscais para fazer esta inspeção.
Inspecione as versões e a hora UTC:
openssl version -a
curl --version
date -u
timedatectl statustimedatectl status consulta relógio e sincronização em ambientes com systemd. Se indisponível, registre a hora por date -u e peça ao administrador que verifique a sincronização do ambiente. Não ajuste a hora para fazer um certificado vencido parecer válido. Manual oficial do timedatectl.
Escolha o hostname do endpoint de HOMOLOGAÇÃO já confirmado na configuração autorizada. Este exemplo contém um marcador que precisa ser substituído conscientemente; não inclui certificado cliente, senha, chave fiscal nem chamada SOAP:
tls_host='HOST_HOMOLOGACAO_CONFIRMADO'
timeout 20s openssl s_client \
-connect "${tls_host}:443" \
-servername "$tls_host" \
-verify_hostname "$tls_host" \
-verify_return_error \
-tls1_2 \
-CAfile /etc/ssl/certs/ca-certificates.crt \
-showcerts </dev/null-servername envia SNI; -verify_hostname verifica o nome; -verify_return_error interrompe o handshake se a verificação falhar. Sem esta última opção, s_client é uma ferramenta de teste que pode continuar após erros. -showcerts mostra os certificados enviados pelo servidor, não uma cadeia já validada. Saída por timeout não é prova de falha de certificado. Um servidor que exige mTLS pode recusar a ausência do certificado cliente depois da verificação do servidor; isso não comprova que o A1 ou a operação fiscal funcionem. Manual oficial do s_client.
Interprete a evidência antes de corrigir
| Evidência | Hipótese a verificar | Próximo passo |
|---|---|---|
unable to get local issuer certificate ou unable to verify the first certificate | Falta uma raiz local ou uma intermediária na cadeia recebida | Compare os certificados enviados, a cadeia oficial e o truststore realmente usado. O texto sozinho não distingue as duas causas. |
| Certificado de servidor vencido ou ainda não válido | Relógio incorreto ou certificado fora da validade | Compare UTC e notBefore/notAfter; encaminhe a renovação ou o ajuste de relógio ao responsável. |
hostname mismatch | Nome DNS diferente do certificado, SNI incorreto ou endpoint incorreto | Confirme o endereço oficial, o SNI e os nomes do certificado. Não substitua o hostname por um IP. |
| Emissor muda entre duas redes, ou aparece CA corporativa | Inspeção TLS/proxy ou outro caminho de rede | Compare os certificados e confirme a política com a equipe de rede antes de confiar em outra CA. |
| Verificação funciona em uma ferramenta e falha no worker | Backend, truststore, versão ou ambiente diferentes | Identifique o componente da conexão e o ambiente de execução real. |
O OpenSSL verifica a formação e as relações da cadeia, suas restrições e as datas contra o relógio do sistema. Uma CA intermediária pode ajudar a construir o caminho sem se tornar uma âncora de confiança. Opções oficiais de verificação do OpenSSL.
Quando necessário, um administrador pode conferir offline uma cópia do certificado público do servidor (servidor.pem) com a raiz autenticada (icp-brasil-v10.crt) e as intermediárias oficiais (intermediarias.pem):
openssl verify \
-CAfile ./icp-brasil-v10.crt \
-untrusted ./intermediarias.pem \
-purpose sslserver \
-verify_hostname "$tls_host" \
./servidor.pemO ZIP do aviso é uma referência histórica. Na evidência desse diagnóstico, a raiz arquivada conferiu, mas a intermediária do ZIP não era idêntica à apresentada pelo endpoint atual. Não substitua a cadeia atual por uma intermediária antiga só porque veio de um arquivo oficial. Confira a cadeia recebida no handshake e a fonte oficial vigente para aquele certificado.
Os arquivos precisam corresponder à cadeia daquele servidor. Se a validação só passa com uma intermediária que não veio no handshake, há evidência de cadeia incompleta servida pelo endpoint; o responsável pelo servidor deve corrigir essa entrega. Não transforme a intermediária ou o certificado final em raiz confiada para mascarar o problema. Manual oficial do openssl verify.
O curl também depende do backend com que foi compilado; Schannel pode usar o repositório Windows, enquanto outros arranjos usam arquivos. Um proxy HTTPS tem sua própria conexão TLS e sua verificação pode ser distinta da conexão ao servidor final. Não publique valores de variáveis de proxy: podem conter credenciais. Verificação TLS no curl.
Obtenha e autentique a raiz pública
O repositório AC-Raiz do ITI identifica a V10 para SSL e aponta para o certificado oficial ICP-Brasilv10.crt. O download HTTPS validado foi conferido novamente em 17/10/2026. Na comparação histórica com ac_raiz_br_v10.cer do ZIP oficial da SEFAZ-PE, as impressões digitais DER coincidiram.
| Campo conferido em 17/10/2026 | Valor |
|---|---|
| Formato e tamanho do arquivo servido pelo ITI | PEM, 2374 bytes |
| SHA-256 dos bytes do arquivo PEM baixado | 3b3ef39649ba13e10a99bc042cfc112723d8e0503326174f15ab89941a0f33e0 |
| Subject e issuer da raiz | CN=Autoridade Certificadora Raiz Brasileira v10, OU=Instituto Nacional de Tecnologia da Informacao - ITI, O=ICP-Brasil, C=BR |
| Serial | 00D2D58B44BF819342 |
| Início da validade, UTC | 2019-07-01 19:15:59 |
| Fim da validade, UTC | 2032-07-01 12:00:59 |
| SHA-256 do certificado DER | 6E:0B:FF:06:9A:26:99:4C:15:DE:2C:48:88:CC:54:AF:84:88:2E:54:95:B7:FB:F6:6B:E9:CC:FF:EC:74:89:F6 |
Os dois hashes acima medem objetos diferentes: o SHA-256 do arquivo inclui os bytes exatos do texto PEM, enquanto a impressão digital DER identifica o certificado decodificado. Normalizar quebras de linha ou regravar o PEM pode mudar o hash do arquivo sem mudar a impressão digital DER. Nenhum desses valores é o hash do ZIP, que também pode mudar quando a SEFAZ atualiza o pacote. Confirme a identidade por fonte oficial HTTPS e por um segundo canal confiável da organização; um nome de arquivo ou o campo subject isolado não autentica uma CA. Se a impressão digital DER divergir, pare e investigue uma eventual renovação oficial.
Baixe para uma pasta de trabalho, mantendo a validação HTTPS ativa. Se esse download falhar por TLS, obtenha o certificado por outro ambiente confiável e transfira-o pelo procedimento aprovado; não desative a verificação para baixar a própria raiz:
curl --proto '=https' --proto-redir '=https' --tlsv1.2 --location --fail --silent --show-error \
--output ./ICP-Brasilv10.download \
'https://acraiz.icpbrasil.gov.br/credenciadas/RAIZ/ICP-Brasilv10.crt'Confira o hash dos bytes baixados antes de qualquer conversão; na conferência acima, ele foi 3b3ef39649ba13e10a99bc042cfc112723d8e0503326174f15ab89941a0f33e0:
sha256sum ./ICP-Brasilv10.downloadO arquivo servido pelo ITI estava em PEM na conferência. Normalize e inspecione localmente; -fingerprint -sha256 calcula a impressão digital do certificado DER, não o hash do arquivo de entrada:
openssl x509 -in ./ICP-Brasilv10.download -out ./icp-brasil-v10.crt
openssl x509 -in ./icp-brasil-v10.crt -noout \
-subject -issuer -serial -dates -fingerprint -sha256
openssl x509 -in ./icp-brasil-v10.crt -noout -textVerifique CA:TRUE, uso de assinatura de certificados, identidade e fingerprint. Ser autoassinado e ter CA:TRUE não basta para merecer confiança. Se o fornecedor oficial passar a distribuir DER, converta explicitamente com openssl x509 -inform DER; não interprete arquivos desconhecidos como certificados válidos. Manual oficial do openssl x509.
Correção Ubuntu/Debian: somente com aprovação administrativa
Faça esta etapa apenas se a evidência confirmar a raiz ausente e o administrador aprovar a CA, sua impressão digital e o ambiente a alterar. Adicionar uma raiz amplia a confiança de todos os aplicativos daquele ambiente que consultam esse repositório; não fica limitado ao SDK. Também podem ser executados hooks de atualização de certificados. A mudança no host não prova mudança na imagem de um contêiner ou em outra distribuição WSL.
O ca-certificates do Ubuntu/Debian reconhece certificados locais PEM com extensão .crt em /usr/local/share/ca-certificates; use um certificado por arquivo. update-ca-certificates atualiza /etc/ssl/certs e o bundle /etc/ssl/certs/ca-certificates.crt. Não substitua esse bundle por um arquivo contendo apenas a nova raiz e não importe todo o ZIP como raízes. Manual Debian de update-ca-certificates.
O exemplo abaixo pressupõe pacote ca-certificates instalado, raiz já autenticada e execução em Bash. Registra uma cópia pública do bundle e inventários das impressões digitais antes/depois; recusa sobrescrever um certificado local existente. Execute em uma pasta de trabalho de acesso restrito e interrompa se qualquer verificação falhar. Contar certificados não demonstra que as identidades anteriores foram preservadas:
(
set -eu
set -o pipefail
tls_inventory() {
local tls_bundle="$1"
local tls_output="$2"
local tls_tmp
local tls_cert
tls_tmp="$(mktemp -d ./tls-inventory.XXXXXX)"
awk -v dir="$tls_tmp" '
/-----BEGIN CERTIFICATE-----/ {
n++
file=sprintf("%s/cert-%06d.pem", dir, n)
}
file != "" { print > file }
/-----END CERTIFICATE-----/ { close(file); file="" }
' "$tls_bundle"
for tls_cert in "$tls_tmp"/cert-*.pem; do
test -f "$tls_cert"
openssl x509 -in "$tls_cert" -outform DER | sha256sum | cut -d ' ' -f 1
done | LC_ALL=C sort -u > "$tls_output"
}
tls_sha256="$(openssl x509 -in ./icp-brasil-v10.crt -outform DER | sha256sum | cut -d ' ' -f 1)"
test "$tls_sha256" = '6e0bff069a26994c15de2c4888cc54af84882e5495b7fbf66be9ccffec7489f6'
tls_dest='/usr/local/share/ca-certificates/icp-brasil-v10.crt'
if [ -e "$tls_dest" ] || [ -L "$tls_dest" ]; then
printf '%s\n' 'Destino já existe: conferir identidade antes de prosseguir.' >&2
exit 1
fi
tls_backup="$(mktemp -d ./tls-trust-backup.XXXXXX)"
printf 'Backup e inventários deste procedimento: %s\n' "$tls_backup"
cp /etc/ssl/certs/ca-certificates.crt "$tls_backup/ca-certificates.crt.before"
sha256sum "$tls_backup/ca-certificates.crt.before" > "$tls_backup/SHA256SUMS"
tls_inventory "$tls_backup/ca-certificates.crt.before" "$tls_backup/before.sha256"
if grep -Fqx "$tls_sha256" "$tls_backup/before.sha256"; then
printf '%s\n' 'A raiz já está no bundle: investigar qual truststore o motor consultou.' >&2
exit 1
fi
sudo install -m 0644 ./icp-brasil-v10.crt "$tls_dest"
sudo update-ca-certificates
cp /etc/ssl/certs/ca-certificates.crt "$tls_backup/ca-certificates.crt.after"
tls_inventory "$tls_backup/ca-certificates.crt.after" "$tls_backup/after.sha256"
LC_ALL=C comm -23 "$tls_backup/before.sha256" "$tls_backup/after.sha256" > "$tls_backup/removed.sha256"
LC_ALL=C comm -13 "$tls_backup/before.sha256" "$tls_backup/after.sha256" > "$tls_backup/added.sha256"
test ! -s "$tls_backup/removed.sha256"
grep -Fqx "$tls_sha256" "$tls_backup/added.sha256"
printf 'Revise os inventários e as alterações em: %s\n' "$tls_backup"
)O procedimento acrescenta o arquivo local; não exige remover outras raízes, reconstruir toda a distribuição ou reiniciar WSL/Docker. A atualização executa hooks e pode incorporar alterações que já estavam pendentes no ambiente. Revise a saída e os conjuntos de fingerprints: removed.sha256 deve estar vazio, e added.sha256 deve conter a raiz esperada. Qualquer outra alteração precisa ser explicada e aprovada; a receita, isoladamente, não garante que outros certificados permanecerão iguais. Os diretórios de evidência contêm somente certificados públicos e devem ser guardados até a revisão. Uma reversão, quando aprovada, deve remover somente a raiz adicionada e atualizar o repositório pelo procedimento administrativo daquela distribuição. Orientação oficial do Ubuntu para raízes locais.
Valide a correção e retome com controle
- Repita a verificação do certificado público com hostname, SNI, validade e o truststore efetivamente consultado. Registre o horário UTC e o resultado; não forneça A1 para esse diagnóstico inicial.
- Confirme no mesmo ambiente do worker se o motor continua usando o repositório auditado. Eventual cache de processo precisa ser avaliado pelo responsável, sem reiniciar atividades fiscais às cegas.
- Reconcilie operações incertas pelo guia de recuperação da versão em uso. Retomada de operação fiscal exige sua autorização e controles próprios, mesmo em HOMOLOGAÇÃO.
- Diferencie as evidências: build, teste offline e mock comprovam somente seus respectivos caminhos; handshake aceito comprova aquela verificação TLS; homologação real exige uma operação autorizada com retorno fiscal preservado. Nenhuma dessas evidências é substituta automática das demais.
Mantenha a verificação TLS habilitada. Desativá-la elimina justamente a conferência de identidade que protege a comunicação; não corrige a cadeia, o relógio ou o proxy. Não use a produção para testar hipóteses de confiança.