1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-22 19:23:52 +00:00

support for otpauth:// urls for totp codes

This commit is contained in:
Kyle Spearrin
2018-07-31 11:25:50 -04:00
parent 2045e7047a
commit 41ab22a82f
4 changed files with 108 additions and 39 deletions

View File

@@ -9,30 +9,78 @@ import { Utils } from '../misc/utils';
const b32Chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567';
export class TotpService implements TotpServiceAbstraction {
constructor(private storageService: StorageService, private cryptoFunctionService: CryptoFunctionService) {}
constructor(private storageService: StorageService, private cryptoFunctionService: CryptoFunctionService) { }
async getCode(key: string): Promise<string> {
let period = 30;
let alg: 'sha1' | 'sha256' | 'sha512' = 'sha1';
let digits = 6;
let keyB32 = key;
if (key.indexOf('otpauth://') === 0) {
const params = Utils.getQueryParams(key);
if (params.has('digits') && params.get('digits') != null) {
try {
const digitParams = parseInt(params.get('digits').trim(), null);
if (digitParams > 10) {
digits = 10;
} else if (digitParams > 0) {
digits = digitParams;
}
} catch { }
}
if (params.has('period') && params.get('period') != null) {
try {
period = parseInt(params.get('period').trim(), null);
} catch { }
}
if (params.has('secret') && params.get('secret') != null) {
keyB32 = params.get('secret');
}
if (params.has('algorithm') && params.get('algorithm') != null) {
const algParam = params.get('algorithm').toLowerCase();
if (algParam === 'sha1' || algParam === 'sha256' || algParam === 'sha512') {
alg = algParam;
}
}
}
async getCode(keyb32: string): Promise<string> {
const epoch = Math.round(new Date().getTime() / 1000.0);
const timeHex = this.leftpad(this.dec2hex(Math.floor(epoch / 30)), 16, '0');
const timeHex = this.leftpad(this.dec2hex(Math.floor(epoch / period)), 16, '0');
const timeBytes = Utils.fromHexToArray(timeHex);
const keyBytes = this.b32tobytes(keyb32);
const keyBytes = this.b32tobytes(keyB32);
if (!keyBytes.length || !timeBytes.length) {
return null;
}
const hashHex = await this.sign(keyBytes, timeBytes);
if (!hashHex) {
const hash = await this.sign(keyBytes, timeBytes, alg);
if (hash.length === 0) {
return null;
}
const offset = this.hex2dec(hashHex.substring(hashHex.length - 1));
// tslint:disable-next-line
let otp = (this.hex2dec(hashHex.substr(offset * 2, 8)) & this.hex2dec('7fffffff')) + '';
otp = (otp).substr(otp.length - 6, 6);
/* tslint:disable */
const offset = (hash[hash.length - 1] & 0xf);
const binary = ((hash[offset] & 0x7f) << 24) | ((hash[offset + 1] & 0xff) << 16) |
((hash[offset + 2] & 0xff) << 8) | (hash[offset + 3] & 0xff);
/* tslint:enable */
let otp = (binary % Math.pow(10, digits)).toString();
otp = this.leftpad(otp, digits, '0');
return otp;
}
getTimeInterval(key: string): number {
let period = 30;
if (key.indexOf('otpauth://') === 0) {
const params = Utils.getQueryParams(key);
if (params.has('period') && params.get('period') != null) {
try {
period = parseInt(params.get('period').trim(), null);
} catch { }
}
}
return period;
}
async isAutoCopyEnabled(): Promise<boolean> {
return !(await this.storageService.get<boolean>(ConstantsService.disableAutoTotpCopyKey));
}
@@ -50,10 +98,6 @@ export class TotpService implements TotpServiceAbstraction {
return (d < 15.5 ? '0' : '') + Math.round(d).toString(16);
}
private hex2dec(s: string): number {
return parseInt(s, 16);
}
private b32tohex(s: string): string {
s = s.toUpperCase();
let cleanedInput = '';
@@ -87,8 +131,8 @@ export class TotpService implements TotpServiceAbstraction {
return Utils.fromHexToArray(this.b32tohex(s));
}
private async sign(keyBytes: Uint8Array, timeBytes: Uint8Array) {
const signature = await this.cryptoFunctionService.hmac(timeBytes.buffer, keyBytes.buffer, 'sha1');
return Utils.fromBufferToHex(signature);
private async sign(keyBytes: Uint8Array, timeBytes: Uint8Array, alg: 'sha1' | 'sha256' | 'sha512') {
const signature = await this.cryptoFunctionService.hmac(timeBytes.buffer, keyBytes.buffer, alg);
return new Uint8Array(signature);
}
}