Pular para o conteúdo principal

Sign an XML document - PKI Express

PKI Express signs XML documents on your server: the whole document or one element, in two steps with the user's certificate and Web PKI, or in one step with a key held by the server. The start-xml, complete-sig and sign-xml commands exist since PKI Express 1.0.0.

The code is Node.js, from the PKI Suite samples, with pki-express (1.16.2), and PHP with lacuna/pki-express (1.17.0) for the element signatures. Only the Node.js, Java and PHP libraries sign XML; the Python and Ruby libraries have no XML classes.

Flow with the user's certificate​

The flow is the one of the PDF signature, with XmlSignatureStarter:

  1. Browser: Web PKI reads the user's certificate; the page posts its content.
  2. Server: XmlSignatureStarter.start() returns the hash to sign, its digest algorithm and a transfer file.
  3. Browser: Web PKI signs the hash with signHash().
  4. Server: SignatureFinisher.complete() with the same XML writes the signed XML.

Sign the whole document​

const signatureStarter = new XmlSignatureStarter();
Util.setPkiDefaults(signatureStarter);
signatureStarter.signaturePolicy = StandardSignaturePolicies.XADES_BES;
signatureStarter.setXmlToSignFromPathSync(StorageMock.getSampleDocPath(SampleDocs.SAMPLE_XML));
signatureStarter.setCertificateFromBase64Sync(certContent);
signatureStarter.start()
.then((response) => {
const { toSignHash } = response;
const { digestAlgorithm } = response;
const { transferFile } = response;
// render the page that signs the hash with Web PKI's signHash()
}).catch((err) => next(err));

Then complete it as a PDF signature, with the XML as the file to sign and an .xml output:

const signatureFinisher = new SignatureFinisher();
Util.setPkiDefaults(signatureFinisher);
signatureFinisher.setFileToSignFromPathSync(StorageMock.getSampleDocPath(SampleDocs.SAMPLE_XML));
signatureFinisher.setTransferFileFromPathSync(transferFile);
signatureFinisher.signature = signature;
const outputFile = `${uuidv4()}.xml`;
signatureFinisher.outputFile = path.join(APP_ROOT, 'app-data', outputFile);
signatureFinisher.complete(true).then((certificate) => { /* signed XML at outputFile */ });

Sign with a server key​

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

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';
const outputFile = `${uuidv4()}.xml`;
signer.outputFile = path.join(APP_ROOT, 'app-data', outputFile);
let signerCert = await signer.sign(true);

The other key sources (a certificate thumbprint, a key name, a cloud certificate) are the same as for PDFs: see Sign with a server key.

Sign one element​

Set toSignElementId to the value of the Id attribute of the element to sign; PKI Express receives it as --element-id.

Brazilian electronic invoice (NF-e)​

The NF-e sample signs the infNFe element with the NF-e national standard policy:

$signatureStarter = new XmlSignatureStarter();
Util::setPkiDefaults($signatureStarter);
$signatureStarter->signaturePolicy = StandardSignaturePolicies::NFE_PADRAO_NACIONAL;
$signatureStarter->toSignElementId = 'NFe35141214314050000662550010001084271182362300';
$signatureStarter->setXmlToSign(StorageMock::getSampleDocPath(SampleDocs::SAMPLE_NFE));
$signatureStarter->setCertificateBase64($certContent);
$response = $signatureStarter->start();

complete() then works as above, with SignatureFinisher and the same XML.

Certificate of origin (COD)​

The COD sample makes two chained element signatures on a COD envelope: first the COD element, then CODEH on the file the first signature produced, each with the COD policy and in two steps.

$signatureStarter->signaturePolicy = StandardSignaturePolicies::COD_WITH_SHA1;
$signatureStarter->toSignElementId = 'COD'; // then 'CODEH' on the signed file

Where the signature goes​

Node.js (setSignatureElementInsertion) and Java (setSignatureElementInsertion with XmlElementInsertion) choose where the signature element is inserted: append-child, prepend-child, append-sibling or prepend-sibling. This needs PKI Express 1.26.0 or later. TODO(docs): where PKI Express inserts the signature by default.

Signature policies​

Constant (Node.js)ValueNotes
XADES_BESxades
XML_DSIG_BASICbasicXMLDSig; PKI Express 1.32.0 added a SHA-1 variant for legacy signatures only
NFE_PADRAO_NACIONALnfeNF-e
PKI_BRAZIL_XML_ADR_BASICAadrbICP-Brasil
PKI_BRAZIL_XML_ADR_TEMPOadrtICP-Brasil; needs a timestamp authority
PKI_BRAZIL_XML_ADR_COMPLETAadrcICP-Brasil; needs a timestamp authority
PKI_BRAZIL_XML_ADR_ARQUIVAMENTOadraICP-Brasil, PKI Express 1.25.0 or later; needs a timestamp authority
COD_WITH_SHA1, COD_WITH_SHA256cod-sha1, cod-sha256COD, PKI Express 1.6 or later
COD_WITH_SHA256_EC14Ncod-sha256-ec14nCOD with the extra xml-exc-c14n# transform, PKI Express 1.36.0
  • Java names the constants in camel case (XadesBes, NFePadraoNacional, CodWithSHA1) and adds XadesBrazil (xades-brazil) and PkiBrazilXadesWithCertProtection.
  • TODO(docs): what the xades-brazil and pki-brazil-with-cert-protection generation policies produce.
  • TODO(docs): which client libraries set the classic-enveloped-transform option that PKI Express 1.27.0 added; only Java has it (setUseClassicEnvelopedTransform).

In each language​

LibraryXML (start)ElementServer key
Node.jsXmlSignatureStarter.setXmlToSignFromPathtoSignElementIdXmlSigner
JavaXmlSignatureStarter.setXmlToSignsetToSignElementIdXmlSigner
PHPXmlSignatureStarter.setXmlToSigntoSignElementId or setToSignElementIdXmlSigner

Errors​

A failed command ends with a PKI Express return code, such as 14 (ValidationFailed) or 32 (TimestampOptionsRequired). See Return codes.

See also​

View as Markdown