| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155 | 
							- import { bytes as assertBytes } from './_assert.js';
 
- import { hkdf } from './hkdf.js';
 
- import { sha256 } from './sha256.js';
 
- import { pbkdf2 as _pbkdf2 } from './pbkdf2.js';
 
- import { scrypt as _scrypt } from './scrypt.js';
 
- import { bytesToHex, createView, hexToBytes, toBytes } from './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 KDF
 
- export function scrypt(password, salt) {
 
-     return _scrypt(password, salt, { N: SCRYPT_FACTOR, r: 8, p: 1, dkLen: 32 });
 
- }
 
- // PBKDF2-HMAC-SHA256
 
- export function pbkdf2(password, salt) {
 
-     return _pbkdf2(sha256, password, salt, { c: PBKDF2_FACTOR, dkLen: 32 });
 
- }
 
- // Combines two 32-byte byte arrays
 
- function xor32(a, b) {
 
-     assertBytes(a, 32);
 
-     assertBytes(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.
 
-  */
 
- export 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;
 
- }
 
- /**
 
-  * 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 = 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);
 
-         createView(salt).setUint32(0, accountId, false);
 
-     }
 
-     else {
 
-         throw new Error(`accountId must be a number${allowsStr ? ' or string' : ''}`);
 
-     }
 
-     const info = 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' + 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 = 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();
 
-  */
 
- export 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) {
 
-         assertBytes(seed, 32);
 
-         const { salt, info } = getSaltInfo(protocol, accountId); // validate protocol & accountId
 
-         const keyLength = getKeyLength(options); // validate options
 
-         const key = hkdf(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 });
 
- }
 
- //# sourceMappingURL=eskdf.js.map
 
 
  |