Identidade do certificado
Ler o CPF/CNPJ do certificado, recusar antes do PIN e restringir quem pode assinar.
Antes de o usuário digitar o PIN, a API já validou o certificado e devolveu a identidade dele. Este guia mostra como usar essa identidade, recusar o certificado errado antes do PIN e restringir quem pode assinar.
O que a API valida no prepare#
No prepare, a API confere a cadeia até uma raiz do contexto de segurança escolhido, a validade e o uso da chave (o certificado precisa poder assinar). Se algo falhar, o erro vem antes do PIN, e nada é gasto. Nos perfis AD-RC e AD-RA ela também consulta a revogação (LCR e OCSP).
A resposta traz a identidade em certificate:
{
"thumbprint": "…",
"subjectDn": "CN=MARIA DA SILVA:12345678909,OU=…,O=ICP-Brasil,C=BR",
"issuerDn": "CN=AC Exemplo,OU=…,O=ICP-Brasil,C=BR",
"subjectCn": "MARIA DA SILVA",
"issuerCn": "AC Exemplo",
"serialNumber": "…",
"pkiBrazil": { "kind": "pf", "cpf": "12345678909", "cnpj": null, "…": "…" }
}
CPF, CNPJ e demais dados: pkiBrazil#
O bloco pkiBrazil é lido pelo servidor dos campos ICP-Brasil do certificado. Sempre existe: fora da ICP-Brasil, todos os campos são null.
| Campo | Observação |
|---|---|
kind |
"pf" = e-CPF (tem cpf), "pj" = e-CNPJ (tem cnpj), null = sem campos ICP-Brasil. |
cpf, birthDate, rg, rgIssuer, nis |
Só do titular pessoa física. Em e-CNPJ, cpf é null (não confunda com o do responsável). |
cnpj |
14 dígitos. |
companyName |
Razão social do e-CNPJ, já sem o :CNPJ do nome. null se não for e-CNPJ. |
responsibleName, responsibleCpf, responsibleBirthDate |
Dados do responsável, no e-CNPJ. |
email |
O e-mail do certificado. |
- Os valores vêm só com dígitos, sem máscara (
"12345678909"). Campo ausente, zerado ou de tamanho errado viranull: nunca vem lixo. - Os dígitos verificadores não são conferidos: cabe ao seu formulário, se quiser.
Use o que a API devolve, não o que o navegador mostra
O navegador também lista pkiBrazil na tela de escolha, mas isso só organiza a interface: não é controle de acesso. Para auditoria e regra de negócio use o bloco devolvido pela API no prepare e no complete, lido do certificado que de fato assinou e já validado até a raiz.
Recusar antes do PIN#
O padrão para garantir que a pessoa certa assina: compare o CPF ou CNPJ do certificado com o que o seu cadastro espera, no prepare, e descarte a sessão se não bater.
$p = $api->prepare($token, $certificadoDerBase64);
$pk = $p['certificate']['pkiBrazil'];
$daAssinatura = array_filter([$pk['cpf'], $pk['cnpj'], $pk['responsibleCpf']]);
if (!in_array($cpfOuCnpjEsperado, $daAssinatura, true)) { // só dígitos nos dois lados
$api->discard($token); // nada foi assinado, nada foi gasto
throw new DomainException('certificado de outra pessoa');
}
Para um e-CNPJ, aceite o CNPJ da empresa ou o CPF do responsável, conforme a sua regra. Em JavaScript (Node), a opção beforeSign de signPades faz o mesmo: ela roda depois do prepare e antes do PIN, e lançar um erro descarta a sessão.
Restringir a AC emissora: acceptableIssuers#
No início da assinatura, acceptableIssuers limita o certificado a ACs específicas. A restrição é imposta no servidor, no prepare, antes do PIN:
$api->startMultipart($arquivos, 'pades-icpbrasil-ad-rb', 'icp-brasil', null,
['CN=AC SOLUTI Multipla v5', 'a3f1…64 caracteres hexadecimais da impressão digital de uma AC']); // até 20 itens
Cada item é o DN (RFC 4514, em qualquer ordem, sem diferenciar maiúsculas) ou a impressão digital SHA-256 de uma AC da cadeia validada do certificado (intermediária ou raiz; o certificado final não conta). Basta um item casar. A conferência acontece depois de a cadeia ser validada contra as raízes do contexto: uma AC de mesmo nome que não esteja na cadeia não passa. Sem casar, a API responde 422 certificate_issuer_not_allowed.
Por que o certificado foi recusado: error.reason#
As recusas de certificado (certificate_not_trusted, invalid_certificate e certificate_issuer_not_allowed) trazem um subcódigo estável, sem dados pessoais, para a sua tela explicar sem adivinhar:
reason |
Significa | Mensagem sugerida ao usuário |
|---|---|---|
expired |
O certificado (ou uma AC da cadeia) está vencido. | "Este certificado venceu. Use outro ou renove-o." |
not_yet_valid |
Ainda não começou a valer. | "Este certificado ainda não está válido (confira a data do computador)." |
chain |
A cadeia não fecha numa raiz do contexto (AC desconhecida, autoassinado). | "Este certificado não é aceito aqui (AC não reconhecida)." |
key_usage |
O certificado não serve para assinar. | "Este certificado não permite assinar documentos." |
revoked |
Revogado (AD-RC e AD-RA). | "Este certificado foi revogado." |
revocation_unavailable |
Não foi possível consultar a revogação. | "Não foi possível conferir a revogação agora; tente de novo." |
algorithm |
Algoritmo de chave ou de assinatura não aceito. | "Tipo de certificado não suportado." |
malformed |
O arquivo enviado não é um certificado X.509. | (erro de integração) |
issuer_not_allowed |
A AC não está em acceptableIssuers. |
"Use um certificado de uma das ACs aceitas." |
other |
Qualquer outra falha de validação (veja message). |
Mensagem genérica. |
try {
$api->prepare($token, $der);
} catch (ApiException $e) {
if ($e->errorCode === 'certificate_not_trusted') {
$motivo = $e->reason; // 'expired' | 'chain' | 'key_usage' | ...
}
}