envio-comprobantes-sri
Version:
Envia comprobantes electronicos al SRI (Ecuador). Recibe un objeto JSON. Lo convierte a XML, lo firma, lo envía al servicio web del SRI y devuelve la respuesta.
199 lines (198 loc) • 11.4 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.FirmarComprobanteXML = void 0;
const dom_parser_1 = __importDefault(require("dom-parser"));
const node_forge_1 = __importDefault(require("node-forge"));
const moment_1 = __importDefault(require("moment"));
const Crear_hash_1 = __importDefault(require("./Utils/Crear-hash"));
const Crear_hash_2 = __importDefault(require("./Utils/Crear-hash"));
const CertificateRelatedError_1 = __importDefault(require("./Utils/CertificateRelatedError"));
const SeleccionarCertificadoDeFirma_1 = __importDefault(require("./Utils/SeleccionarCertificadoDeFirma"));
function sha1_base64(txt, encoding) {
var md = node_forge_1.default.md.sha1.create();
md.update(txt, encoding);
return Buffer.from(md.digest().toHex(), 'hex').toString('base64');
}
function p_generar_xades_bes(p12cert, comprobante, callback) {
var parser = new dom_parser_1.default();
var xmlDoc = parser.parseFromString(comprobante);
var claveAcceso = xmlDoc.getElementsByTagName("claveAcceso")[0].textContent;
const { modulus, certificateX509, certificateX509_der_hash, X509SerialNumber, exponent, issuerName } = generarFirma(p12cert, comprobante);
/** Especifico la codificación del xml (utf8) */
var sha1_comprobante = sha1_base64(comprobante.replace('<?xml version="1.0" encoding="UTF-8"?>\n', ''), "utf8");
var xmlns = 'xmlns:ds="http://www.w3.org/2000/09/xmldsig#" xmlns:etsi="http://uri.etsi.org/01903/v1.3.2#"';
//numeros involucrados en los hash:
var Certificate_number = p_obtener_aleatorio(); //1562780 en el ejemplo del SRI
var Signature_number = p_obtener_aleatorio(); //620397 en el ejemplo del SRI
var SignedProperties_number = p_obtener_aleatorio(); //24123 en el ejemplo del SRI
//numeros fuera de los hash:
var SignedInfo_number = p_obtener_aleatorio(); //814463 en el ejemplo del SRI
var SignedPropertiesID_number = p_obtener_aleatorio(); //157683 en el ejemplo del SRI
var Reference_ID_number = p_obtener_aleatorio(); //363558 en el ejemplo del SRI
var SignatureValue_number = p_obtener_aleatorio(); //398963 en el ejemplo del SRI
var Object_number = p_obtener_aleatorio(); //231987 en el ejemplo del SRI
var SignedProperties = '';
SignedProperties += '<etsi:SignedProperties Id="Signature' + Signature_number + '-SignedProperties' + SignedProperties_number + '">'; //SignedProperties
SignedProperties += '<etsi:SignedSignatureProperties>';
SignedProperties += '<etsi:SigningTime>';
SignedProperties += (0, moment_1.default)().format('YYYY-MM-DD\THH:mm:ssZ');
SignedProperties += '</etsi:SigningTime>';
SignedProperties += '<etsi:SigningCertificate>';
SignedProperties += '<etsi:Cert>';
SignedProperties += '<etsi:CertDigest>';
SignedProperties += '<ds:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1">';
SignedProperties += '</ds:DigestMethod>';
SignedProperties += '<ds:DigestValue>';
SignedProperties += certificateX509_der_hash;
SignedProperties += '</ds:DigestValue>';
SignedProperties += '</etsi:CertDigest>';
SignedProperties += '<etsi:IssuerSerial>';
SignedProperties += '<ds:X509IssuerName>';
SignedProperties += issuerName;
SignedProperties += '</ds:X509IssuerName>';
SignedProperties += '<ds:X509SerialNumber>';
SignedProperties += X509SerialNumber;
SignedProperties += '</ds:X509SerialNumber>';
SignedProperties += '</etsi:IssuerSerial>';
SignedProperties += '</etsi:Cert>';
SignedProperties += '</etsi:SigningCertificate>';
SignedProperties += '</etsi:SignedSignatureProperties>';
SignedProperties += '<etsi:SignedDataObjectProperties>';
SignedProperties += '<etsi:DataObjectFormat ObjectReference="#Reference-ID-' + Reference_ID_number + '">';
SignedProperties += '<etsi:Description>';
SignedProperties += 'contenido comprobante';
SignedProperties += '</etsi:Description>';
SignedProperties += '<etsi:MimeType>';
SignedProperties += 'text/xml';
SignedProperties += '</etsi:MimeType>';
SignedProperties += '</etsi:DataObjectFormat>';
SignedProperties += '</etsi:SignedDataObjectProperties>';
SignedProperties += '</etsi:SignedProperties>'; //fin SignedProperties
var SignedProperties_para_hash = SignedProperties.replace('<etsi:SignedProperties', '<etsi:SignedProperties ' + xmlns);
var sha1_SignedProperties = sha1_base64(SignedProperties_para_hash);
var KeyInfo = '<ds:KeyInfo Id="Certificate' + Certificate_number + '">';
KeyInfo += '\n<ds:X509Data>';
KeyInfo += '\n<ds:X509Certificate>\n';
KeyInfo += certificateX509;
KeyInfo += '\n</ds:X509Certificate>';
KeyInfo += '\n</ds:X509Data>';
KeyInfo += '\n<ds:KeyValue>';
KeyInfo += '\n<ds:RSAKeyValue>';
KeyInfo += '\n<ds:Modulus>\n';
//MODULO DEL CERTIFICADO X509
KeyInfo += modulus;
KeyInfo += '\n</ds:Modulus>';
KeyInfo += '\n<ds:Exponent>';
//KeyInfo += 'AQAB';
KeyInfo += exponent;
KeyInfo += '</ds:Exponent>';
KeyInfo += '\n</ds:RSAKeyValue>';
KeyInfo += '\n</ds:KeyValue>';
KeyInfo += '\n</ds:KeyInfo>';
var KeyInfo_para_hash = KeyInfo.replace('<ds:KeyInfo', '<ds:KeyInfo ' + xmlns);
var sha1_certificado = sha1_base64(KeyInfo_para_hash);
var SignedInfo = '<ds:SignedInfo Id="Signature-SignedInfo' + SignedInfo_number + '">';
SignedInfo += '\n<ds:CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315">';
SignedInfo += '</ds:CanonicalizationMethod>';
SignedInfo += '\n<ds:SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1">';
SignedInfo += '</ds:SignatureMethod>';
SignedInfo += '\n<ds:Reference Id="SignedPropertiesID' + SignedPropertiesID_number + '" Type="http://uri.etsi.org/01903#SignedProperties" URI="#Signature' + Signature_number + '-SignedProperties' + SignedProperties_number + '">';
SignedInfo += '\n<ds:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1">';
SignedInfo += '</ds:DigestMethod>';
SignedInfo += '\n<ds:DigestValue>';
//HASH O DIGEST DEL ELEMENTO <etsi:SignedProperties>';
SignedInfo += sha1_SignedProperties;
SignedInfo += '</ds:DigestValue>';
SignedInfo += '\n</ds:Reference>';
SignedInfo += '\n<ds:Reference URI="#Certificate' + Certificate_number + '">';
SignedInfo += '\n<ds:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1">';
SignedInfo += '</ds:DigestMethod>';
SignedInfo += '\n<ds:DigestValue>';
//HASH O DIGEST DEL CERTIFICADO X509
SignedInfo += sha1_certificado;
SignedInfo += '</ds:DigestValue>';
SignedInfo += '\n</ds:Reference>';
SignedInfo += '\n<ds:Reference Id="Reference-ID-' + Reference_ID_number + '" URI="#comprobante">';
SignedInfo += '\n<ds:Transforms>';
SignedInfo += '\n<ds:Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature">';
SignedInfo += '</ds:Transform>';
SignedInfo += '\n</ds:Transforms>';
SignedInfo += '\n<ds:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1">';
SignedInfo += '</ds:DigestMethod>';
SignedInfo += '\n<ds:DigestValue>';
//HASH O DIGEST DE TODO EL ARCHIVO XML IDENTIFICADO POR EL id="comprobante"
SignedInfo += sha1_comprobante;
SignedInfo += '</ds:DigestValue>';
SignedInfo += '\n</ds:Reference>';
SignedInfo += '\n</ds:SignedInfo>';
var SignedInfo_para_firma = SignedInfo.replace('<ds:SignedInfo', '<ds:SignedInfo ' + xmlns);
const firma_SignedInfo = (0, Crear_hash_1.default)(p12cert, SignedInfo_para_firma);
var xades_bes = '';
//INICIO DE LA FIRMA DIGITAL
xades_bes += '<ds:Signature ' + xmlns + ' Id="Signature' + Signature_number + '">';
xades_bes += '\n' + SignedInfo;
xades_bes += '\n<ds:SignatureValue Id="SignatureValue' + SignatureValue_number + '">\n';
//VALOR DE LA FIRMA (ENCRIPTADO CON LA LLAVE PRIVADA DEL CERTIFICADO DIGITAL)
xades_bes += firma_SignedInfo;
xades_bes += '\n</ds:SignatureValue>';
xades_bes += '\n' + KeyInfo;
xades_bes += '\n<ds:Object Id="Signature' + Signature_number + '-Object' + Object_number + '">';
xades_bes += '<etsi:QualifyingProperties Target="#Signature' + Signature_number + '">';
//ELEMENTO <etsi:SignedProperties>';
xades_bes += SignedProperties;
xades_bes += '</etsi:QualifyingProperties>';
xades_bes += '</ds:Object>';
xades_bes += '</ds:Signature>';
//FIN DE LA FIRMA DIGITAL
callback(comprobante.replace(/<\/\w+>$/, closing_tag => xades_bes + closing_tag), claveAcceso);
}
function generarFirma(p12, infoAFirmar) {
var certificateX509 = '';
var { cert, key } = (0, SeleccionarCertificadoDeFirma_1.default)(p12);
if (!cert || !key)
throw new CertificateRelatedError_1.default("cert_bags_missing");
var signature = (0, Crear_hash_2.default)(p12, infoAFirmar);
var certificateX509_pem = node_forge_1.default.pki.certificateToPem(cert);
certificateX509 = certificateX509_pem;
certificateX509 = certificateX509.substr(certificateX509.indexOf('\n'));
certificateX509 = certificateX509.substr(0, certificateX509.indexOf('\n-----END CERTIFICATE-----'));
certificateX509 = certificateX509.replace(/\r?\n|\r/g, '').replace(/([^\0]{76})/g, '$1\n');
//Pasar certificado a formato DER y sacar su hash:
var certificateX509_asn1 = node_forge_1.default.pki.certificateToAsn1(cert);
var certificateX509_der = node_forge_1.default.asn1.toDer(certificateX509_asn1).getBytes();
var certificateX509_der_hash = sha1_base64(certificateX509_der);
//Serial Number
var X509SerialNumber = BigInt("0x" + cert.serialNumber).toString(10);
var exponent = hexToBase64(key.e.data[0].toString(16));
var modulus = bigint2base64(key.n);
var modulus_pem = modulus;
var issuerName = cert.issuer.attributes.map(attr => `${attr.shortName || attr.type}=${attr.value}`).reverse().join(', ');
return {
signature, certificateX509, modulus, certificateX509_pem, modulus_pem,
certificateX509_der_hash, X509SerialNumber, exponent, issuerName
};
}
function bigint2base64(bigint) {
var base64 = Buffer.from(bigint.toString(16).match(/\w{2}/g)?.map(function (a) {
return String.fromCharCode(parseInt(a, 16));
}).join("")).toString("base64");
return base64.match(/.{1,76}/g)?.join("\n");
}
function p_obtener_aleatorio() {
return Math.floor(Math.random() * 999000) + 990;
}
function hexToBase64(str) {
var hex = ('00' + str).slice(0 - str.length - str.length % 2);
return Buffer.from(String.fromCharCode.apply(null, hex.replace(/\r|\n/g, "").replace(/([\da-fA-F]{2}) ?/g, "0x$1 ").replace(/ +$/, "").split(" "))).toString("base64");
}
function FirmarComprobanteXML(p12cert, comprobante_xml) {
return new Promise((resolve, reject) => {
p_generar_xades_bes(p12cert, comprobante_xml, function (comprobante_firmado, claveAcceso) {
resolve({ comprobante_firmado, claveAcceso });
});
});
}
exports.FirmarComprobanteXML = FirmarComprobanteXML;