Pular para o conteúdo principal

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​

  1. 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.
  2. Servidor: PadesSignatureStarter.start() devolve o hash a assinar, o algoritmo de hash e um arquivo de transferência.
  3. Navegador: o Web PKI assina o hash com signHash() e envia a assinatura.
  4. 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 o readCertificate() do Web PKI o devolve.
  • start() resolve para toSignHash, digestAlgorithm, digestAlgorithmOid e transferFile, 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 SignatureFinisher o 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 SignatureFinisher també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.
  • Defina overwriteOriginalFile em vez de outputFile para assinar o próprio PDF.
  • TODO(docs): em qual repositório de certificados o PKI Express procura o certThumb no Linux, e se a opção setUseMachine do 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)ValorObservações
PADES_BASICpades
PADES_BASIC_WITH_LTVpades-ltv
PADES_Tpades-texige uma autoridade de carimbo de tempo
PKI_BRAZIL_PADES_ADR_BASICAadrbICP-Brasil, PKI Express 1.12.0 ou posterior
PKI_BRAZIL_PADES_ADR_BASICA_WITH_LTVadrb-ltvICP-Brasil, PKI Express 1.12.0 ou posterior
PKI_BRAZIL_PADES_ADR_TEMPOadrtICP-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 acrescenta PkiBrazilPadesAdrCompleta (adrc) e PkiBrazilPadesAdrArquivamento (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ão adra como 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 com setVisualRepresentationFile(). As imagens que ele usa podem ser acrescentadas com addFileReference(alias, path) e citadas como fref://alias. Veja Representação visual e as tags do texto.
  • Outras opções de PadesSignatureStarter e PadesSigner, com a versão do PKI Express que cada uma exige:
OpçãoPropriedade no Node.jsPKI Express
Motivo do signatárioreason1.13.0
Ocultar a representação visual padrãosuppressDefaultVisualRep1.13.1
Nome do campo de assinaturacustomSignatureFieldName1.15.0
Nível de certificaçãocertificationLevel1.16.0
Nome do signatário e localsó Java: setSignerName, setLocation1.27.0
Metadados do PDFsó 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:

BibliotecaPDF e certificado (início)Arquivo de transferência e assinatura (conclusão)PKCS #12 (chave no servidor)
Node.jssetPdfToSignFromPath, setCertificateFromBase64setTransferFileFromPath, signaturesetPkcs12FromPath, certPassword
JavasetPdfToSign, setCertificateBase64setTransferFilePath, setSignaturesetPkcs12, setCertPassword
PHPsetPdfToSign, setCertificateBase64setTransferFile, setSignaturesetPkcs12, setCertPassword
Pythonset_pdf_to_sign_from_path, set_certificate_from_base64set_transfer_file_from_path, signatureset_pkcs12_from_path, cert_password
Rubypdf_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.

Veja também​

Ver em Markdown