Pular para o conteúdo principal

PKI Express quick reference

The essentials of PKI Express on one page, with links to the full pages.

What it is for​

PKI Express creates CMS/CAdES, PDF/PAdES and XmlDSig/XAdES digital signatures from virtually any programming language, on your own Windows or Linux servers. You install it on the server and drive it through a client library for Java, Node.js, PHP, Python or Ruby; to sign with the user's certificate on a web page, it works together with Web PKI.

Choose insteadWhen it fits better
PKI SDKYour application is .NET: PKI SDK is a library for .NET applications, and the PKI guide recommends it for server signatures in .NET
Rest PKI CoreYou would rather call a REST API than install software on your servers: use it as a service at core.pki.rest or as your own instance; it also hosts the page where the user signs
Web PKIYou need the browser side: listing the user's certificates and signing with them on a web page. It complements PKI Express rather than replacing it
SignerYou want to send documents to people and collect their signatures with a ready-made workflow, as SaaS or on-premises, instead of building the flow

Install​

Two parts: PKI Express itself, on each server that runs your application, and the client package for your language.

LanguagePackageCurrent majorRegistrySource code
PKI Express, Windows Server 2008 R2+ / Windows 7+ (needs .NET Framework 4.6.1)pkie-1.38.0-x64.msi installer; x86 and zip packages on the setup page1 (1.38.0)cdn.lacunasoftware.com —
PKI Express, Linux (Ubuntu/Mint, RHEL, CentOS/Oracle Linux/Fedora)pkie-1.38.0.tar.gz (setup)1 (1.38.0)cdn.lacunasoftware.com —
Javacom.lacunasoftware.pkiexpress:pki-express1 (1.23.1)Maven Central PkiExpressJava
Node.jspki-express1 (1.16.2)npm PkiExpressNode
PHP 5.5+lacuna/pki-express1 (1.17.0)Packagist PkiExpressPhp
Pythonpkiexpress1 (1.10.4)PyPI PkiExpressPython
Rubypki_express1 (1.3.2)RubyGems PkiExpressRuby

The client libraries are open source, in the LacunaSoftware GitHub organization under the names above, with no hyphen. Each release has a tag named after the package version, so a file of the version you use is at, for example, https://raw.githubusercontent.com/LacunaSoftware/PkiExpressPhp/1.17.0/src/CommandException.php. The default branch is master for Java and develop for the others.

On Linux, extract the package to /usr/share/pkie, link /usr/share/pkie/pkie into /usr/local/bin, set the log folder and run sudo pkie activate LacunaPkiLicense.config; the Ubuntu, RHEL and CentOS pages give the commands. To update, run the new installer (Windows) or replace the contents of /usr/share/pkie with the new package (Linux).

Configuration and authentication​

License, no API key. PKI Express needs a license file, LacunaPkiLicense.config; ask Lacuna for a free trial license. Activate it once per machine: pkie activate on Linux (manual activation if the network activation fails), the PKI Express Configuration Manager (pkiemgr.exe, in the installation folder) on Windows. Your application does not read the license, and there is no service URL: the client library runs the PKI Express installed on the same machine.

Server settings are set with pkie config --set name=value (administrator privileges); pkie config --effective shows the values in use.

SettingKeysPage
Log folder and minimum levellogDir, logLevelLogging
Temporary folder (cache of CRLs and CA certificates)tempDirTemporary directory
ProxyproxyHost, proxyPort, proxyUsername, proxyPasswordProxy
LAPP, to fetch CRLs and CA certificates through *.lacunasoftware.com (support must also authorize it)lappEnabledLAPP
Keys in Azure Key VaultazureAppId, azureAppSecret, azureKeyVaultEndpointAzure
Cloud certificates (BirdID, NeoID, RemoteID, SafeID, VIDaaS, others)trustServices:<service>:clientId, trustServices:<service>:clientSecret, trustServices:<service>:endpointCloud certificates
Keys on PKCS#11 tokens and HSMspkcs11LibraryPathChangelog 1.38.0

TODO(docs): pkcs11LibraryPath (PKI Express 1.38.0) has no configuration page; only the changelog names it, and neither the client libraries nor any published help text describe it.

