| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162 | "use strict";Object.defineProperty(exports, "__esModule", { value: true });exports.eskdf = exports.deriveMainSeed = exports.pbkdf2 = exports.scrypt = void 0;const _assert_js_1 = require("./_assert.js");const hkdf_js_1 = require("./hkdf.js");const sha256_js_1 = require("./sha256.js");const pbkdf2_js_1 = require("./pbkdf2.js");const scrypt_js_1 = require("./scrypt.js");const utils_js_1 = require("./utils.js");// A tiny KDF for various applications like AES key-gen.// Uses HKDF in a non-standard way, so it's not "KDF-secure", only "PRF-secure".// Which is good enough: assume sha2-256 retained preimage resistance.const SCRYPT_FACTOR = 2 ** 19;const PBKDF2_FACTOR = 2 ** 17;// Scrypt KDFfunction scrypt(password, salt) {    return (0, scrypt_js_1.scrypt)(password, salt, { N: SCRYPT_FACTOR, r: 8, p: 1, dkLen: 32 });}exports.scrypt = scrypt;// PBKDF2-HMAC-SHA256function pbkdf2(password, salt) {    return (0, pbkdf2_js_1.pbkdf2)(sha256_js_1.sha256, password, salt, { c: PBKDF2_FACTOR, dkLen: 32 });}exports.pbkdf2 = pbkdf2;// Combines two 32-byte byte arraysfunction xor32(a, b) {    (0, _assert_js_1.bytes)(a, 32);    (0, _assert_js_1.bytes)(b, 32);    const arr = new Uint8Array(32);    for (let i = 0; i < 32; i++) {        arr[i] = a[i] ^ b[i];    }    return arr;}function strHasLength(str, min, max) {    return typeof str === 'string' && str.length >= min && str.length <= max;}/** * Derives main seed. Takes a lot of time. Prefer `eskdf` method instead. */function deriveMainSeed(username, password) {    if (!strHasLength(username, 8, 255))        throw new Error('invalid username');    if (!strHasLength(password, 8, 255))        throw new Error('invalid password');    const scr = scrypt(password + '\u{1}', username + '\u{1}');    const pbk = pbkdf2(password + '\u{2}', username + '\u{2}');    const res = xor32(scr, pbk);    scr.fill(0);    pbk.fill(0);    return res;}exports.deriveMainSeed = deriveMainSeed;/** * Converts protocol & accountId pair to HKDF salt & info params. */function getSaltInfo(protocol, accountId = 0) {    // Note that length here also repeats two lines below    // We do an additional length check here to reduce the scope of DoS attacks    if (!(strHasLength(protocol, 3, 15) && /^[a-z0-9]{3,15}$/.test(protocol))) {        throw new Error('invalid protocol');    }    // Allow string account ids for some protocols    const allowsStr = /^password\d{0,3}|ssh|tor|file$/.test(protocol);    let salt; // Extract salt. Default is undefined.    if (typeof accountId === 'string') {        if (!allowsStr)            throw new Error('accountId must be a number');        if (!strHasLength(accountId, 1, 255))            throw new Error('accountId must be valid string');        salt = (0, utils_js_1.toBytes)(accountId);    }    else if (Number.isSafeInteger(accountId)) {        if (accountId < 0 || accountId > 2 ** 32 - 1)            throw new Error('invalid accountId');        // Convert to Big Endian Uint32        salt = new Uint8Array(4);        (0, utils_js_1.createView)(salt).setUint32(0, accountId, false);    }    else {        throw new Error(`accountId must be a number${allowsStr ? ' or string' : ''}`);    }    const info = (0, utils_js_1.toBytes)(protocol);    return { salt, info };}function countBytes(num) {    if (typeof num !== 'bigint' || num <= BigInt(128))        throw new Error('invalid number');    return Math.ceil(num.toString(2).length / 8);}/** * Parses keyLength and modulus options to extract length of result key. * If modulus is used, adds 64 bits to it as per FIPS 186 B.4.1 to combat modulo bias. */function getKeyLength(options) {    if (!options || typeof options !== 'object')        return 32;    const hasLen = 'keyLength' in options;    const hasMod = 'modulus' in options;    if (hasLen && hasMod)        throw new Error('cannot combine keyLength and modulus options');    if (!hasLen && !hasMod)        throw new Error('must have either keyLength or modulus option');    // FIPS 186 B.4.1 requires at least 64 more bits    const l = hasMod ? countBytes(options.modulus) + 8 : options.keyLength;    if (!(typeof l === 'number' && l >= 16 && l <= 8192))        throw new Error('invalid keyLength');    return l;}/** * Converts key to bigint and divides it by modulus. Big Endian. * Implements FIPS 186 B.4.1, which removes 0 and modulo bias from output. */function modReduceKey(key, modulus) {    const _1 = BigInt(1);    const num = BigInt('0x' + (0, utils_js_1.bytesToHex)(key)); // check for ui8a, then bytesToNumber()    const res = (num % (modulus - _1)) + _1; // Remove 0 from output    if (res < _1)        throw new Error('expected positive number'); // Guard against bad values    const len = key.length - 8; // FIPS requires 64 more bits = 8 bytes    const hex = res.toString(16).padStart(len * 2, '0'); // numberToHex()    const bytes = (0, utils_js_1.hexToBytes)(hex);    if (bytes.length !== len)        throw new Error('invalid length of result key');    return bytes;}/** * ESKDF * @param username - username, email, or identifier, min: 8 characters, should have enough entropy * @param password - password, min: 8 characters, should have enough entropy * @example * const kdf = await eskdf('example-university', 'beginning-new-example'); * const key = kdf.deriveChildKey('aes', 0); * console.log(kdf.fingerprint); * kdf.expire(); */async function eskdf(username, password) {    // We are using closure + object instead of class because    // we want to make `seed` non-accessible for any external function.    let seed = deriveMainSeed(username, password);    function deriveCK(protocol, accountId = 0, options) {        (0, _assert_js_1.bytes)(seed, 32);        const { salt, info } = getSaltInfo(protocol, accountId); // validate protocol & accountId        const keyLength = getKeyLength(options); // validate options        const key = (0, hkdf_js_1.hkdf)(sha256_js_1.sha256, seed, salt, info, keyLength);        // Modulus has already been validated        return options && 'modulus' in options ? modReduceKey(key, options.modulus) : key;    }    function expire() {        if (seed)            seed.fill(1);        seed = undefined;    }    // prettier-ignore    const fingerprint = Array.from(deriveCK('fingerprint', 0))        .slice(0, 6)        .map((char) => char.toString(16).padStart(2, '0').toUpperCase())        .join(':');    return Object.freeze({ deriveChildKey: deriveCK, expire, fingerprint });}exports.eskdf = eskdf;//# sourceMappingURL=eskdf.js.map
 |