Firma digital de XML con token criptográfico
En este tutorial, se verá la implementación de firma digital desde una aplicación escrita en NodeJS, haciendo uso de un dispositivo criptográfico con el certificado contenido en el token.
Para la firma y acceso al Token criptográfico se utilizará la librería pkcs11-xml ya que es muy fácil de utilizar lo que viene bien para este propósito. El estándar utilizado para la firma digital del XML es DSIG que además coincide con la firma utilizada en los sistemas de facturación electrónica.
Si no tiene instalado NodeJS, puede seguir los pasos descritos en el siguiente enlace: NodeJS
Una vez instalado, ejecutar las siguientes instrucciones para preparar el proyecto y editar el primer archivo del proyecto:
- mkdir firmar-xml
- cd firmar-xml
- npm init
- npm install pkcs11-xml
- nano app.js
Con los pasos anteriores en la consola de comandos, se abrirá un editor de texto donde se puede copiar el siguiente código:
- const Dsig = require('pkcs11-xml');
- var dsig = new Dsig('/usr/lib/x86_64-linux-gnu/pkcs11/opensc-pkcs11.so');
Con el código anterior, se importa la librería pkcs11-xml y se carga la interfaz de acceso al dispositivo criptográfico, en este caso opensc.
El estándar XMLDSIG permite firmar documentos XML de diferentes formas, sin embargo para este tutorial se utilizará específicamente la siguiente:
- Canonicalization.- http://www.w3.org/TR/2001/REC-xml-c14n-20010315
- Transformation Algorithm.- http://www.w3.org/2000/09/xmldsig#enveloped-signature
- Hashing Algorithm.- http://www.w3.org/2001/04/xmlenc#sha256
- Signature Algorithm.- http://www.w3.org/2001/04/xmldsig-more#rsa-sha256
Los dispositivos criptográficos, ofrecen la posibilibdad de almacenar más de un par de claves, por lo que es necesario listar su contenido antes de poder firmar un documento. Para esto escribimos lo siguiente a continuación del programa iniciado en el paso anterior.
- try {
- dsig.openSession('12345678');
- const claves = dsig.getPrivateKeys();
- for (let i = 0; i < claves.length; i++) {
- console.log(claves[i].label);
- }
- } catch(e) {
- console.error(e);
- } finally {
- dsig.closeSession();
- }
En el ejemplo anterior se inicia sessión en el token a través del pin de seguridad y en caso de éxito se ejecutará la siguiente línea listando las claves privadas almacenadas en el token. En caso de error se muestra el mensaje y finalmente se cierra la sesión.
A continuación se describe como firmar el tag "titulo" de un documento XML con la primera clave encontrada. El siguiente código debe ser agregado a continuación de la llave que finaliza el ciclo for.
- dsig.privateKey = claves[0].label;
- var xml = '<libro><titulo>Viaje al centro de la tierra</titulo><autor>Julio Berne</autor></libro>';
- console.log(dsig.computeSignature(xml, 'titulo'));
El resultado final después de la firma, es el XML de la variable xml, agregando dentro del tag libro la firma del documento como se muestra a continuación:
- <libro>
- <titulo id="titulo">Viaje al centro de la tierra</titulo>
- <autor>Julio Berne</autor>
- <Signature xmlns="http://www.w3.org/2000/09/xmldsig#">
- <SignedInfo>
- <CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"></CanonicalizationMethod>
- <SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"></SignatureMethod>
- <Reference URI="#titulo">
- <Transforms>
- <Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"></Transform>
- <Transform Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments"></Transform>
- </Transforms>
- <DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"></DigestMethod>
- <DigestValue>N1dKcogNSj4gE1lxjNKWpFxmTpKpZ1Uilj6wIt7yxGQ=</DigestValue>
- </Reference>
- </SignedInfo>
- <SignatureValue>mdLAY/sdrRDbO9K7vYywLN660KysgS2PvepuN6xrSPZ0g+0hoOY6ANqPwqn7AV9oeeFHHaNi7l4j6sfZQBqbVSUtffCXCMbgnHgwk3dPZyej558HCt5CIrPmzi02jKqM+GL2XAXsX1kP2goU6XbWcBTVIGZM9e+wP9n5wyYCqgiUDkESAWRO9jbo2P7MbvILNBnH862ivshw4IM9T/+xxgINPtN3OLMKwJ34kJvRVrBR+Yod3CfsTzUes0bT1+q4CejWcu5ogXD/17n2rWRuF8nsSO1jYOLPnxInJVSfW7RePlWeJkE6SjcV11vuCxvc7/d40Ja9YVk5Q9VYCQfMHg==</SignatureValue>
- <KeyInfo>
- <X509Data>
- <X509Certificate>MIID3DCCAsSgAwIBAgIJAMUWAJ4eqSiyMA0GCSqGSIb3DQEBCwUAMIGCMQswCQYD
- VQQGEwJCTzEPMA0GA1UECAwGTGEgUGF6MQ8wDQYDVQQHDAZMYSBQYXoxDjAMBgNV
- BAoMBUFEU0lCMQwwCgYDVQQLDANVSUQxEzARBgNVBAMMCkp1YW4gUGVyZXoxHjAc
- BgkqhkiG9w0BCQEWD2pwZXJlekBtYWlsLmNvbTAeFw0xOTA2MjcxOTAxNTJaFw0x
- OTA3MjcxOTAxNTJaMIGCMQswCQYDVQQGEwJCTzEPMA0GA1UECAwGTGEgUGF6MQ8w
- DQYDVQQHDAZMYSBQYXoxDjAMBgNVBAoMBUFEU0lCMQwwCgYDVQQLDANVSUQxEzAR
- BgNVBAMMCkp1YW4gUGVyZXoxHjAcBgkqhkiG9w0BCQEWD2pwZXJlekBtYWlsLmNv
- bTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAO6Fg+eCOnsHxH4ls9RQ
- 9CGD/q6On6e0XKEzXxdM3YBFnQGiVXQ+odeJEKzTx6QKUP+gYyXLRzZTiJO9DNBM
- 5J90LRpvo34g6qIFxAEjuK0TP44bpPw8KVspk+OGaG2i44mzdKL5Hrh1PV+P87Dg
- JppceU2JmW73M+WRWUMDPk8XSxfelNvGHR8vWq2vxqOMd6blu9rIYBDEC10r48sn
- tjF/bhnHmQn9volMM3Dj0hH58kyEwt2bSgt+rBvIFuFMyDmSshA1tWhC1ITyjFgp
- 6w+tu90FONV3Rf4cn3TNgygD5GE/+z34ZXybX6qnd/JbPwqMGZ7K+SqHz6rdx/64
- b/UCAwEAAaNTMFEwHQYDVR0OBBYEFGuAkfgego+nx3fxrFnZ2qbREC2jMB8GA1Ud
- IwQYMBaAFGuAkfgego+nx3fxrFnZ2qbREC2jMA8GA1UdEwEB/wQFMAMBAf8wDQYJ
- KoZIhvcNAQELBQADggEBAHKJNitx/h059KY9Asslqq09/fp27EusWG66eKBAeBR0
- qtxwSQ7yAXzpmSbzHFAP3j0iQLYeZ8HNhfUbsBoNoLPULwvfEkvpC24DCzfVGoJD
- LgtewQpWibYwh/279OYcaKRPMq5hJjG2OdSXlTBY1vEp24AvRcyBC5/h1wHfjyTA
- kO1cminOqIWC77ButB+rbTOObSL4SbuwP7Es1XCe0TXpklbSQIiWMpLjTh87JAS2
- Y3REEdw0e7fZtaQ5WvUeo3C2eksNfZSzF5Ch3WqKioD7KtiwCwxeKJpbTb+O4ZYU
- mxPwYiubMcA4fpPz0g+Q79+Bbnp9/6ZIYGmVJ6GFNw8=</X509Certificate>
- </X509Data>
- </KeyInfo>
- </Signature></libro>
Se puede observar que dentro del tag Signature que se agregó al documento, se encuentran las especifiaciones de configuraicón para la firma XML-DSIG para que la firma pueda ser validada con solo el documento. Así mismo se agrega el certificado del signatario para la correspondiente validación.
Para ejecutar el programa simplemente utilizar:
- node app.js
Para guardar el resultado en un archivo agregar en la cabecera:
- const fs = require('fs');
Y después del console.log o en lugar de:
- fs.writeFileSync('/tmp/xml.xml', dsig.computeSignature(xml, 'titulo'));
Si desea descargar un ejemplo de archivo firmado puede hacerlo a través del siguiente link: Archivo firmado de ejemplo
Si desea descargar el código de ejemplo del programa anterior, puede hacerlo a través del siguiente link: Código de ejemplo