diff --git a/src/background.html b/src/background.html index 4a69fa81..fe0c92c3 100644 --- a/src/background.html +++ b/src/background.html @@ -11,7 +11,6 @@ - diff --git a/src/background.js b/src/background.js index cfb42b56..3c3a35b0 100644 --- a/src/background.js +++ b/src/background.js @@ -8,6 +8,7 @@ import i18nService from './services/i18nService.js'; import LockService from './services/lockService.js'; import PasswordGenerationService from './services/passwordGeneration.service'; import TokenService from './services/token.service'; +import TotpService from './services/totp.service'; import UtilsService from './services/utils.service'; // Model imports @@ -93,7 +94,7 @@ var bg_isBackground = true, window.bg_syncService = bg_syncService = new SyncService(bg_cipherService, bg_folderService, bg_userService, bg_apiService, bg_settingsService, bg_cryptoService, logout); window.bg_passwordGenerationService = bg_passwordGenerationService = new PasswordGenerationService(bg_cryptoService); - window.bg_totpService = bg_totpService = new TotpService(bg_constantsService); + window.bg_totpService = bg_totpService = new TotpService(); window.bg_autofillService = bg_autofillService = new AutofillService(bg_utilsService, bg_totpService, bg_tokenService, bg_cipherService, bg_constantsService); diff --git a/src/services/environment.service.ts b/src/services/environment.service.ts index 1ff5b98b..098a67f7 100644 --- a/src/services/environment.service.ts +++ b/src/services/environment.service.ts @@ -16,12 +16,12 @@ export default class EnvironmentService { async setUrlsFromStorage(): Promise { const urlsObj: any = await UtilsService.getObjFromStorage(ConstantsService.environmentUrlsKey); - const urls = urlsObj[ConstantsService.environmentUrlsKey] || { + const urls = urlsObj || { base: null, api: null, identity: null, icons: null, - webVault: null + webVault: null, }; const envUrls = new EnvironmentUrls(); @@ -51,7 +51,7 @@ export default class EnvironmentService { api: urls.api, identity: urls.identity, webVault: urls.webVault, - icons: urls.icons + icons: urls.icons, }); this.baseUrl = urls.base; @@ -78,7 +78,7 @@ export default class EnvironmentService { } url = url.replace(/\/+$/g, ''); - if (!url.startsWith("http://") && !url.startsWith('https://')) { + if (!url.startsWith('http://') && !url.startsWith('https://')) { url = 'https://' + url; } diff --git a/src/services/totp.service.ts b/src/services/totp.service.ts new file mode 100644 index 00000000..a0f1e19f --- /dev/null +++ b/src/services/totp.service.ts @@ -0,0 +1,113 @@ +import ConstantsService from './constants.service'; +import UtilsService from './utils.service'; + +const b32Chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567'; + +const TotpAlgorithm = { + name: 'HMAC', + hash: { name: 'SHA-1' }, +}; + +export default class TotpService { + async getCode(keyb32: string): Promise { + const epoch = Math.round(new Date().getTime() / 1000.0); + const timeHex = this.leftpad(this.dec2hex(Math.floor(epoch / 30)), 16, '0'); + const timeBytes = this.hex2bytes(timeHex); + const keyBytes = this.b32tobytes(keyb32); + + if (!keyBytes.length || !timeBytes.length) { + return null; + } + + const hashHex = await this.sign(keyBytes, timeBytes); + if (!hashHex) { + 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); + return otp; + } + + async isAutoCopyEnabled(): Promise { + return await UtilsService.getObjFromStorage(ConstantsService.disableAutoTotpCopyKey); + } + + // Helpers + + private leftpad(s: string, l: number, p: string): string { + if (l + 1 >= s.length) { + s = Array(l + 1 - s.length).join(p) + s; + } + return s; + } + + private dec2hex(d: number): string { + return (d < 15.5 ? '0' : '') + Math.round(d).toString(16); + } + + private hex2dec(s: string): number { + return parseInt(s, 16); + } + + private hex2bytes(s: string): Uint8Array { + const bytes = new Uint8Array(s.length / 2); + for (let i = 0; i < s.length; i += 2) { + bytes[i / 2] = parseInt(s.substr(i, 2), 16); + } + return bytes; + } + + private buff2hex(buff: ArrayBuffer): string { + const bytes = new Uint8Array(buff); + const hex = []; + for (const b of bytes) { + // tslint:disable-next-line + hex.push((b >>> 4).toString(16)); + // tslint:disable-next-line + hex.push((b & 0xF).toString(16)); + } + return hex.join(''); + } + + private b32tohex(s: string): string { + s = s.toUpperCase(); + let cleanedInput = ''; + + for (const c of s) { + if (b32Chars.indexOf(c) < 0) { + continue; + } + + cleanedInput += c; + } + s = cleanedInput; + + let bits = ''; + let hex = ''; + for (let i = 0; i < s.length; i++) { + const byteIndex = b32Chars.indexOf(s.charAt(i)); + if (byteIndex < 0) { + continue; + } + bits += this.leftpad(byteIndex.toString(2), 5, '0'); + } + for (let i = 0; i + 4 <= bits.length; i += 4) { + const chunk = bits.substr(i, 4); + hex = hex + parseInt(chunk, 2).toString(16); + } + return hex; + } + + private b32tobytes(s: string): Uint8Array { + return this.hex2bytes(this.b32tohex(s)); + } + + private async sign(keyBytes: Uint8Array, timeBytes: Uint8Array) { + const key = await window.crypto.subtle.importKey('raw', keyBytes, TotpAlgorithm, false, ['sign']); + const signature = await window.crypto.subtle.sign(TotpAlgorithm, key, timeBytes); + return this.buff2hex(signature); + } +} diff --git a/src/services/totpService.js b/src/services/totpService.js deleted file mode 100644 index b079a605..00000000 --- a/src/services/totpService.js +++ /dev/null @@ -1,123 +0,0 @@ -function TotpService(constantsService) { - this.constantsService = constantsService; - initTotpService(); -} - -function initTotpService() { - var b32Chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567'; - - var leftpad = function (s, l, p) { - if (l + 1 >= s.length) { - s = Array(l + 1 - s.length).join(p) + s; - } - return s; - }; - - var dec2hex = function (d) { - return (d < 15.5 ? '0' : '') + Math.round(d).toString(16); - }; - - var hex2dec = function (s) { - return parseInt(s, 16); - }; - - var hex2bytes = function (s) { - var bytes = new Uint8Array(s.length / 2); - for (var i = 0; i < s.length; i += 2) { - bytes[i / 2] = parseInt(s.substr(i, 2), 16); - } - return bytes; - }; - - var buff2hex = function (buff) { - var bytes = new Uint8Array(buff); - var hex = []; - for (var i = 0; i < bytes.length; i++) { - hex.push((bytes[i] >>> 4).toString(16)); - hex.push((bytes[i] & 0xF).toString(16)); - } - return hex.join(''); - }; - - var b32tohex = function (s) { - s = s.toUpperCase(); - var cleanedInput = ''; - var i; - for (i = 0; i < s.length; i++) { - if (b32Chars.indexOf(s[i]) < 0) { - continue; - } - - cleanedInput += s[i]; - } - s = cleanedInput; - - var bits = ''; - var hex = ''; - for (i = 0; i < s.length; i++) { - var byteIndex = b32Chars.indexOf(s.charAt(i)); - if (byteIndex < 0) { - continue; - } - bits += leftpad(byteIndex.toString(2), 5, '0'); - } - for (i = 0; i + 4 <= bits.length; i += 4) { - var chunk = bits.substr(i, 4); - hex = hex + parseInt(chunk, 2).toString(16); - } - return hex; - }; - - var b32tobytes = function (s) { - return hex2bytes(b32tohex(s)); - }; - - var sign = function (keyBytes, timeBytes) { - return window.crypto.subtle.importKey('raw', keyBytes, - { name: 'HMAC', hash: { name: 'SHA-1' } }, false, ['sign']).then(function (key) { - return window.crypto.subtle.sign({ name: 'HMAC', hash: { name: 'SHA-1' } }, key, timeBytes); - }).then(function (signature) { - return buff2hex(signature); - }).catch(function (err) { - return null; - }); - }; - - TotpService.prototype.getCode = function (keyb32) { - var epoch = Math.round(new Date().getTime() / 1000.0); - var timeHex = leftpad(dec2hex(Math.floor(epoch / 30)), 16, '0'); - var timeBytes = hex2bytes(timeHex); - var keyBytes = b32tobytes(keyb32); - - if (!keyBytes.length || !timeBytes.length) { - return Q(null); - } - - return sign(keyBytes, timeBytes).then(function (hashHex) { - if (!hashHex) { - return null; - } - - var offset = hex2dec(hashHex.substring(hashHex.length - 1)); - var otp = (hex2dec(hashHex.substr(offset * 2, 8)) & hex2dec('7fffffff')) + ''; - otp = (otp).substr(otp.length - 6, 6); - return otp; - }); - }; - - TotpService.prototype.isAutoCopyEnabled = function () { - var deferred = Q.defer(); - var self = this; - - chrome.storage.local.get(self.constantsService.disableAutoTotpCopyKey, function (obj) { - if (obj && !!obj[self.constantsService.disableAutoTotpCopyKey]) { - deferred.resolve(false); - } - else { - deferred.resolve(true); - } - }); - - return deferred.promise; - }; -}