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
- Browser: Web PKI lists the user's certificates and reads the one they pick (
readCertificate()); the page posts its content to the server. - Server:
PadesSignatureStarter.start()returns the hash to sign, its digest algorithm and a transfer file. - Browser: Web PKI signs the hash with
signHash()and posts the signature. - 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));
certContentis the user's certificate, Base64-encoded, as Web PKI'sreadCertificate()returns it.start()resolves totoSignHash,digestAlgorithm,digestAlgorithmOidandtransferFile, 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
SignatureFinisherthe 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. SignatureFinisheralso 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.
- a certificate installed on the server, by its thumbprint (
- Set
overwriteOriginalFileinstead ofoutputFileto sign the PDF in place. - TODO(docs): which certificate store PKI Express searches for
certThumbon Linux, and whether the JavasetUseMachineoption 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) | Value | Notes |
|---|---|---|
PADES_BASIC | pades | |
PADES_BASIC_WITH_LTV | pades-ltv | |
PADES_T | pades-t | needs a timestamp authority |
PKI_BRAZIL_PADES_ADR_BASICA | adrb | ICP-Brasil, PKI Express 1.12.0 or later |
PKI_BRAZIL_PADES_ADR_BASICA_WITH_LTV | adrb-ltv | ICP-Brasil, PKI Express 1.12.0 or later |
PKI_BRAZIL_PADES_ADR_TEMPO | adrt | ICP-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 addsPkiBrazilPadesAdrCompleta(adrc) andPkiBrazilPadesAdrArquivamento(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'sadra; 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 withsetVisualRepresentationFile(). Images it references can be added withaddFileReference(alias, path)and used asfref://alias. See Visual representation and the tags for the text. - Other
PadesSignatureStarterandPadesSigneroptions, each with the PKI Express version it needs:
| Option | Node.js property | PKI Express |
|---|---|---|
| Signer's reason | reason | 1.13.0 |
| Hide the default visual representation | suppressDefaultVisualRep | 1.13.1 |
| Name of the signature field | customSignatureFieldName | 1.15.0 |
| Certification level | certificationLevel | 1.16.0 |
| Signer name and location | Java only: setSignerName, setLocation | 1.27.0 |
| PDF metadata | PHP only: addMetadata(key, value) | 1.38.0 |
In each language
The classes have the same names in every client library; the setters differ:
| Library | PDF and certificate (start) | Transfer file and signature (complete) | PKCS #12 (server key) |
|---|---|---|---|
| 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= |
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.