@supernova-team/xml-sunat
Version:
Librería para poder firmar XML's según la SUNAT
124 lines (123 loc) • 6.22 kB
JavaScript
;
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;