ec-sri-invoice-signer
Version:
Ecuador SRI invoice signer.
165 lines (164 loc) • 7.71 kB
JavaScript
;
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.extractX509Data = exports.extractIssuerData = exports.extractPrivateKeyData = exports.extractPrivateKeyAndCertificateFromPkcs12 = exports.getHash = exports.sign = void 0;
const forge = __importStar(require("node-forge"));
const errors_1 = require("./errors");
const sign = (data, privateKey) => {
const md = forge.md.sha1.create();
md.update(data, 'utf8');
return forge.util.encode64(privateKey.sign(md));
};
exports.sign = sign;
const getHash = (data) => {
const md = forge.md.sha1.create();
md.update(data, 'utf8');
return forge.util.encode64(md.digest().bytes());
};
exports.getHash = getHash;
const getBancoCentralPkcs12PrivateKey = (pkcs8ShroudedKeyBags) => {
const privateKeyBag = pkcs8ShroudedKeyBags.find((bag) => {
var _a, _b;
const name = (_b = (_a = bag === null || bag === void 0 ? void 0 : bag.attributes) === null || _a === void 0 ? void 0 : _a.friendlyName) === null || _b === void 0 ? void 0 : _b[0];
return /signing|private|key/i.test(name) || !name;
});
if (!privateKeyBag) {
throw new errors_1.UnsuportedPkcs12Error("No private key bag found in BCE .p12");
}
const privateKey = privateKeyBag.key;
if (!privateKey) {
throw new errors_1.UnsuportedPkcs12Error("No valid key found in BCE .p12");
}
return privateKey;
};
const extractPrivateKeyAndCertificateFromPkcs12 = (pkcs12RawData, password = '') => {
var _a, _b, _c;
const pkcs12InBase64 = typeof pkcs12RawData === 'string'
? pkcs12RawData
: pkcs12RawData.toString('base64');
const pkcs12InDer = forge.util.decode64(pkcs12InBase64);
const p12Asn1 = forge.asn1.fromDer(pkcs12InDer);
const p12 = forge.pkcs12.pkcs12FromAsn1(p12Asn1, password);
const certBags = p12.getBags({ bagType: forge.pki.oids.certBag });
const certBag = (_a = certBags[forge.pki.oids.certBag]) === null || _a === void 0 ? void 0 : _a[0];
const pkcs8ShroudedKeyBags = p12.getBags({ bagType: forge.pki.oids.pkcs8ShroudedKeyBag });
if (!certBag) {
throw new errors_1.UnsuportedPkcs12Error("No certificate found in PKCS#12");
}
if (!pkcs8ShroudedKeyBags || Object.keys(pkcs8ShroudedKeyBags).length === 0) {
throw new errors_1.UnsuportedPkcs12Error("No private key found in PKCS#12");
}
const friendlyName = (_c = (_b = certBag === null || certBag === void 0 ? void 0 : certBag.attributes) === null || _b === void 0 ? void 0 : _b.friendlyName) === null || _c === void 0 ? void 0 : _c[0];
const certificate = certBag.cert;
if (!certificate) {
throw new errors_1.UnsuportedPkcs12Error("Couldn't find certificate");
}
let privateKey = null;
// Extract private key
const allKeyBags = [
...(pkcs8ShroudedKeyBags[forge.pki.oids.pkcs8ShroudedKeyBag] || []),
...(pkcs8ShroudedKeyBags[forge.pki.oids.keyBag] || []),
];
// If it's a BCE certificate, use the specific extraction
if (/banco central/i.test(friendlyName) || /eci|bce/i.test(friendlyName)) {
privateKey = getBancoCentralPkcs12PrivateKey(allKeyBags);
}
else {
const firstKeyBag = allKeyBags[0];
privateKey = (firstKeyBag === null || firstKeyBag === void 0 ? void 0 : firstKeyBag.key) ? firstKeyBag.key : null;
}
if (!privateKey) {
throw new errors_1.UnsuportedPkcs12Error("Couldn't find private key");
}
// Also extract the full certificate chain if available
const caBags = p12.getBags({ bagType: forge.pki.oids.certBag });
const certChain = (caBags[forge.pki.oids.certBag] || [])
.map(bag => bag.cert)
.filter(cert => cert !== undefined);
return {
privateKey,
certificate,
certChain // Full chain for validation
};
};
exports.extractPrivateKeyAndCertificateFromPkcs12 = extractPrivateKeyAndCertificateFromPkcs12;
const normalizeIssuerAttributeShortName = (shortName) => {
switch (shortName) {
case 'E':
// As required by the SRI validator code in this line (https://github.com/gdiazs/MITyCLib/blob/master/MITyCLibXADES/src/main/java/es/mityc/firmaJava/libreria/xades/ValidarFirmaXML.java#L2139).
// X500Principal needs EMAILADDRESS instead of E (https://docs.oracle.com/javase/7/docs/api/javax/security/auth/x500/X500Principal.html#X500Principal(java.lang.String)) and has been seen that some certificate issuers set the email address with 'E' shortName.
// The oid for email (1.2.840.113549.1.9.1) could also be used, but that's a bit cryptic and we know the SRI accepts EMAILADDRESS without issue so no need to be that generic.
return 'EMAILADDRESS';
default:
return shortName;
}
;
};
const extractIssuerData = (certificate) => {
// reverse to follow convention of country-name-is-last seen in the SRI example
const attributes = certificate.issuer.attributes
.filter((attr) => attr.shortName || attr.type)
.reverse();
return attributes.map((attr) => {
const name = attr.shortName ? normalizeIssuerAttributeShortName(attr.shortName) : attr.type;
const value = attr.value || '';
return `${name}=${value}`;
}).join(',');
};
exports.extractIssuerData = extractIssuerData;
const extractX509Data = (certificate) => {
const serialNumber = new forge.jsbn.BigInteger(Array.from(Buffer.from(certificate.serialNumber, 'hex'))).toString();
const issuerName = extractIssuerData(certificate);
const certificateAsAsn1 = forge.pki.certificateToAsn1(certificate);
const contentAsDer = forge.asn1.toDer(certificateAsAsn1);
const contentHash = forge.util.encode64(forge.sha1.create().update(contentAsDer.bytes()).digest().bytes());
const content = forge.util.encode64(contentAsDer.bytes());
return {
content,
contentHash,
issuerName,
serialNumber
};
};
exports.extractX509Data = extractX509Data;
const extractPrivateKeyData = (privateKey) => {
const modulus = forge.util.encode64(forge.util.hexToBytes(privateKey.n.toString(16)));
const exponent = forge.util.encode64(forge.util.hexToBytes(privateKey.e.toString(16)));
return {
modulus,
exponent
};
};
exports.extractPrivateKeyData = extractPrivateKeyData;