Tutorial · C# / .NET

Validando a cadeia ICP-Brasil no backend em C# / .NET

Receber um certificado do cliente é fácil. Provar que ele é um e-CPF ou e-CNPJ legítimo, emitido por uma AC confiável e não revogado, é o trabalho de verdade. Veja como fazer com X509Chain — e onde mora a manutenção que ninguém conta.

X509Chain · X509Certificate2 .NET 6+ Leitura: ~10 min
Neste guia
  1. Por que validar não é só "recebeu, confia"
  2. Montando o repositório de ACs confiáveis
  3. Validando com X509Chain
  4. Checando revogação (CRL / OCSP)
  5. Extraindo CPF, CNPJ e nome do titular
  6. Armadilhas comuns
  7. O que precisa de manutenção contínua

Por que validar não é só "recebeu, confia"

Quando o seu servidor recebe um certificado de cliente — via mTLS ou enviado pela aplicação —, ter o certificado em mãos não significa nada por si só. Qualquer um pode gerar um certificado dizendo "sou o CPF 123". O que dá valor a um e-CPF é a cadeia de confiança: ele foi assinado por uma AC intermediária, que foi assinada por outra, até chegar na AC-Raiz da ICP-Brasil, na qual você — e a legislação brasileira — confia.

Validar corretamente significa responder a quatro perguntas, nesta ordem:

  1. O certificado encadeia até uma AC-Raiz da ICP-Brasil em que eu confio?
  2. Todos os certificados da cadeia estão dentro da validade (nem expirados, nem futuros)?
  3. Nenhum deles foi revogado?
  4. Quais são os dados do titular (CPF/CNPJ, nome) que posso confiar?

Pular qualquer uma dessas etapas abre um buraco de segurança. O .NET oferece as ferramentas para todas — mas você precisa configurá-las certo, porque os padrões não conhecem a ICP-Brasil.

Montando o repositório de ACs confiáveis

O .NET, por padrão, valida contra as âncoras de confiança do sistema operacional. A AC-Raiz da ICP-Brasil não está nesse conjunto na maioria dos servidores. Então o primeiro passo é carregar, você mesmo, os certificados da raiz e das intermediárias que quer aceitar.

C# — carregando as ACs da ICP-Brasil
using System.Security.Cryptography.X509Certificates;

// carrega cada .cer/.crt baixado do repositório do ITI
var acsConfiaveis = new X509Certificate2Collection();

foreach (var arquivo in Directory.GetFiles("acs-icp-brasil", "*.cer"))
{
    acsConfiaveis.Add(new X509Certificate2(arquivo));
}
Esse conjunto não é fixo. A ICP-Brasil adiciona, renova e revoga ACs periodicamente. Se uma AC intermediária nova entrar em operação e você não tiver o certificado dela nessa pasta, todo e-CPF emitido por ela vai falhar a validação na sua aplicação — mesmo sendo perfeitamente legítimo. Manter a cadeia completa e atual é uma rotina, não um passo único.

Validando com X509Chain

O X509Chain é a classe do .NET que monta e valida a cadeia. O ponto-chave é configurar o ChainPolicy para usar suas ACs como âncoras de confiança, em vez das do sistema:

C# — validação da cadeia
public bool ValidarCadeia(X509Certificate2 certCliente,
                          X509Certificate2Collection acsConfiaveis)
{
    using var chain = new X509Chain();

    // não usar o trust store do SO; usar só as ACs da ICP-Brasil
    chain.ChainPolicy.TrustMode = X509ChainTrustMode.CustomRootTrust;
    chain.ChainPolicy.CustomTrustStore.AddRange(acsConfiaveis);

    // incluir as intermediárias para o .NET conseguir montar a cadeia
    chain.ChainPolicy.ExtraStore.AddRange(acsConfiaveis);

    // revogação: ver a próxima seção
    chain.ChainPolicy.RevocationMode = X509RevocationMode.Online;
    chain.ChainPolicy.RevocationFlag = X509RevocationFlag.EntireChain;

    bool ok = chain.Build(certCliente);

    if (!ok)
    {
        foreach (var status in chain.ChainStatus)
            Console.WriteLine($"Falha: {status.StatusInformation}");
    }

    return ok;
}

