O que é mTLS e por que a ICP-Brasil usa
No TLS convencional — o cadeado do HTTPS — só o servidor apresenta um certificado. O cliente confirma que está falando com quem diz ser, mas o servidor não faz ideia de quem é o cliente. É o suficiente para navegar num site, não para provar identidade.
O mTLS (TLS mútuo, ou mutual TLS) inverte também o outro lado: durante o handshake, o servidor exige que o cliente apresente um certificado e o valida antes de deixar a conexão prosseguir. É esse mecanismo que permite usar um certificado ICP-Brasil (e-CPF para pessoa física, e-CNPJ para empresa) como prova de identidade forte no login de um sistema — sem senha, sem SMS, com garantia jurídica de quem está do outro lado.
A parte específica da ICP-Brasil é a cadeia de confiança: um certificado e-CPF não é assinado diretamente pela raiz, mas por uma Autoridade Certificadora (AC) intermediária, que por sua vez encadeia até a AC-Raiz da ICP-Brasil. Para o NGINX aceitar um e-CPF, ele precisa conhecer toda a cadeia — a raiz e todas as intermediárias válidas. É aqui que a coisa começa a ficar trabalhosa, como você vai ver.
Pré-requisitos
- NGINX compilado com o módulo ngx_http_ssl_module (o padrão da maioria das distribuições já inclui).
- Um certificado de servidor válido para o seu domínio (Let's Encrypt serve).
- Um arquivo .pem contendo a cadeia de ACs da ICP-Brasil que você quer confiar (vamos montar na próxima seção).
- Para testar: um e-CPF ou e-CNPJ A1 (arquivo) instalado no navegador, ou um A3 (token/smartcard) com o driver configurado.
Montando a cadeia de ACs confiáveis
O NGINX valida o certificado do cliente contra um arquivo único que reúne os certificados das ACs em que você confia. Os certificados oficiais são publicados pelo ITI (Instituto Nacional de Tecnologia da Informação), que opera a AC-Raiz.
Na prática, você baixa os certificados da AC-Raiz e de cada AC intermediária e concatena tudo num só arquivo:
# cada .crt é uma AC (raiz + intermediárias) baixada do repositório do ITI
cat ac-raiz.crt \
ac-intermediaria-1.crt \
ac-intermediaria-2.crt \
# ... e assim por diante, para cada AC válida
> icp-brasil-cadeia.pem
Configuração do NGINX (bloco server)
Com a cadeia montada, o bloco server fica assim. As três diretivas que fazem o mTLS acontecer são ssl_client_certificate, ssl_verify_client e ssl_verify_depth:
server {
listen 443 ssl;
server_name app.seudominio.com.br;
# certificado do SERVIDOR (o HTTPS normal)
ssl_certificate /etc/nginx/ssl/fullchain.pem;
ssl_certificate_key /etc/nginx/ssl/privkey.pem;
# ---- mTLS: validação do certificado do CLIENTE ----
# arquivo com a cadeia de ACs da ICP-Brasil que você confia
ssl_client_certificate /etc/nginx/ssl/icp-brasil-cadeia.pem;
# 'on' = exige e rejeita se inválido
# 'optional' = pede, mas deixa passar (você decide no backend)
ssl_verify_client on;
# profundidade da cadeia: raiz -> intermediária -> cliente
# na ICP-Brasil normalmente 2 é o mínimo seguro
ssl_verify_depth 2;
location / {
proxy_pass http://127.0.0.1:8000;
# repassa os dados do certificado ao backend (ver próxima seção)
proxy_set_header X-SSL-Verify $ssl_client_verify;
proxy_set_header X-SSL-Subject $ssl_client_s_dn;
proxy_set_header X-SSL-Cert $ssl_client_escaped_cert;
}
}
Sobre ssl_verify_client, vale entender a diferença prática:
- on — o NGINX rejeita a conexão no handshake se o certificado for inválido ou ausente. O backend nunca vê a requisição. Mais seguro, porém o erro que o usuário vê é cru (do navegador), difícil de customizar.
- optional — o NGINX pede o certificado, valida se vier, mas deixa a conexão passar de qualquer forma. Você lê $ssl_client_verify no backend e decide o que fazer. Dá muito mais controle sobre a mensagem de erro e o fluxo de login.
Testando a configuração
# valida a sintaxe antes de recarregar
nginx -t
# recarrega sem derrubar conexões ativas
systemctl reload nginx
Repassando o certificado ao backend
O NGINX faz a validação, mas quem decide "esse e-CPF é do usuário João, libera a sessão" é a sua aplicação. Por isso os proxy_set_header acima: eles injetam nos cabeçalhos o resultado da verificação e os dados do certificado.
As variáveis mais úteis que o NGINX expõe:
- $ssl_client_verify — resultado da validação: SUCCESS, FAILED ou NONE.
- $ssl_client_s_dn — o Subject DN, onde estão nome e, no padrão ICP-Brasil, os dados do titular.
- $ssl_client_escaped_cert — o certificado completo (URL-encoded), caso você precise fazer o parsing dos campos por conta própria.
No backend, você lê esses cabeçalhos e segue com a criação da sessão. Um exemplo mínimo em Python:
def autenticar(request):
verify = request.headers.get("X-SSL-Verify")
if verify != "SUCCESS":
return erro("Certificado não reconhecido", 401)
subject = request.headers.get("X-SSL-Subject")
# aqui vem a parte chata: extrair CPF/CNPJ e nome do Subject DN,
# que na ICP-Brasil ficam codificados em campos e OIDs específicos
dados = parse_icp_brasil(subject)
return criar_sessao(dados)
Erros comuns de handshake
Se você chegou até aqui, provavelmente vai esbarrar em pelo menos um destes. Os mais frequentes:
No required SSL certificate was sent
(unable to get local issuer)
navegadores / no A3
(ou o contrário)
O trabalho que não acaba: manutenção
Colocar o mTLS de pé, como você viu, é factível numa tarde. O que raramente entra na conta é o que vem depois do deploy — e é aí que mora o custo real:
- A cadeia muda. A ICP-Brasil adiciona e renova ACs. Cada mudança que você não acompanhar vira um usuário legítimo barrado no handshake, com um erro que ninguém entende às duas da manhã.
- Revogação. Validar CRL/OCSP corretamente é um projeto à parte, com CRLs que também precisam ser atualizadas periodicamente.
- Compatibilidade de navegador. A negociação de certificado cliente se comporta de forma diferente entre Chrome, Firefox, Edge e Safari — e muda com o tempo. Testar e manter isso funcionando em todos é um trabalho recorrente.
- Parsing dos dados. Extrair e interpretar corretamente e-CPF e e-CNPJ dos OIDs proprietários, cobrindo A1, A3 e as variações de emissão.
Nada disso é impossível. Mas é um sistema vivo que precisa de atenção contínua — e que não tem relação nenhuma com o produto que você realmente quer construir. É infraestrutura de identidade, não a sua feature.
Autenticação ICP-Brasil como serviço
O IHub-Auth entrega login por e-CPF e e-CNPJ sem você montar nem manter mTLS. A cadeia ICP-Brasil completa fica atualizada do nosso lado, a validação acontece via verificação server-to-server, e você recebe os dados do titular já estruturados. Integração em minutos — inclua um script, configure seu token, pronto.