const config = require("config"); const { pki, random } = require("node-forge"); const crypto = require("crypto"); class Crypto { #dayjs; #dayjs_format; #redis; constructor() { this.#dayjs = require("dayjs"); this.#dayjs_format = config.get("crypto.private_key.date_format"); let Redis = require("ioredis"); this.#redis = new Redis({ host: config.get("redis.host"), }); } private_key_get_current() { let seed_date = this.#dayjs().format(this.#dayjs_format); return this.#private_key_get(seed_date); } private_key_get_last() { let seed_date = this.#dayjs() .subtract(1, "month") .format(this.#dayjs_format); return this.#private_key_get(seed_date); } async #private_key_get(seed_date) { let private_key = {}; let cache_key = "private_keys" + seed_date; // Check if the private key already exists in cache let cached_key = await this.#redis.get(cache_key); if (cached_key === null) { let seed = config.get("crypto.private_key.seed") + seed_date; // Feed the seed into a pseudorandom number generator let prng_instance = random.createInstance(); prng_instance.seedFileSync = () => seed; let keypair = pki.rsa.generateKeyPair({ bits: 4096, prng: prng_instance, workers: -1, }); private_key = { n: keypair.privateKey.n.toString(), e: keypair.privateKey.e.toString(), d: keypair.privateKey.d.toString(), p: keypair.privateKey.p.toString(), q: keypair.privateKey.q.toString(), dmp1: keypair.privateKey.dP.toString(), dmq1: keypair.privateKey.dQ.toString(), coeff: keypair.privateKey.qInv.toString(), }; // Store the key in cache await this.#redis.set(cache_key, JSON.stringify(private_key)); } else { private_key = JSON.parse(await this.#redis.get(cache_key)); } return private_key; } /** * Creates an hmac hash for purchase data so we can check it later */ createIntegrityHash( order_id, amount, unit_size, price_per_unit, public_key_n, public_key_e ) { let data_to_hash = JSON.stringify({ order_id: parseInt(order_id), amount: parseInt(amount), unit_size: parseInt(unit_size), price_per_unit: parseFloat(price_per_unit), public_key_n: public_key_n, public_key_e: public_key_e, }); let forge = require("node-forge"); let hmac = forge.hmac.create(); hmac.start("sha256", config.get("crypto.hmac_integrity_seed")); hmac.update(data_to_hash); return hmac.digest().toHex(); } /** * Validates an hmac hash for purchase data so we can check it later */ validateIntegrityHash( user_hash, order_id, amount, unit_size, price_per_unit, public_key_n, public_key_e ) { let data_to_hash = JSON.stringify({ order_id: parseInt(order_id), amount: parseInt(amount), unit_size: parseInt(unit_size), price_per_unit: parseFloat(price_per_unit), public_key_n: public_key_n, public_key_e: public_key_e, }); let forge = require("node-forge"); let hmac = forge.hmac.create(); hmac.start("sha256", config.get("crypto.hmac_integrity_seed")); hmac.update(data_to_hash); let server_hash = hmac.digest().toHex(); return crypto.timingSafeEqual( Buffer.from(server_hash, "utf8"), Buffer.from(user_hash, "utf8") ); } } module.exports = Crypto;