feat: changed TOTP and base32 library to self-writed special for quick-js, fixed BigInt incompatibility
This commit is contained in:
54
lib/totp-quickjs/OTPGenerator.js
Normal file
54
lib/totp-quickjs/OTPGenerator.js
Normal file
@@ -0,0 +1,54 @@
|
||||
import { decode } from "./base32decoder.js";
|
||||
import jsSHA from "jssha";
|
||||
"use bigint"
|
||||
/**
|
||||
* get HOTP based on counter
|
||||
* @param {BigInt} counter BigInt counter of HOTP
|
||||
* @param {string} secret base32 encoded string
|
||||
* @param {number} [digits=6] number of digits in OTP token
|
||||
* @param {string} [hashType='SHA-1'] type of hash (more in jsSHA documentation)
|
||||
* @returns HOTP string
|
||||
*/
|
||||
export function getHOTP(counter, secret, digits = 6, hashType = 'SHA-1'){
|
||||
|
||||
//Stage 1: Prepare data
|
||||
const rawDataCounter = new DataView(new ArrayBuffer(8))
|
||||
rawDataCounter.setUint32(0, counter >> 32)
|
||||
rawDataCounter.setUint32(4, counter)
|
||||
const bCounter = new Uint8Array(rawDataCounter.buffer)
|
||||
|
||||
const bSecret = new Uint8Array(decode(secret).match(/.{1,2}/g).map(chunk => parseInt(chunk, 16))); //confirmed
|
||||
|
||||
//Stage 2: Hash data
|
||||
const jssha = new jsSHA(hashType, 'UINT8ARRAY')
|
||||
jssha.setHMACKey(bSecret, 'UINT8ARRAY')
|
||||
jssha.update(bCounter)
|
||||
const hmacResult = jssha.getHMAC('UINT8ARRAY') //confirmed
|
||||
|
||||
//Stage 3: Dynamic truncate
|
||||
const offsetB = hmacResult[19] & 0xf;
|
||||
const P = hmacResult.slice(offsetB, offsetB + 4)
|
||||
P[0] = P[0] & 0x7f;
|
||||
|
||||
//Stage 4: Format string
|
||||
let res = (new DataView(P.buffer).getInt32(0) % Math.pow(10, digits)).toString()
|
||||
while(res.length < digits)
|
||||
res = '0' + res;
|
||||
return res;
|
||||
}
|
||||
|
||||
/**
|
||||
* get OTP based on current time
|
||||
* @param {string} secret base32 encoded string
|
||||
* @param {number} [digits=6] digits in OTP
|
||||
* @param {number} [time=Date.now()] time for counter (default unix time epoch)
|
||||
* @param {number} [fetchTime=30] period of token in seconds
|
||||
* @param {number} [timeOffset=0] time offset for token in seconds
|
||||
* @param {string} [hashType='SHA-1'] type of hash (more in jsSHA documentation)
|
||||
* @returns TOTP string
|
||||
*/
|
||||
export function getTOTP(secret, digits = 6, time = Date.now(), fetchTime = 30, timeOffset = 0, hashType = 'SHA-1')
|
||||
{
|
||||
const unixTime = Math.round((time / 1000 + timeOffset) / fetchTime)
|
||||
return getHOTP(BigInt(unixTime), secret, digits)
|
||||
}
|
||||
26
lib/totp-quickjs/base32decoder.js
Normal file
26
lib/totp-quickjs/base32decoder.js
Normal file
@@ -0,0 +1,26 @@
|
||||
export function decode(base32) {
|
||||
for (
|
||||
var base32chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567",
|
||||
bits = "",
|
||||
hex = "",
|
||||
i = 0;
|
||||
i < base32.length;
|
||||
i++
|
||||
) {
|
||||
var val = base32chars.indexOf(base32.charAt(i).toUpperCase());
|
||||
bits += leftpad(val.toString(2), 5, "0");
|
||||
}
|
||||
for (i = 0; i + 4 <= bits.length; i += 4) {
|
||||
var chunk = bits.substr(i, 4);
|
||||
hex += parseInt(chunk, 2).toString(16);
|
||||
}
|
||||
return hex;
|
||||
}
|
||||
|
||||
function leftpad(str, len, pad) {
|
||||
return (
|
||||
len + 1 >= str.length &&
|
||||
(str = new Array(len + 1 - str.length).join(pad) + str),
|
||||
str
|
||||
);
|
||||
}
|
||||
61
lib/totp-quickjs/index.js
Normal file
61
lib/totp-quickjs/index.js
Normal file
@@ -0,0 +1,61 @@
|
||||
import { getHOTP } from "./OTPGenerator.js"
|
||||
"use bigint"
|
||||
/**
|
||||
* TOTP instance
|
||||
*/
|
||||
export class TOTP{
|
||||
/**
|
||||
*
|
||||
* @param {string} secret base32 encoded string
|
||||
* @param {string} issuer issuer of TOTP
|
||||
* @param {number} [digits=6] number of digits in OTP token
|
||||
* @param {number} [fetchTime=30] period of token in seconds
|
||||
* @param {number} [timeOffset=0] time offset for token in seconds
|
||||
* @param {string} [hashType='SHA-1'] type of hash (more in jsSHA documentation)
|
||||
*/
|
||||
constructor(secret,
|
||||
issuer,
|
||||
digits = 6,
|
||||
fetchTime = 30,
|
||||
timeOffset = 0,
|
||||
hashType = 'SHA-1')
|
||||
{
|
||||
this.secret = secret
|
||||
this.issuer = issuer
|
||||
this.digits = digits
|
||||
this.fetchTime = fetchTime
|
||||
this.timeOffset = timeOffset
|
||||
this.hashType = hashType
|
||||
}
|
||||
/**
|
||||
*
|
||||
* @param {number} time time for counter (default unix time epoch)
|
||||
* @returns OTP instance
|
||||
*/
|
||||
getOTP(time = Date.now()){
|
||||
const unixTime = (time / 1000 + this.timeOffset) / this.fetchTime
|
||||
const otp = getHOTP(Math.floor(unixTime), this.secret, this.digits)
|
||||
const expireTime = time +
|
||||
(this.fetchTime -
|
||||
(time / 1000 + this.timeOffset) %
|
||||
this.fetchTime) * 1000
|
||||
|
||||
return new OTP(otp, expireTime)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Class for TOTP.getOTP result
|
||||
*/
|
||||
export class OTP{
|
||||
/**
|
||||
*
|
||||
* @param {string} otp OTP string
|
||||
* @param {number} expireTime time in seconds to reset OTP
|
||||
*/
|
||||
constructor(otp, expireTime)
|
||||
{
|
||||
this.otp = otp
|
||||
this.expireTime = expireTime
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user