Pular para o conteúdo principal

Assinar um documento XML - PKI Express

O PKI Express assina documentos XML no seu servidor: o documento inteiro ou um elemento, em duas etapas, com o certificado do usuário e o Web PKI, ou em uma etapa, com uma chave que o servidor guarda. Os comandos start-xml, complete-sig e sign-xml existem desde o PKI Express 1.0.0.

O código é Node.js, dos samples do PKI Suite, com o pki-express (1.16.2), e PHP, com o lacuna/pki-express (1.17.0), para as assinaturas de elementos. Só as bibliotecas Node.js, Java e PHP assinam XML; as bibliotecas Python e Ruby não têm classes de XML.

Fluxo com o certificado do usuário​

O fluxo é o da assinatura de PDF, com XmlSignatureStarter:

  1. Navegador: o Web PKI lê o certificado do usuário; a página envia o conteúdo.
  2. Servidor: XmlSignatureStarter.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().
  4. Servidor: SignatureFinisher.complete(), com o mesmo XML, grava o XML assinado.

Assinar o documento inteiro​

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));

Depois, conclua como na assinatura de PDF, com o XML como arquivo a assinar e uma saída .xml:

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 */ });

Assinar com uma chave no servidor​

O XmlSigner assina em uma chamada, sem navegador; o sample usa um arquivo PKCS #12 de teste com a senha 1234, ou um gerado a partir de um certificado emitido pelo sample do Amplia.

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);

As outras origens de chave (thumbprint de um certificado, nome de uma chave, certificado em nuvem) são as mesmas dos PDFs: veja Assinar com uma chave no servidor.

Assinar um elemento​

Defina toSignElementId com o valor do atributo Id do elemento a assinar; o PKI Express o recebe como --element-id.

Nota fiscal eletrônica (NF-e)​

O sample de NF-e assina o elemento infNFe com a política do padrão nacional da NF-e:

$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();

Depois, o complete() funciona como acima, com o SignatureFinisher e o mesmo XML.

Certificado de origem (COD)​

O sample de COD faz duas assinaturas de elemento encadeadas em um envelope COD: primeiro o elemento COD, depois o CODEH no arquivo que a primeira assinatura gerou, cada uma com a política COD e em duas etapas.

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

Onde fica a assinatura​

O Node.js (setSignatureElementInsertion) e o Java (setSignatureElementInsertion com XmlElementInsertion) escolhem onde o elemento da assinatura é inserido: append-child, prepend-child, append-sibling ou prepend-sibling. Isso exige o PKI Express 1.26.0 ou posterior. TODO(docs): onde o PKI Express insere a assinatura por padrão.

Políticas de assinatura​

Constante (Node.js)ValorObservações
XADES_BESxades
XML_DSIG_BASICbasicXMLDSig; o PKI Express 1.32.0 acrescentou uma variante com SHA-1 só para assinaturas legadas
NFE_PADRAO_NACIONALnfeNF-e
PKI_BRAZIL_XML_ADR_BASICAadrbICP-Brasil
PKI_BRAZIL_XML_ADR_TEMPOadrtICP-Brasil; exige uma autoridade de carimbo de tempo
PKI_BRAZIL_XML_ADR_COMPLETAadrcICP-Brasil; exige uma autoridade de carimbo de tempo
PKI_BRAZIL_XML_ADR_ARQUIVAMENTOadraICP-Brasil, PKI Express 1.25.0 ou posterior; exige uma autoridade de carimbo de tempo
COD_WITH_SHA1, COD_WITH_SHA256cod-sha1, cod-sha256COD, PKI Express 1.6 ou posterior
COD_WITH_SHA256_EC14Ncod-sha256-ec14nCOD com a transformação extra xml-exc-c14n#, PKI Express 1.36.0
  • O Java dá às constantes nomes em camel case (XadesBes, NFePadraoNacional, CodWithSHA1) e acrescenta XadesBrazil (xades-brazil) e PkiBrazilXadesWithCertProtection.
  • TODO(docs): o que as políticas de geração xades-brazil e pki-brazil-with-cert-protection produzem.
  • TODO(docs): quais bibliotecas cliente definem a opção classic-enveloped-transform que o PKI Express 1.27.0 acrescentou; só o Java a tem (setUseClassicEnvelopedTransform).

Em cada linguagem​

BibliotecaXML (início)ElementoChave no servidor
Node.jsXmlSignatureStarter.setXmlToSignFromPathtoSignElementIdXmlSigner
JavaXmlSignatureStarter.setXmlToSignsetToSignElementIdXmlSigner
PHPXmlSignatureStarter.setXmlToSigntoSignElementId ou setToSignElementIdXmlSigner

Erros​

Um comando que falha termina com um código de retorno do PKI Express, como 14 (ValidationFailed) ou 32 (TimestampOptionsRequired). Veja Códigos de retorno.

Veja também​

Ver em Markdown