Assinar um PDF (PAdES) - PKI Express
O PKI Express assina PDFs (PAdES) no seu servidor de duas formas:
- Com o certificado do usuário, em duas etapas: o PKI Express calcula o hash a assinar, o Web PKI o assina no navegador com a chave do usuário e o PKI Express grava o PDF assinado.
- Com uma chave no servidor, em uma etapa e sem navegador: o PKI Express assina com uma chave que o seu servidor guarda, como um arquivo PKCS #12 (.pfx).
As duas usam os comandos start-pades, complete-sig e sign-pades, presentes desde o PKI Express 1.0.0; assinar com um arquivo PKCS #12
exige a 1.3.0 ou posterior.
O código é Node.js, dos samples do PKI Suite, com o pki-express (1.16.2); os
samples em Java, PHP, Python e Ruby fazem o mesmo.
Para um passo a passo do sample em Java, veja Como funciona a assinatura com PKI Express em Java.
Fluxo com o certificado do usuário
- Navegador: o Web PKI lista os certificados do usuário e lê o que ele escolher (
readCertificate()); a página envia o conteúdo ao servidor. - Servidor:
PadesSignatureStarter.start()devolve o hash a assinar, o algoritmo de hash e um arquivo de transferência. - Navegador: o Web PKI assina o hash com
signHash()e envia a assinatura. - Servidor:
SignatureFinisher.complete(), com o mesmo PDF, o arquivo de transferência e a assinatura, grava o PDF assinado.
1. Iniciar a assinatura
const signatureStarter = new PadesSignatureStarter();
Util.setPkiDefaults(signatureStarter);
signatureStarter.signaturePolicy = StandardSignaturePolicies.PADES_BASIC_WITH_LTV;
signatureStarter.setPdfToSignFromPathSync(StorageMock.getDataPath(fileId));
signatureStarter.setCertificateFromBase64Sync(certContent);
signatureStarter.addFileReferenceSync('stamp', StorageMock.getPdfStampPath());
signatureStarter
.setVisualRepresentationSync(PadesVisualElementsExpress.getVisualRepresentation());
signatureStarter.start()
.then((response) => {
const { toSignHash } = response;
const { digestAlgorithm } = response;
const { transferFile } = response;
// render the page that signs the hash, with these values in hidden fields
}).catch((err) => next(err));
certContenté o certificado do usuário em Base64, como oreadCertificate()do Web PKI o devolve.start()resolve paratoSignHash,digestAlgorithm,digestAlgorithmOidetransferFile, o nome de um arquivo que o PKI Express mantém na pasta de transfer data até a assinatura ser concluída.Util.setPkiDefaults()aplica as configurações de confiança e a autoridade de carimbo de tempo dos samples; veja Validar assinaturas e Carimbo de tempo em PDF.- Se o PDF já estiver assinado, o mesmo fluxo acrescenta uma assinatura a ele.
2. Assinar o hash no navegador
pki.signHash({
thumbprint: formElements.certThumbField.val(),
hash: formElements.toSignHashField.val(),
digestAlgorithm: formElements.digestAlgorithmField.val()
}).success(function (signature) {
formElements.signatureField.val(signature);
formElements.form.submit();
});
O thumbprint é o do certificado lido na primeira etapa; a página o guarda, com o arquivo de transferência, em campos ocultos. Sobre o Web PKI, veja Web PKI.
3. Concluir a assinatura
const signatureFinisher = new SignatureFinisher();
Util.setPkiDefaults(signatureFinisher);
signatureFinisher.setFileToSignFromPathSync(StorageMock.getDataPath(fileId));
signatureFinisher.setTransferFileFromPathSync(transferFile);
signatureFinisher.signature = signature;
const outputFile = `${uuidv4()}.pdf`;
signatureFinisher.outputFile = path.join(APP_ROOT, 'app-data', outputFile);
signatureFinisher
.complete(true)
.then((certificate) => { /* signed PDF at outputFile */ })
.catch((err) => next(err));
- Informe ao
SignatureFinishero mesmo PDF da primeira etapa. - Com
complete(true), a Promise resolve para o certificado do signatário; isso exige o PKI Express 1.8 ou posterior. Sem o parâmetro, nada é devolvido. - O
SignatureFinishertambém conclui assinaturas CAdES e XML.
Assinar com uma chave no servidor
O PadesSigner assina em uma chamada, sem navegador. O sample assina com um arquivo PKCS #12 de teste, senha 1234, ou com um gerado a
partir de um certificado emitido pelo sample do Amplia.
const signer = new PadesSigner();
Util.setPkiDefaults(signer);
signer.signaturePolicy = StandardSignaturePolicies.PADES_BASIC_WITH_LTV;
await signer.setPdfToSignFromPath(StorageMock.getDataPath(fileId));
await signer.setPkcs12FromPath(StorageMock.getSamplePkcs12Path());
signer.certPassword = '1234';
await signer.addFileReference('stamp', StorageMock.getPdfStampPath());
await signer.setVisualRepresentation(PadesVisualElementsExpress.getVisualRepresentation());
const outputFile = `${uuidv4()}.pdf`;
signer.outputFile = path.join(APP_ROOT, 'app-data', outputFile);
let signerCert = await signer.sign(true);
- O PKCS #12 também pode ser informado em bytes (
setPkcs12FromRaw) ou em Base64 (setPkcs12FromBase64). - Em vez de um arquivo PKCS #12, a chave pode ser:
- um certificado instalado no servidor, pelo thumbprint (
certThumb; PKI Express 1.3 ou posterior); - uma chave pelo nome (
keyName) com o arquivo do certificado (setCertFileFromPath), no PKI Express 1.14 ou posterior, que acrescentou chaves guardadas no Azure Key Vault (veja Integração com o Azure); - um certificado em nuvem, por uma sessão de serviço de confiança: veja Serviços de certificado digital em nuvem.
- um certificado instalado no servidor, pelo thumbprint (
- Defina
overwriteOriginalFileem vez deoutputFilepara assinar o próprio PDF. - TODO(docs): em qual repositório de certificados o PKI Express procura o
certThumbno Linux, e se a opçãosetUseMachinedo Java vale lá. - TODO(docs): como usar uma chave em token PKCS #11 ou HSM; o PKI Express 1.38.0 acrescentou o
pkcs11LibraryPath, que nenhuma página descreve ainda.
Políticas de assinatura
A política é um valor de StandardSignaturePolicies e define o que entra na assinatura. Os samples usam PADES_BASIC_WITH_LTV.
| Constante (Node.js, Python) | Valor | Observações |
|---|---|---|
PADES_BASIC | pades | |
PADES_BASIC_WITH_LTV | pades-ltv | |
PADES_T | pades-t | exige uma autoridade de carimbo de tempo |
PKI_BRAZIL_PADES_ADR_BASICA | adrb | ICP-Brasil, PKI Express 1.12.0 ou posterior |
PKI_BRAZIL_PADES_ADR_BASICA_WITH_LTV | adrb-ltv | ICP-Brasil, PKI Express 1.12.0 ou posterior |
PKI_BRAZIL_PADES_ADR_TEMPO | adrt | ICP-Brasil, PKI Express 1.12.0 ou posterior; exige uma autoridade de carimbo de tempo |
- Com uma política que exige carimbo de tempo, a biblioteca Node.js se recusa a executar se não houver autoridade de carimbo de tempo; o
próprio PKI Express devolve o código 32 (
TimestampOptionsRequired). Veja Carimbo de tempo em PDF. - O Java dá às constantes nomes em camel case (
PadesBasicWithLTV,PkiBrazilPadesAdrBasica,PkiBrazilPadesAdrTempo) e acrescentaPkiBrazilPadesAdrCompleta(adrc) ePkiBrazilPadesAdrArquivamento(adra); o PKI Express 1.34.0 acrescentou as políticas PAdES AD-RC e AD-RA da ICP-Brasil. - TODO(docs): a biblioteca PHP chama a política PAdES AD-RA de
pades-ad-ra(PADES_AD_RA), e nãoadracomo o Java; não está documentado qual valor o PKI Express 1.34.0 espera. - Os nomes das políticas diferem dos do Rest PKI: veja a tabela de equivalência.
Representação visual e outras opções
- A representação visual (o carimbo desenhado na página) é um modelo JSON informado com
setVisualRepresentation(), ou um arquivo comsetVisualRepresentationFile(). As imagens que ele usa podem ser acrescentadas comaddFileReference(alias, path)e citadas comofref://alias. Veja Representação visual e as tags do texto. - Outras opções de
PadesSignatureStarterePadesSigner, com a versão do PKI Express que cada uma exige:
| Opção | Propriedade no Node.js | PKI Express |
|---|---|---|
| Motivo do signatário | reason | 1.13.0 |
| Ocultar a representação visual padrão | suppressDefaultVisualRep | 1.13.1 |
| Nome do campo de assinatura | customSignatureFieldName | 1.15.0 |
| Nível de certificação | certificationLevel | 1.16.0 |
| Nome do signatário e local | só Java: setSignerName, setLocation | 1.27.0 |
| Metadados do PDF | só PHP: addMetadata(key, value) | 1.38.0 |
Em cada linguagem
As classes têm o mesmo nome em todas as bibliotecas cliente; os setters mudam:
| Biblioteca | PDF e certificado (início) | Arquivo de transferência e assinatura (conclusão) | PKCS #12 (chave no servidor) |
|---|---|---|---|
| Node.js | setPdfToSignFromPath, setCertificateFromBase64 | setTransferFileFromPath, signature | setPkcs12FromPath, certPassword |
| Java | setPdfToSign, setCertificateBase64 | setTransferFilePath, setSignature | setPkcs12, setCertPassword |
| PHP | setPdfToSign, setCertificateBase64 | setTransferFile, setSignature | setPkcs12, setCertPassword |
| Python | set_pdf_to_sign_from_path, set_certificate_from_base64 | set_transfer_file_from_path, signature | set_pkcs12_from_path, cert_password |
| Ruby | pdf_to_sign_path=, certificate_base64= | transfer_file_id=, signature= | pkcs12_path=, cert_password= |
No Node.js, start(), complete() e sign() devolvem Promises; os setters ...Sync são versões síncronas dos que devolvem Promises.
Lotes
Para assinar muitos PDFs com o mesmo certificado, o sample de lote executa as mesmas etapas de início e conclusão uma vez por documento,
chamadas por AJAX. No navegador, o preauthorizeSignatures() do Web PKI pede a autorização do usuário uma só vez, e depois o signHash()
assina cada hash.
Receitas médicas
O sample de receita gera um PDF com os metadados de prescrição da ICP-Brasil (OIDs como 2.16.76.1.12.1.1 e o CRM) e depois o assina com o
certificado do usuário. O nome do campo de assinatura precisa terminar com a palavra-chave Emitente.
signatureStarter.customSignatureFieldName = "Signature1 Emitente";
signatureStarter.signaturePolicy = StandardSignaturePolicies.PADES_BASIC;
TODO(docs): qual documento da ICP-Brasil define os metadados de prescrição e a palavra-chave Emitente.
Erros
Um comando que falha termina com um código de retorno do PKI Express: por exemplo 14 (ValidationFailed), quando o certificado do
signatário não é confiável ou não é válido, ou 32 (TimestampOptionsRequired). No Node.js, a Promise é rejeitada com um CommandError. Veja
Códigos de retorno.