In your code, every operation class inherits PkiExpressOperator, whose settings the samples apply in one helper, Util.setPkiDefaults(), called by every block below:

  • what to trust: only ICP-Brasil certificates by default; add roots with addTrustedRootSync(), or the Lacuna test root with trustLacunaTestRoot (trusting custom certificates);
  • timestampAuthority, a TimestampAuthority for policies that need a timestamp: the samples use REST PKI's, https://pki.rest/tsp/a402df41-8559-47b2-a05c-be555bf66310, with a REST PKI access token (setOAuthTokenAuthentication()); user and password, and mutual SSL, also work (timestamp authority);
  • offline, to run without network access.

Each class also takes an optional PkiExpressConfig (PKI Express folder, temporary folder, transfer data folder): without it, the libraries look for PKI Express in the default Windows installation folders, and run pkie from the PATH on Linux. On Linux, only the Java library can run PKI Express 1.31.0 or later from a custom folder; see client library settings.

Core operations​

The blocks are trimmed from the Node.js samples (nodejs/expressmvc/routes/ in PkiSuiteSamples), the language with the most PKI Express samples. Policy names come from StandardSignaturePolicies and differ from Rest PKI's: see the equivalence table.

Each operation has its own page: Sign a PDF (PAdES), with the server-key variant; Sign with CAdES; Sign an XML document; Validate signatures; Timestamp a PDF; Printer-friendly version; Certificate authentication; Generate a key and a PKCS #12 file.

Sign a PDF with the user's certificate​

PAdES with the key on the user's computer, in two server steps: PadesSignatureStarter.start() returns the hash that Web PKI signs in the browser (signHash()), and SignatureFinisher.complete() writes the signed PDF. Step by step in Java: how it works; signature stamp: visual representation. Details: Sign a PDF (PAdES).

// POST /start: certContent is the user's certificate, read by Web PKI
const signatureStarter = new PadesSignatureStarter();
Util.setPkiDefaults(signatureStarter);
signatureStarter.signaturePolicy = StandardSignaturePolicies.PADES_BASIC_WITH_LTV;
signatureStarter.setPdfToSignFromPathSync(StorageMock.getDataPath(fileId));
signatureStarter.setCertificateFromBase64Sync(certContent);
signatureStarter.start().then((response) => { /* toSignHash, digestAlgorithm, transferFile */ });

// POST /complete: signature is the result of Web PKI's signHash()
const signatureFinisher = new SignatureFinisher();
Util.setPkiDefaults(signatureFinisher);
signatureFinisher.setFileToSignFromPathSync(StorageMock.getDataPath(fileId));
signatureFinisher.setTransferFileFromPathSync(transferFile);
signatureFinisher.signature = signature;
signatureFinisher.outputFile = path.join(APP_ROOT, 'app-data', outputFile);
signatureFinisher.complete(true).then((result) => { /* signed PDF at outputFile */ });

Sign a PDF with a server key​

One step, no browser: PadesSigner.sign() signs with a PKCS #12 file on the server. Details: Sign with a server key.

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';
signer.outputFile = path.join(APP_ROOT, 'app-data', outputFile);
let signerCert = await signer.sign(true);

Sign a file with CAdES​

CadesSignatureStarter follows the same start/complete flow, finished by SignatureFinisher (output .p7s); if the file is already a CMS, PKI Express co-signs it. Details: Sign with CAdES.

const signatureStarter = new CadesSignatureStarter();
Util.setPkiDefaults(signatureStarter);
signatureStarter.signaturePolicy = StandardSignaturePolicies.PKI_BRAZIL_CADES_ADR_BASICA;
signatureStarter.setFileToSignFromPathSync(StorageMock.getDataPath(fileId));
signatureStarter.setCertificateFromBase64Sync(certContent);
signatureStarter.start().then((response) => { /* same as the PDF flow */ });

Sign an XML document​

XmlSigner.sign() signs with a server key; with the user's certificate, use XmlSignatureStarter and SignatureFinisher as in the PDF flow. Details: Sign an XML document.

const signer = new XmlSigner();
Util.setPkiDefaults(signer);
signer.signaturePolicy = StandardSignaturePolicies.XADES_BES;
await signer.setXmlToSignFromPath(StorageMock.getSampleDocPath(SampleDocs.SAMPLE_XML));
await signer.setPkcs12FromPath(StorageMock.getSamplePkcs12Path());
signer.certPassword = '1234';
signer.outputFile = path.join(APP_ROOT, 'app-data', outputFile);
let signerCert = await signer.sign(true);

Validate a signed PDF​