O chain.Build() retorna true só se a cadeia inteira for válida sob a política definida. Quando falha, o ChainStatus diz exatamente por quê — e é aí que você vai passar boa parte do tempo de depuração.

Sobre o CustomRootTrust. Sem ele, o .NET valida contra o trust store do Windows/Linux, que não conhece a ICP-Brasil — e a validação falha mesmo com um certificado legítimo. Definir a raiz customizada é o passo que a maioria dos tutoriais genéricos de X509 esquece.

Checando revogação (CRL / OCSP)

Um certificado pode ser válido na cadeia e ainda assim ter sido revogado — por exemplo, quando alguém perde o token A3 e comunica a AC. Ignorar revogação significa aceitar certificados que deveriam estar bloqueados.

No código acima, RevocationMode = Online faz o .NET consultar as listas de revogação (CRL) ou OCSP publicadas pelas ACs. Parece automático, mas há armadilhas de produção:

Decisão de arquitetura. Verificação online é simples de escrever e frágil em produção. Verificação offline (baixar e cachear CRLs) é robusta e trabalhosa de manter. Não existe opção "de graça" — só o trade-off entre fragilidade e manutenção.

Extraindo CPF, CNPJ e nome do titular

Cadeia validada, falta o que você realmente queria: quem é essa pessoa/empresa? E aqui a ICP-Brasil complica de propósito. O CPF, o CNPJ, a data de nascimento e outros dados não ficam nos campos padrão do X.509 — ficam em extensões com OIDs proprietários (como o 2.16.76.1.3.*), codificados num formato específico.

C# — lendo a extensão do titular (e-CPF)
// OID da ICP-Brasil que carrega os dados da pessoa física
const string OID_PESSOA_FISICA = "2.16.76.1.3.1";

var extensao = certCliente.Extensions
    .Cast<X509Extension>()
    .FirstOrDefault(e => e.Oid?.Value == OID_PESSOA_FISICA);

if (extensao != null)
{
    byte[] raw = extensao.RawData;
    // raw é uma string ASN.1 com campos concatenados por posição:
    // data de nascimento, CPF, RG, órgão emissor... tudo em offsets fixos.
    // você precisa fatiar por posição e decodificar cada pedaço.
    var dados = ParseCamposPessoaFisica(raw);
}

O ParseCamposPessoaFisica() esconde a real dificuldade: os campos vêm concatenados por posição numa string, com tamanhos fixos que você tem de conhecer de antemão. E muda entre e-CPF e e-CNPJ (OID 2.16.76.1.3.3 para o CNPJ), entre versões de certificado e entre ACs. É o tipo de código que funciona nos testes e quebra com o certificado de um cliente específico seis meses depois.

Armadilhas comuns

Sintoma
Causa provável
UntrustedRoot mesmo com
certificado legítimo
Faltou TrustMode = CustomRootTrust com a AC-Raiz da ICP-Brasil no CustomTrustStore. O .NET está validando contra o trust store do SO.
PartialChain
Uma AC intermediária da cadeia daquele certificado não está no seu ExtraStore. Normalmente uma AC nova que você ainda não baixou.
RevocationStatusUnknown
Não conseguiu acessar CRL/OCSP (timeout, endpoint fora do ar) ou a AC não publica o método consultado.
CPF/CNPJ vem "embaralhado"
ou vazio
Parsing por offset errado, ou o certificado é de um tipo/versão que sua função não cobre. Muito comum ao encontrar o primeiro certificado "diferente" em produção.

O que precisa de manutenção contínua

Escrever a validação uma vez, para os certificados que você tem hoje, é a parte visível — e a menor. O que fica é o sistema vivo por trás:

É trabalho de manutenção permanente, sensível a segurança, sobre uma norma que não é sua e muda no ritmo dela. Funciona — mas rouba tempo de engenharia do produto que você de fato quer entregar.

Ou não escreva nada disso

Deixe a validação ICP-Brasil com quem só faz isso

Com o IHub-Auth você não monta X509Chain, não persegue CRL, não faz parsing de OID proprietário. A cadeia ICP-Brasil completa fica atualizada do nosso lado, a validação acontece via verificação server-to-server, e o seu backend recebe CPF, CNPJ e nome do titular já prontos. Você integra em minutos e volta a cuidar do seu produto.