-
Dominik Hebeler authoredDominik Hebeler authored
Crypto.js 6.43 KiB
const config = require("config");
const { pki, random, md } = require("node-forge");
const crypto = require("crypto");
const BlindSignature = require("blind-signatures");
var BigInteger = require("jsbn").BigInteger;
const NodeRSA = require("node-rsa");
const dayjs = require("dayjs");
const RedisClient = require("./RedisClient");
class Crypto {
#dayjs;
#dayjs_format;
constructor() {
this.#dayjs = require("dayjs");
this.#dayjs_format = config.get("crypto.private_key.date_format");
}
/**
* Retrieves the Private Key for the given date
* Private Keys are generated from seeds using the Year and the month
* of the given date
*
* @param {dayjs.Dayjs} date Date/Month for the private Key seed
* @returns {Promise<NodeRSA>}
*/
async get_private_key(date) {
let cache_key = "private_key:" + date.format(this.#dayjs_format);
// Check if the private key already exists in cache
let redis_client = RedisClient.CLIENT();
let cached_key = await redis_client.get(cache_key);
let private_key = new NodeRSA();
if (cached_key === null) {
let seed =
config.get("crypto.private_key.seed") + date.format(this.#dayjs_format);
let sha512 = md.sha512.create();
sha512.update(seed);
seed = sha512.digest().toHex();
// Feed the seed into a pseudorandom number generator
let prng_instance = random.createInstance();
prng_instance.seedFileSync = () => seed;
let keypair = pki.rsa.generateKeyPair({
bits: config.get("crypto.private_key.bit_length"),
prng: prng_instance,
workers: -1,
});
let private_key_pem = pki
.privateKeyToPem(keypair.privateKey)
.trim()
.replace(/\r\n/g, "\n");
private_key = private_key.importKey(
Buffer.from(private_key_pem, "utf8"),
"private"
);
// Store the key in cache
await redis_client
.pipeline()
.set(cache_key, private_key.exportKey("pkcs8"))
.expireat(cache_key, date.add(2, "month").unix())
.exec();
} else {
let private_key_data = await redis_client.get(cache_key);
private_key = private_key.importKey(private_key_data, "pkcs8");
}
return private_key;
}
/**
*
* @param {String} blinded_token
* @param {NodeRSA} private_key
* @returns {Promise<BigInteger>}
*/
async sign(blinded_token, private_key) {
let min_ms = 150;
let start = dayjs();
let blinded_signature = BlindSignature.sign({
blinded: new BigInteger(blinded_token),
key: private_key,
});
let missing_ms = Math.max(min_ms - dayjs().diff(start, "millisecond"), 0);
await new Promise((resolve) => setTimeout(resolve, missing_ms));
return blinded_signature;
}
/**
*
* @param {String} token
* @param {String} date
*/
async validateToken(token, signature, date) {
let min_ms = 150;
let start = dayjs();
let current_date = dayjs(date, this.#dayjs_format);
let private_key = await this.get_private_key(current_date);
let verification_result = BlindSignature.verify2({
unblinded: signature,
key: private_key,
message: token,
});
let missing_ms = Math.max(min_ms - dayjs().diff(start, "millisecond"), 0);
await new Promise((resolve) => setTimeout(resolve, missing_ms));
return verification_result;
}
async validateMetaGerPassCode(
generation_month,
expiration_month,
metager_pass_codes
) {
// Check if codes are expired
if (!this.#dayjs().isBefore(expiration_month, "month")) {
return Promise.reject("Redeem Codes are expired.");
}
let private_key = await this.private_key_get(
generation_month,
expiration_month
);
let uuid_regexExp =
/^[0-9a-fA-F]{8}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{12}$/gi;
for (let i = 0; i < metager_pass_codes.length; i++) {
// Check if code iss in correct format
let metager_pass_code = metager_pass_codes[i];
if (
!metager_pass_code.hasOwnProperty("code") ||
!metager_pass_code.code.match(uuid_regexExp)
) {
return Promise.reject(metager_pass_code.code + " is not a valid UUID");
}
// Check if signature is in correct format
if (
!metager_pass_code.hasOwnProperty("signature") ||
!metager_pass_code.signature.match(/^\d+$/gi)
) {
return Promise.reject(i + 1 + ". signature has not a valid format");
}
let verification_result = BlindSignature.verify2({
unblinded: metager_pass_code.signature,
key: private_key,
message: metager_pass_code.code,
});
if (!verification_result) {
console.log("Failed" + i);
return Promise.reject("One or more signatures could not be verified");
}
}
}
/**
* Creates an hmac hash for purchase data so we can check it later
*/
createIntegrityHash(
order_id,
expires_at,
amount,
unit_size,
price_per_unit,
public_key_n,
public_key_e
) {
let data_to_hash = JSON.stringify({
order_id: parseInt(order_id),
expires_at: new String(expires_at),
amount: parseInt(amount),
unit_size: parseInt(unit_size),
price_per_unit: parseFloat(price_per_unit),
public_key_n: new String(public_key_n),
public_key_e: new String(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,
expires_at,
amount,
unit_size,
price_per_unit,
public_key_n,
public_key_e
) {
let data_to_hash = JSON.stringify({
order_id: parseInt(order_id),
expires_at: new String(expires_at),
amount: parseInt(amount),
unit_size: parseInt(unit_size),
price_per_unit: parseFloat(price_per_unit),
public_key_n: new String(public_key_n),
public_key_e: new String(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;