Press n or j to go to the next uncovered block, b, p or k for the previous block.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 | 1x 1x 112x 43x 42x 42x 1x 41x 40x 40x 40x 40x 40x 40x 39x 39x 18x 18x 39x 39x 39x 39x 39x 39x 39x 39x 39x 39x 31x 31x 31x 31x 24x 24x 31x 39x 39x 1x | /** * @file MFKDF TOTP Factor Derivation * @copyright Multifactor, Inc. 2022–2025 * * @description * Derive TOTP factor for multi-factor key derivation * * @author Vivek Nair (https://nair.me) <[email protected]> */ const speakeasy = require('speakeasy') const { decrypt } = require('../../crypt') function mod (n, m) { return ((n % m) + m) % m } /** * Derive an MFKDF TOTP factor * * @example * // setup key with totp factor * const setup = await mfkdf.setup.key([ * await mfkdf.setup.factors.totp({ * secret: Buffer.from('abcdefghijklmnopqrst'), * time: 1650430806597 * }) * ]) * * // derive key with totp factor * const derive = await mfkdf.derive.key(setup.policy, { * totp: mfkdf.derive.factors.totp(953265, { time: 1650430943604 }) * }) * * setup.key.toString('hex') // -> 01d0…2516 * derive.key.toString('hex') // -> 01d0…2516 * * @param {number} code - The TOTP code from which to derive an MFKDF factor * @param {Object} [options] - Additional options for deriving the TOTP factor * @param {number} [options.time] - Current time for TOTP; defaults to Date.now() * @param {Object} [options.oracle] - Timing oracle offsets to use; none by default * @returns {function(config:Object): Promise<MFKDFFactor>} Async function to generate MFKDF factor information * @author Vivek Nair (https://nair.me) <[email protected]> * @since 0.13.0 * @memberof derive.factors */ function totp (code, options = {}) { if (!Number.isInteger(code)) throw new TypeError('code must be an integer') if (typeof options.time === 'undefined') options.time = Date.now() if (!Number.isInteger(options.time)) { throw new TypeError('time must be an integer') } if (options.time <= 0) throw new RangeError('time must be positive') return async (params) => { const offsets = Buffer.from(params.offsets, 'base64') const startCounter = Math.floor(params.start / (params.step * 1000)) const nowCounter = Math.floor(options.time / (params.step * 1000)) const index = nowCounter - startCounter if (index >= params.window) throw new RangeError('TOTP window exceeded') let offset = offsets.readUInt32BE(4 * index) if (options.oracle) { const time = nowCounter * params.step * 1000 offset = mod(offset - options.oracle[time], 10 ** params.digits) } const target = mod(offset + code, 10 ** params.digits) const buffer = Buffer.allocUnsafe(4) buffer.writeUInt32BE(target, 0) return { type: 'totp', data: buffer, params: async ({ key }) => { const pad = Buffer.from(params.pad, 'base64') const secret = decrypt(pad, key) const time = options.time const newOffsets = Buffer.allocUnsafe(4 * params.window) offsets.copy(newOffsets, 0, 4 * index) for (let i = params.window - index; i < params.window; i++) { const counter = Math.floor(time / (params.step * 1000)) + i const code = parseInt( speakeasy.totp({ secret: secret.subarray(0, 20).toString('hex'), encoding: 'hex', step: params.step, counter, algorithm: params.hash, digits: params.digits }) ) let offset = mod(target - code, 10 ** params.digits) if (options.oracle) { const time = counter * params.step * 1000 offset = mod(offset + options.oracle[time], 10 ** params.digits) } newOffsets.writeUInt32BE(offset, 4 * i) } return { start: time, hash: params.hash, digits: params.digits, step: params.step, window: params.window, pad: params.pad, offsets: newOffsets.toString('base64') } }, output: async () => { return {} } } } } module.exports.totp = totp |