PadesSignatureExplorer.open() returns the file's signatures and, with validate = true, validates them; CadesSignatureExplorer does the same for CMS/CAdES. Details: Validate signatures.

const sigExplorer = new PadesSignatureExplorer();
Util.setPkiDefaults(sigExplorer);
sigExplorer.validate = true;
sigExplorer.setSignatureFileFromPathSync(filePath);
sigExplorer.signaturePolicy = StandardSignaturePolicies.PADES_BASIC;
sigExplorer.open().then((r) => res.render('open-pades-express', { signature: r }));

Timestamp a PDF​

PadesTimestamper.stamp() adds a timestamp to a PDF, using the operator's timestamp authority. Details: Timestamp a PDF.

const stamper = new PadesTimestamper();
Util.setPkiDefaults(stamper);
stamper.setPdfFromPath(pdfPath).then(() => {
stamper.setOutputFilePath(outputFilePath);
stamper.stamp().then(() => { /* timestamped PDF at outputFilePath */ });
});

Make a printer-friendly version​

After opening the signatures with PadesSignatureExplorer, PdfMarker.apply() stamps texts and images built with PdfHelper on the PDF, such as the signers' names and a verification code. Details: Printer-friendly version.

const pdfMarker = new PdfMarker();
Util.setPkiDefaults(pdfMarker);
pdfMarker.setFileFromPathSync(pdfPath);
const pdf = new PdfHelper();
pdfMarker.marks.push(pdf.mark().onAllPages()
.onContainer(pdf.container().width(1).anchorRight(1).height(1).anchorBottom(1))
.addElement(pdf.imageElement().withOpacity(75).withImageContent(Util.getIcpBrasilLogoContent(), 'image/png')));
pdfMarker.outputFile = path.join(APP_ROOT, 'app-data', outputFile);
pdfMarker.apply();

Generate a key and a PKCS #12 file​

KeyGenerator.generate() makes an RSA key (1024, 2048 or 4096 bits; formats json, blob, xml) and, with genCsr, a PEM CSR for your CA; Pkcs12Generator.generate() then combines the key with the issued certificate into a PKCS #12 file to sign with. Ruby has neither class. Details, PHP code and each language's names: Generate a key and a PKCS #12 file.

const keyGenerator = new KeyGenerator({ keySize: 2048, genCsr: true });
const genKey = await keyGenerator.generate(); // genKey.key, genKey.csr
// ...the CA issues the certificate from genKey.csr...
const pkcs12Generator = new Pkcs12Generator({ key: genKey.key });
pkcs12Generator.setCertFileFromPathSync(certPath);
const result = await pkcs12Generator.generate(password); // result.pfx.contentRaw

Authenticate with a certificate​

Authentication.start() returns a nonce and digest algorithm for Web PKI to sign; complete() checks the signature and validates the certificate. Details: Certificate authentication.

const auth = new Authentication();
Util.setPkiDefaults(auth);
auth.setNonce(nonce); // from auth.start(), signed in the browser by Web PKI
await auth.setCertificateFromBase64(certContent);
auth.setSignature(signature);
const result = await auth.complete();
if (result.validationResults.isValid()) { const userCert = result.certificate; }

Errors​

Each PKI Express operation ends with a return code, 00 - Ok or an error from 01 to 43; signatures, authentications and inspections with validation also report validation errors such as CertificateChainRootNotTrusted. Full list: return codes. In Node.js, a failed operation rejects its promise with a CommandError whose code is the return code (ValidationError for code 14, InstallationNotFoundError when PKI Express is not found); the samples pass it to next(err). PHP throws CommandException ($e->returnCode), Ruby PkiExpress::CommandError (e.code.exitstatus); Java and Python throw a plain exception with the output in the message: see return codes in the client libraries.

ErrorMeaningWhat to do
17 - NotActivatedPKI Express is not activated on this machineActivate it with the license (setup)
14 - ValidationFailed with CertificateChainRootNotTrustedThe certificate's root is not trusted; by default, only ICP-Brasil isTrust the root: trustLacunaTestRoot or addTrustedRootSync() (trusting custom certificates)
CertificateRevocationStatusUnknownPKI Express could not access the certificate's CRLsConfigure the proxy or LAPP
32 - TimestampOptionsRequiredNo timestamp authority given, and the policy requires a timestampSet the operator's timestampAuthority (timestamp authority)

Test certificates and credentials​

Try it​

Run the demos, or start from a sample in your language:

View as Markdown