UNPKG

nodefact

Version:

Biblioteca para facturación electrónica en Perú con Node.js. Compatible con UBL 2.1 y servicios web de SUNAT.

175 lines (174 loc) 6.85 kB
"use strict"; /** * Módulo Signature - Firma digital de documentos XML * * Este módulo proporciona funcionalidades para la firma digital de documentos XML * según los requerimientos de SUNAT. */ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.XmlSigner = exports.SignatureError = void 0; exports.signXml = signXml; const fs = __importStar(require("fs")); const xml_crypto_1 = require("xml-crypto"); const xmldom_1 = require("xmldom"); const core_1 = require("../core"); /** * Clase para errores de firma digital */ class SignatureError extends core_1.NodeFactError { constructor(message) { super(message); this.name = 'SignatureError'; } } exports.SignatureError = SignatureError; /** * Clase para la firma digital de documentos XML */ class XmlSigner { /** * Firma un documento XML * @param xml Documento XML a firmar * @param options Opciones de firma * @returns Resultado de la firma */ async sign(xml, options) { try { let privateKey; let certificate; // Validar opciones if (options.pemFile) { // Si se proporciona un archivo PEM único const pemContent = await this.readFile(options.pemFile); // Extraer la clave privada y el certificado del archivo PEM const privateKeyMatch = pemContent.match(/-----BEGIN PRIVATE KEY-----[\s\S]*?-----END PRIVATE KEY-----/); const certificateMatch = pemContent.match(/-----BEGIN CERTIFICATE-----[\s\S]*?-----END CERTIFICATE-----/); if (!privateKeyMatch || !certificateMatch) { throw new SignatureError('El archivo PEM no contiene una clave privada y un certificado válidos'); } privateKey = privateKeyMatch[0]; certificate = certificateMatch[0]; } else if (options.keyFile && options.certFile) { // Si se proporcionan archivos separados privateKey = await this.readFile(options.keyFile); certificate = await this.readFile(options.certFile); } else { throw new SignatureError('Se requiere un archivo PEM único o la ruta de los archivos de clave privada y certificado'); } // Parsear XML const doc = new xmldom_1.DOMParser().parseFromString(xml, 'text/xml'); // Obtener el nodo de extensión para la firma const extensionContent = doc.getElementsByTagNameNS('urn:oasis:names:specification:ubl:schema:xsd:CommonExtensionComponents-2', 'ExtensionContent')[0]; if (!extensionContent) { throw new SignatureError('No se encontró el nodo ExtensionContent en el XML'); } // Crear firma const sig = new xml_crypto_1.SignedXml({ privateKey: privateKey, keyInfoProvider: { getKeyInfo: () => { return `<X509Data><X509Certificate>${certificate}</X509Certificate></X509Data>`; }, getKey: () => { return privateKey; } } }); // Configurar transformaciones const transforms = options.transforms || [ 'http://www.w3.org/2000/09/xmldsig#enveloped-signature', 'http://www.w3.org/TR/2001/REC-xml-c14n-20010315' ]; // Configurar referencia sig.addReference({ xpath: options.uri || '', transforms: transforms, digestAlgorithm: options.algorithm || 'http://www.w3.org/2000/09/xmldsig#sha1' }); // Firmar el documento sig.computeSignature(xml, { location: { reference: options.uri || '', action: 'append' } }); // Obtener la firma const signature = sig.getSignatureXml(); // Agregar la firma al nodo de extensión extensionContent.appendChild(new xmldom_1.DOMParser().parseFromString(signature, 'text/xml').documentElement); // Serializar el documento firmado const signedXml = new xmldom_1.XMLSerializer().serializeToString(doc); return { success: true, signedXml }; } catch (err) { const error = err; return { success: false, error: `Error al firmar XML: ${error.message}` }; } } /** * Lee un archivo de forma asíncrona * @param filePath Ruta del archivo * @returns Contenido del archivo */ async readFile(filePath) { return new Promise((resolve, reject) => { fs.readFile(filePath, 'utf8', (err, data) => { if (err) { reject(new SignatureError(`Error al leer el archivo ${filePath}: ${err.message}`)); } else { resolve(data); } }); }); } } exports.XmlSigner = XmlSigner; /** * Firma un documento XML * @param xml Documento XML a firmar * @param options Opciones de firma * @returns Resultado de la firma */ async function signXml(xml, options) { const signer = new XmlSigner(); return await signer.sign(xml, options); }