mirror of
https://github.com/bitwarden/browser.git
synced 2024-12-20 16:07:45 +01:00
totp service to ts
This commit is contained in:
parent
8d60c6c824
commit
0c878d78f3
@ -11,7 +11,6 @@
|
|||||||
<script type="text/javascript" src="services/cipherService.js"></script>
|
<script type="text/javascript" src="services/cipherService.js"></script>
|
||||||
<script type="text/javascript" src="services/syncService.js"></script>
|
<script type="text/javascript" src="services/syncService.js"></script>
|
||||||
<script type="text/javascript" src="services/autofillService.js"></script>
|
<script type="text/javascript" src="services/autofillService.js"></script>
|
||||||
<script type="text/javascript" src="services/totpService.js"></script>
|
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
</body>
|
</body>
|
||||||
|
@ -8,6 +8,7 @@ import i18nService from './services/i18nService.js';
|
|||||||
import LockService from './services/lockService.js';
|
import LockService from './services/lockService.js';
|
||||||
import PasswordGenerationService from './services/passwordGeneration.service';
|
import PasswordGenerationService from './services/passwordGeneration.service';
|
||||||
import TokenService from './services/token.service';
|
import TokenService from './services/token.service';
|
||||||
|
import TotpService from './services/totp.service';
|
||||||
import UtilsService from './services/utils.service';
|
import UtilsService from './services/utils.service';
|
||||||
|
|
||||||
// Model imports
|
// 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,
|
window.bg_syncService = bg_syncService = new SyncService(bg_cipherService, bg_folderService, bg_userService, bg_apiService, bg_settingsService,
|
||||||
bg_cryptoService, logout);
|
bg_cryptoService, logout);
|
||||||
window.bg_passwordGenerationService = bg_passwordGenerationService = new PasswordGenerationService(bg_cryptoService);
|
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,
|
window.bg_autofillService = bg_autofillService = new AutofillService(bg_utilsService, bg_totpService, bg_tokenService, bg_cipherService,
|
||||||
bg_constantsService);
|
bg_constantsService);
|
||||||
|
|
||||||
|
@ -16,12 +16,12 @@ export default class EnvironmentService {
|
|||||||
|
|
||||||
async setUrlsFromStorage(): Promise<void> {
|
async setUrlsFromStorage(): Promise<void> {
|
||||||
const urlsObj: any = await UtilsService.getObjFromStorage(ConstantsService.environmentUrlsKey);
|
const urlsObj: any = await UtilsService.getObjFromStorage(ConstantsService.environmentUrlsKey);
|
||||||
const urls = urlsObj[ConstantsService.environmentUrlsKey] || {
|
const urls = urlsObj || {
|
||||||
base: null,
|
base: null,
|
||||||
api: null,
|
api: null,
|
||||||
identity: null,
|
identity: null,
|
||||||
icons: null,
|
icons: null,
|
||||||
webVault: null
|
webVault: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
const envUrls = new EnvironmentUrls();
|
const envUrls = new EnvironmentUrls();
|
||||||
@ -51,7 +51,7 @@ export default class EnvironmentService {
|
|||||||
api: urls.api,
|
api: urls.api,
|
||||||
identity: urls.identity,
|
identity: urls.identity,
|
||||||
webVault: urls.webVault,
|
webVault: urls.webVault,
|
||||||
icons: urls.icons
|
icons: urls.icons,
|
||||||
});
|
});
|
||||||
|
|
||||||
this.baseUrl = urls.base;
|
this.baseUrl = urls.base;
|
||||||
@ -78,7 +78,7 @@ export default class EnvironmentService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
url = url.replace(/\/+$/g, '');
|
url = url.replace(/\/+$/g, '');
|
||||||
if (!url.startsWith("http://") && !url.startsWith('https://')) {
|
if (!url.startsWith('http://') && !url.startsWith('https://')) {
|
||||||
url = 'https://' + url;
|
url = 'https://' + url;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
113
src/services/totp.service.ts
Normal file
113
src/services/totp.service.ts
Normal file
@ -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<string> {
|
||||||
|
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<boolean> {
|
||||||
|
return await UtilsService.getObjFromStorage<boolean>(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);
|
||||||
|
}
|
||||||
|
}
|
@ -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;
|
|
||||||
};
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user