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