Pular para o conteúdo principal

Sign a PDF (PAdES) - PKI Express

PKI Express signs PDFs (PAdES) on your server in two ways:

  • With the user's certificate, in two steps: PKI Express computes the hash to sign, Web PKI signs it in the browser with the user's key, and PKI Express writes the signed PDF.
  • With a server key, in one step and no browser: PKI Express signs with a key that your server holds, such as a PKCS #12 (.pfx) file.

Both use the start-pades, complete-sig and sign-pades commands, present since PKI Express 1.0.0; signing with a PKCS #12 file needs 1.3.0 or later.

The code is Node.js, from the PKI Suite samples, with pki-express (1.16.2); the Java, PHP, Python and Ruby samples do the same. For a line-by-line walkthrough of the Java sample, see Signature sample explained.

Flow with the user's certificate​

  1. Browser: Web PKI lists the user's certificates and reads the one they pick (readCertificate()); the page posts its content to the server.
  2. Server: PadesSignatureStarter.start() returns the hash to sign, its digest algorithm and a transfer file.
  3. Browser: Web PKI signs the hash with signHash() and posts the signature.
  4. Server: SignatureFinisher.complete() with the same PDF, the transfer file and the signature writes the signed PDF.

1. Start the signature​

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 is the user's certificate, Base64-encoded, as Web PKI's readCertificate() returns it.
  • start() resolves to toSignHash, digestAlgorithm, digestAlgorithmOid and transferFile, the name of a file that PKI Express keeps in its transfer data folder until the signature is completed.
  • Util.setPkiDefaults() applies the samples' trust settings and timestamp authority; see Validate signatures and Timestamp a PDF.
  • If the PDF is already signed, the same flow adds a signature to it.

2. Sign the hash in the browser​

pki.signHash({
thumbprint: formElements.certThumbField.val(),
hash: formElements.toSignHashField.val(),
digestAlgorithm: formElements.digestAlgorithmField.val()
}).success(function (signature) {
formElements.signatureField.val(signature);
formElements.form.submit();
});

The thumbprint is the one of the certificate read in the first step; the page keeps it, with the transfer file, in hidden fields. For Web PKI itself, see Web PKI.

3. Complete the signature​

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));
  • Give SignatureFinisher the same PDF as in the first step.
  • With complete(true), the promise resolves to the signer's certificate; this needs PKI Express 1.8 or later. Without it, nothing is returned.
  • SignatureFinisher also completes CAdES and XML signatures.

Sign with a server key​

PadesSigner signs in one call, with no browser. The sample signs with a test PKCS #12 file, password 1234, or with one generated from a certificate issued by the Amplia sample.

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);
  • The PKCS #12 can also be given as bytes (setPkcs12FromRaw) or Base64 (setPkcs12FromBase64).
  • Instead of a PKCS #12 file, the key can be:
    • a certificate installed on the server, by its thumbprint (certThumb; PKI Express 1.3 or later);
    • a key by name (keyName) with its certificate file (setCertFileFromPath), PKI Express 1.14 or later, which added keys stored on Azure Key Vault (see Azure);
    • a cloud certificate, through a trust service session: see Cloud certificate services.
  • Set overwriteOriginalFile instead of outputFile to sign the PDF in place.
  • TODO(docs): which certificate store PKI Express searches for certThumb on Linux, and whether the Java setUseMachine option applies there.
  • TODO(docs): how to use a key on a PKCS #11 token or HSM; PKI Express 1.38.0 added pkcs11LibraryPath, which no page describes yet.

Signature policies​

The policy is a StandardSignaturePolicies value; it decides what goes into the signature. The samples use PADES_BASIC_WITH_LTV.

Constant (Node.js, Python)ValueNotes
PADES_BASICpades
PADES_BASIC_WITH_LTVpades-ltv
PADES_Tpades-tneeds a timestamp authority
PKI_BRAZIL_PADES_ADR_BASICAadrbICP-Brasil, PKI Express 1.12.0 or later
PKI_BRAZIL_PADES_ADR_BASICA_WITH_LTVadrb-ltvICP-Brasil, PKI Express 1.12.0 or later
PKI_BRAZIL_PADES_ADR_TEMPOadrtICP-Brasil, PKI Express 1.12.0 or later; needs a timestamp authority
  • With a policy that needs a timestamp, the Node.js library refuses to run if no timestamp authority is set; PKI Express itself returns code 32 (TimestampOptionsRequired). See Timestamp a PDF.
  • Java names the constants in camel case (PadesBasicWithLTV, PkiBrazilPadesAdrBasica, PkiBrazilPadesAdrTempo) and adds PkiBrazilPadesAdrCompleta (adrc) and PkiBrazilPadesAdrArquivamento (adra); PKI Express 1.34.0 added the ICP-Brasil AD-RC and AD-RA PAdES policies.
  • TODO(docs): the PHP library names the PAdES AD-RA policy pades-ad-ra (PADES_AD_RA), unlike Java's adra; which value PKI Express 1.34.0 expects is not documented.
  • The policy names differ from Rest PKI's: see the equivalence table.

Visual representation and other options​

  • The visual representation (the stamp drawn on the page) is a JSON model given with setVisualRepresentation(), or a file with setVisualRepresentationFile(). Images it references can be added with addFileReference(alias, path) and used as fref://alias. See Visual representation and the tags for the text.
  • Other PadesSignatureStarter and PadesSigner options, each with the PKI Express version it needs:
OptionNode.js propertyPKI Express
Signer's reasonreason1.13.0
Hide the default visual representationsuppressDefaultVisualRep1.13.1
Name of the signature fieldcustomSignatureFieldName1.15.0
Certification levelcertificationLevel1.16.0
Signer name and locationJava only: setSignerName, setLocation1.27.0
PDF metadataPHP only: addMetadata(key, value)1.38.0

In each language​

The classes have the same names in every client library; the setters differ:

LibraryPDF and certificate (start)Transfer file and signature (complete)PKCS #12 (server key)
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=

In Node.js, start(), complete() and sign() return promises; the ...Sync setters are synchronous versions of the promise-returning ones.

Batches​

To sign many PDFs with the same certificate, the batch sample runs the same start and complete steps once per document, called with AJAX. In the browser, Web PKI's preauthorizeSignatures() asks the user once, then signHash() signs each hash.

Medical prescriptions​

The prescription sample generates a PDF that carries the ICP-Brasil prescription metadata (OIDs such as 2.16.76.1.12.1.1 and the CRM), then signs it with the user's certificate. Its signature field name must end with the keyword Emitente.

signatureStarter.customSignatureFieldName = "Signature1 Emitente";
signatureStarter.signaturePolicy = StandardSignaturePolicies.PADES_BASIC;

TODO(docs): which ICP-Brasil document defines the prescription metadata and the Emitente keyword.

Errors​

A failed command ends with a PKI Express return code: for example 14 (ValidationFailed) when the signer's certificate is not trusted or not valid, or 32 (TimestampOptionsRequired). In Node.js, the promise is rejected with a CommandError. See Return codes.

See also​

View as Markdown