UNPKG

@supernova-team/xml-sunat

Version:

Librería para poder firmar XML's según la SUNAT

124 lines (123 loc) 6.22 kB
"use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.XmlSignature = void 0; const xmldom_1 = require("@xmldom/xmldom"); const promises_1 = __importDefault(require("fs/promises")); const fs_1 = require("fs"); const node_forge_1 = __importDefault(require("node-forge")); const xml_crypto_1 = require("xml-crypto"); class XmlSignature { /** * * @param {string} pfxFilePath - The .pfx file path * @param {string} password - The password of the .pfx file * @param {string} xmlStringStructure - The XML string structure to be signed */ constructor(pfxFilePath, password, xmlStringStructure) { //Signature configuration this.canonicalizationAlgorithm = 'http://www.w3.org/TR/2001/REC-xml-c14n-20010315'; this.signatureAlgorithm = 'http://www.w3.org/2000/09/xmldsig#rsa-sha1'; this.signXpath = "//*[local-name()='Invoice']"; this.transforms = ['http://www.w3.org/2000/09/xmldsig#enveloped-signature', 'http://www.w3.org/TR/2001/REC-xml-c14n-20010315']; this.digestAlgorithm = 'http://www.w3.org/2001/04/xmlenc#sha256'; this.pfxFilePath = pfxFilePath; this.password = password; this.xmlStringStructure = xmlStringStructure; } verifyXMLStructure() { const domXML = new xmldom_1.DOMParser().parseFromString(this.xmlStringStructure, "text/xml"); const signatureNode = domXML.getElementsByTagName("ext:ExtensionContent"); if (signatureNode.length === 0) { throw new Error('Error, the xml structure does not contain the signature node called "ext:ExtensionContent"'); } } /** * @throws {Error} Error if the XML structure is not valid * @throws {Error} Error if the PFX file is not valid * @throws {Error} Error if the PFX file does not contain a private key * @returns {Promise<string>} The signed XML string */ getSignedXML() { return __awaiter(this, void 0, void 0, function* () { this.verifyXMLStructure(); const key = yield this.convertPFXtoPEM(); //Signature configuration const sig = new xml_crypto_1.SignedXml({ privateKey: key.privateKey, signatureAlgorithm: this.signatureAlgorithm, canonicalizationAlgorithm: this.canonicalizationAlgorithm, publicCert: key.cert, getKeyInfoContent: () => { return `<ds:X509Data><ds:X509Certificate>${key.cert.replace(/-----BEGIN CERTIFICATE-----/g, '').replace(/-----END CERTIFICATE-----/g, '').replace(/(\r\n|\n|\r)/gm, "").trim()}</ds:X509Certificate></ds:X509Data>`; } }); //Signature reference sig.addReference({ xpath: this.signXpath, transforms: this.transforms, digestAlgorithm: this.digestAlgorithm, isEmptyUri: true }); //Sign the XML sig.computeSignature(this.xmlStringStructure, { attrs: { Id: 'SignatureSP', }, location: { reference: "//*[local-name(.)='ExtensionContent']", action: "prepend" }, prefix: "ds", }); return sig.getSignedXml(); }); } convertPFXtoPEM() { return __awaiter(this, void 0, void 0, function* () { //Check empty file if (!this.pfxFilePath || !this.pfxFilePath.endsWith('.pfx')) { throw new Error('Error with the pfx file path, it should be a .pfx file'); } //Check if exists file yield new Promise((resolve, reject) => { if ((0, fs_1.existsSync)(this.pfxFilePath)) { resolve(); } else { reject(new Error(`Error, the file ${this.pfxFilePath} does not exist`)); } }); const pfxBuffer = yield promises_1.default.readFile(this.pfxFilePath); const pfxAsn1 = node_forge_1.default.asn1.fromDer(pfxBuffer.toString('binary')); const pfx = node_forge_1.default.pkcs12.pkcs12FromAsn1(pfxAsn1, this.password); const privateKeyBags = pfx.getBags({ bagType: node_forge_1.default.pki.oids.pkcs8ShroudedKeyBag })[node_forge_1.default.pki.oids.pkcs8ShroudedKeyBag]; // Check if private key bags are empty if (!privateKeyBags || privateKeyBags.length === 0) { throw new Error('Error, no private key bag found in the PFX file'); } const privateKeyBag = privateKeyBags[0]; // Check if private key is empty const certBags = pfx.getBags({ bagType: node_forge_1.default.pki.oids.certBag })[node_forge_1.default.pki.oids.certBag]; if (!certBags || certBags.length === 0) { throw new Error('Error, no certificate bag found in the PFX file'); } const certBag = certBags[0]; const privateKeyPem = node_forge_1.default.pki.privateKeyToPem(privateKeyBag.key); const certPem = node_forge_1.default.pki.certificateToPem(certBag.cert); return { privateKey: privateKeyPem, cert: certPem }; }); } } exports.XmlSignature = XmlSignature;