mirror of
https://github.com/bitwarden/desktop.git
synced 2024-09-19 02:41:35 +02:00
password generation service to ts
This commit is contained in:
parent
c586349014
commit
8eeb5821f0
@ -15,7 +15,6 @@
|
|||||||
<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/appIdService.js"></script>
|
<script type="text/javascript" src="services/appIdService.js"></script>
|
||||||
<script type="text/javascript" src="services/passwordGenerationService.js"></script>
|
|
||||||
<script type="text/javascript" src="services/totpService.js"></script>
|
<script type="text/javascript" src="services/totpService.js"></script>
|
||||||
<script type="text/javascript" src="services/environmentService.js"></script>
|
<script type="text/javascript" src="services/environmentService.js"></script>
|
||||||
</head>
|
</head>
|
||||||
|
@ -4,6 +4,7 @@ import i18nService from './services/i18nService.js';
|
|||||||
import LockService from './services/lockService.js';
|
import LockService from './services/lockService.js';
|
||||||
import UtilsService from './services/utils.service';
|
import UtilsService from './services/utils.service';
|
||||||
import CryptoService from './services/crypto.service';
|
import CryptoService from './services/crypto.service';
|
||||||
|
import PasswordGenerationService from './services/passwordGeneration.service';
|
||||||
|
|
||||||
// Model imports
|
// Model imports
|
||||||
import { AttachmentData } from './models/data/attachmentData';
|
import { AttachmentData } from './models/data/attachmentData';
|
||||||
@ -86,7 +87,7 @@ var bg_isBackground = true,
|
|||||||
setIcon, refreshBadgeAndMenu);
|
setIcon, refreshBadgeAndMenu);
|
||||||
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_constantsService, bg_utilsService, 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(bg_constantsService);
|
||||||
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);
|
||||||
@ -101,7 +102,7 @@ var bg_isBackground = true,
|
|||||||
eventAction: 'Generated Password From Command'
|
eventAction: 'Generated Password From Command'
|
||||||
});
|
});
|
||||||
bg_passwordGenerationService.getOptions().then(function (options) {
|
bg_passwordGenerationService.getOptions().then(function (options) {
|
||||||
var password = bg_passwordGenerationService.generatePassword(options);
|
var password = PasswordGenerationService.generatePassword(options);
|
||||||
bg_utilsService.copyToClipboard(password);
|
bg_utilsService.copyToClipboard(password);
|
||||||
bg_passwordGenerationService.addHistory(password);
|
bg_passwordGenerationService.addHistory(password);
|
||||||
});
|
});
|
||||||
@ -236,7 +237,7 @@ var bg_isBackground = true,
|
|||||||
eventAction: 'Generated Password From Context Menu'
|
eventAction: 'Generated Password From Context Menu'
|
||||||
});
|
});
|
||||||
bg_passwordGenerationService.getOptions().then(function (options) {
|
bg_passwordGenerationService.getOptions().then(function (options) {
|
||||||
var password = bg_passwordGenerationService.generatePassword(options);
|
var password = PasswordGenerationService.generatePassword(options);
|
||||||
bg_utilsService.copyToClipboard(password);
|
bg_utilsService.copyToClipboard(password);
|
||||||
bg_passwordGenerationService.addHistory(password);
|
bg_passwordGenerationService.addHistory(password);
|
||||||
});
|
});
|
||||||
|
9
src/models/domain/passwordHistory.ts
Normal file
9
src/models/domain/passwordHistory.ts
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
export default class PasswordHistory {
|
||||||
|
password: string;
|
||||||
|
date: number;
|
||||||
|
|
||||||
|
constructor(password: string, date: number) {
|
||||||
|
this.password = password;
|
||||||
|
this.date = date;
|
||||||
|
}
|
||||||
|
}
|
@ -17,11 +17,11 @@ const Keys = {
|
|||||||
|
|
||||||
const SigningAlgorithm = {
|
const SigningAlgorithm = {
|
||||||
name: 'HMAC',
|
name: 'HMAC',
|
||||||
hash: { name: 'SHA-256' }
|
hash: { name: 'SHA-256' },
|
||||||
};
|
};
|
||||||
|
|
||||||
const AesAlgorithm = {
|
const AesAlgorithm = {
|
||||||
name: 'AES-CBC'
|
name: 'AES-CBC',
|
||||||
};
|
};
|
||||||
|
|
||||||
const Crypto = window.crypto;
|
const Crypto = window.crypto;
|
||||||
@ -269,7 +269,7 @@ export default class CryptoService {
|
|||||||
return this.encrypt(bytes, key, 'raw');
|
return this.encrypt(bytes, key, 'raw');
|
||||||
}
|
}
|
||||||
|
|
||||||
async encrypt(plainValue: string | Uint8Array, key: SymmetricCryptoKey,
|
async encrypt(plainValue: string | Uint8Array, key?: SymmetricCryptoKey,
|
||||||
plainValueEncoding: string = 'utf8'): Promise<CipherString> {
|
plainValueEncoding: string = 'utf8'): Promise<CipherString> {
|
||||||
if (!plainValue) {
|
if (!plainValue) {
|
||||||
return Promise.resolve(null);
|
return Promise.resolve(null);
|
||||||
@ -307,7 +307,7 @@ export default class CryptoService {
|
|||||||
return encBytes.buffer;
|
return encBytes.buffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
async decrypt(cipherString: CipherString, key: SymmetricCryptoKey,
|
async decrypt(cipherString: CipherString, key?: SymmetricCryptoKey,
|
||||||
outputEncoding: string = 'utf8'): Promise<string> {
|
outputEncoding: string = 'utf8'): Promise<string> {
|
||||||
const ivBytes: string = forge.util.decode64(cipherString.initializationVector);
|
const ivBytes: string = forge.util.decode64(cipherString.initializationVector);
|
||||||
const ctBytes: string = forge.util.decode64(cipherString.cipherText);
|
const ctBytes: string = forge.util.decode64(cipherString.cipherText);
|
||||||
@ -531,7 +531,7 @@ export default class CryptoService {
|
|||||||
|
|
||||||
private async computeMacWC(dataBuf: ArrayBuffer, macKeyBuf: ArrayBuffer): Promise<ArrayBuffer> {
|
private async computeMacWC(dataBuf: ArrayBuffer, macKeyBuf: ArrayBuffer): Promise<ArrayBuffer> {
|
||||||
const key = await Subtle.importKey('raw', macKeyBuf, SigningAlgorithm, false, ['sign']);
|
const key = await Subtle.importKey('raw', macKeyBuf, SigningAlgorithm, false, ['sign']);
|
||||||
return await Subtle.sign(SigningAlgorithm, key, dataBuf);
|
return await Subtle.sign(SigningAlgorithm, key, dataBuf);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Safely compare two MACs in a way that protects against timing attacks (Double HMAC Verification).
|
// Safely compare two MACs in a way that protects against timing attacks (Double HMAC Verification).
|
||||||
|
245
src/services/passwordGeneration.service.ts
Normal file
245
src/services/passwordGeneration.service.ts
Normal file
@ -0,0 +1,245 @@
|
|||||||
|
import { CipherString } from '../models/domain/cipherString';
|
||||||
|
import PasswordHistory from '../models/domain/passwordHistory';
|
||||||
|
|
||||||
|
import ConstantsService from './constants.service';
|
||||||
|
import CryptoService from './crypto.service';
|
||||||
|
import UtilsService from './utils.service';
|
||||||
|
|
||||||
|
const DefaultOptions = {
|
||||||
|
length: 10,
|
||||||
|
ambiguous: false,
|
||||||
|
number: true,
|
||||||
|
minNumber: 1,
|
||||||
|
uppercase: true,
|
||||||
|
minUppercase: 1,
|
||||||
|
lowercase: true,
|
||||||
|
minLowercase: 1,
|
||||||
|
special: false,
|
||||||
|
minSpecial: 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
const Keys = {
|
||||||
|
options: 'passwordGenerationOptions',
|
||||||
|
};
|
||||||
|
|
||||||
|
const MaxPasswordsInHistory = 100;
|
||||||
|
|
||||||
|
export default class PasswordGenerationService {
|
||||||
|
static generatePassword(options: any): string {
|
||||||
|
// overload defaults with given options
|
||||||
|
const o = UtilsService.extendObject({}, DefaultOptions, options);
|
||||||
|
|
||||||
|
// sanitize
|
||||||
|
if (o.uppercase && o.minUppercase < 0) {
|
||||||
|
o.minUppercase = 1;
|
||||||
|
}
|
||||||
|
if (o.lowercase && o.minLowercase < 0) {
|
||||||
|
o.minLowercase = 1;
|
||||||
|
}
|
||||||
|
if (o.number && o.minNumber < 0) {
|
||||||
|
o.minNumber = 1;
|
||||||
|
}
|
||||||
|
if (o.special && o.minSpecial < 0) {
|
||||||
|
o.minSpecial = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!o.length || o.length < 1) {
|
||||||
|
o.length = 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
const minLength: number = o.minUppercase + o.minLowercase + o.minNumber + o.minSpecial;
|
||||||
|
if (o.length < minLength) {
|
||||||
|
o.length = minLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
const positions: string[] = [];
|
||||||
|
if (o.lowercase && o.minLowercase > 0) {
|
||||||
|
for (let i = 0; i < o.minLowercase; i++) {
|
||||||
|
positions.push('l');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (o.uppercase && o.minUppercase > 0) {
|
||||||
|
for (let i = 0; i < o.minUppercase; i++) {
|
||||||
|
positions.push('u');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (o.number && o.minNumber > 0) {
|
||||||
|
for (let i = 0; i < o.minNumber; i++) {
|
||||||
|
positions.push('n');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (o.special && o.minSpecial > 0) {
|
||||||
|
for (let i = 0; i < o.minSpecial; i++) {
|
||||||
|
positions.push('s');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
while (positions.length < o.length) {
|
||||||
|
positions.push('a');
|
||||||
|
}
|
||||||
|
|
||||||
|
// shuffle
|
||||||
|
positions.sort(() => {
|
||||||
|
return UtilsService.secureRandomNumber(0, 1) * 2 - 1;
|
||||||
|
});
|
||||||
|
|
||||||
|
// build out the char sets
|
||||||
|
let allCharSet = '';
|
||||||
|
|
||||||
|
let lowercaseCharSet = 'abcdefghijkmnopqrstuvwxyz';
|
||||||
|
if (o.ambiguous) {
|
||||||
|
lowercaseCharSet += 'l';
|
||||||
|
}
|
||||||
|
if (o.lowercase) {
|
||||||
|
allCharSet += lowercaseCharSet;
|
||||||
|
}
|
||||||
|
|
||||||
|
let uppercaseCharSet = 'ABCDEFGHIJKLMNPQRSTUVWXYZ';
|
||||||
|
if (o.ambiguous) {
|
||||||
|
uppercaseCharSet += 'O';
|
||||||
|
}
|
||||||
|
if (o.uppercase) {
|
||||||
|
allCharSet += uppercaseCharSet;
|
||||||
|
}
|
||||||
|
|
||||||
|
let numberCharSet = '23456789';
|
||||||
|
if (o.ambiguous) {
|
||||||
|
numberCharSet += '01';
|
||||||
|
}
|
||||||
|
if (o.number) {
|
||||||
|
allCharSet += numberCharSet;
|
||||||
|
}
|
||||||
|
|
||||||
|
const specialCharSet = '!@#$%^&*';
|
||||||
|
if (o.special) {
|
||||||
|
allCharSet += specialCharSet;
|
||||||
|
}
|
||||||
|
|
||||||
|
let password = '';
|
||||||
|
for (let i = 0; i < o.length; i++) {
|
||||||
|
let positionChars: string;
|
||||||
|
switch (positions[i]) {
|
||||||
|
case 'l':
|
||||||
|
positionChars = lowercaseCharSet;
|
||||||
|
break;
|
||||||
|
case 'u':
|
||||||
|
positionChars = uppercaseCharSet;
|
||||||
|
break;
|
||||||
|
case 'n':
|
||||||
|
positionChars = numberCharSet;
|
||||||
|
break;
|
||||||
|
case 's':
|
||||||
|
positionChars = specialCharSet;
|
||||||
|
break;
|
||||||
|
case 'a':
|
||||||
|
positionChars = allCharSet;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
const randomCharIndex = UtilsService.secureRandomNumber(0, positionChars.length - 1);
|
||||||
|
password += positionChars.charAt(randomCharIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
return password;
|
||||||
|
}
|
||||||
|
|
||||||
|
optionsCache: any;
|
||||||
|
history: PasswordHistory[];
|
||||||
|
|
||||||
|
constructor(private cryptoService: CryptoService) {
|
||||||
|
const self = this;
|
||||||
|
|
||||||
|
const historyKey = ConstantsService.generatedPasswordHistoryKey;
|
||||||
|
UtilsService.getObjFromStorage<PasswordHistory[]>(historyKey).then((encrypted) => {
|
||||||
|
return self.decryptHistory(encrypted);
|
||||||
|
}).then((history) => {
|
||||||
|
self.history = history;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: remove in favor of static
|
||||||
|
generatePassword(options: any) {
|
||||||
|
return PasswordGenerationService.generatePassword(options);
|
||||||
|
}
|
||||||
|
|
||||||
|
async getOptions() {
|
||||||
|
if (this.optionsCache) {
|
||||||
|
return this.optionsCache;
|
||||||
|
}
|
||||||
|
|
||||||
|
const options = await UtilsService.getObjFromStorage(Keys.options);
|
||||||
|
this.optionsCache = options;
|
||||||
|
return options;
|
||||||
|
}
|
||||||
|
|
||||||
|
async saveOptions(options: any) {
|
||||||
|
await UtilsService.saveObjToStorage(Keys.options, options);
|
||||||
|
this.optionsCache = options;
|
||||||
|
}
|
||||||
|
|
||||||
|
getHistory() {
|
||||||
|
return this.history;
|
||||||
|
}
|
||||||
|
|
||||||
|
async addHistory(password: string) {
|
||||||
|
// Prevent duplicates
|
||||||
|
if (this.matchesPrevious(password)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.history.push(new PasswordHistory(password, Date.now()));
|
||||||
|
|
||||||
|
// Remove old items.
|
||||||
|
if (this.history.length > MaxPasswordsInHistory) {
|
||||||
|
this.history.shift();
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.saveHistory();
|
||||||
|
}
|
||||||
|
|
||||||
|
clear(): Promise<any> {
|
||||||
|
this.history = [];
|
||||||
|
return UtilsService.removeFromStorage(ConstantsService.generatedPasswordHistoryKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async saveHistory() {
|
||||||
|
const history = await this.encryptHistory();
|
||||||
|
return UtilsService.saveObjToStorage(ConstantsService.generatedPasswordHistoryKey, history);
|
||||||
|
}
|
||||||
|
|
||||||
|
private encryptHistory(): Promise<PasswordHistory[]> {
|
||||||
|
if (this.history == null) {
|
||||||
|
return Promise.resolve([]);
|
||||||
|
}
|
||||||
|
|
||||||
|
const self = this;
|
||||||
|
const promises = self.history.map(async (item) => {
|
||||||
|
const encrypted = await self.cryptoService.encrypt(item.password);
|
||||||
|
return new PasswordHistory(encrypted.encryptedString, item.date);
|
||||||
|
});
|
||||||
|
|
||||||
|
return Promise.all(promises);
|
||||||
|
}
|
||||||
|
|
||||||
|
private decryptHistory(history: PasswordHistory[]): Promise<PasswordHistory[]> {
|
||||||
|
if (history == null) {
|
||||||
|
return Promise.resolve([]);
|
||||||
|
}
|
||||||
|
|
||||||
|
const self = this;
|
||||||
|
const promises = history.map(async (item) => {
|
||||||
|
const decrypted = await self.cryptoService.decrypt(new CipherString(item.password));
|
||||||
|
return new PasswordHistory(decrypted, item.date);
|
||||||
|
});
|
||||||
|
|
||||||
|
return Promise.all(promises);
|
||||||
|
}
|
||||||
|
|
||||||
|
private matchesPrevious(password: string): boolean {
|
||||||
|
if (this.history == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const len = this.history.length;
|
||||||
|
return len !== 0 && this.history[len - 1].password === password;
|
||||||
|
}
|
||||||
|
}
|
@ -1,266 +0,0 @@
|
|||||||
function PasswordGenerationService(constantsService, utilsService, cryptoService) {
|
|
||||||
this.optionsCache = null;
|
|
||||||
this.constantsService = constantsService;
|
|
||||||
this.utilsService = utilsService;
|
|
||||||
this.cryptoService = cryptoService;
|
|
||||||
this.history = [];
|
|
||||||
|
|
||||||
initPasswordGenerationService(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
function initPasswordGenerationService(self) {
|
|
||||||
var optionsKey = 'passwordGenerationOptions';
|
|
||||||
var defaultOptions = {
|
|
||||||
length: 10,
|
|
||||||
ambiguous: false,
|
|
||||||
number: true,
|
|
||||||
minNumber: 1,
|
|
||||||
uppercase: true,
|
|
||||||
minUppercase: 1,
|
|
||||||
lowercase: true,
|
|
||||||
minLowercase: 1,
|
|
||||||
special: false,
|
|
||||||
minSpecial: 1
|
|
||||||
};
|
|
||||||
|
|
||||||
PasswordGenerationService.prototype.generatePassword = function (options) {
|
|
||||||
// overload defaults with given options
|
|
||||||
var o = extend({}, defaultOptions, options);
|
|
||||||
|
|
||||||
// sanitize
|
|
||||||
if (o.uppercase && o.minUppercase < 0) o.minUppercase = 1;
|
|
||||||
if (o.lowercase && o.minLowercase < 0) o.minLowercase = 1;
|
|
||||||
if (o.number && o.minNumber < 0) o.minNumber = 1;
|
|
||||||
if (o.special && o.minSpecial < 0) o.minSpecial = 1;
|
|
||||||
|
|
||||||
if (!o.length || o.length < 1) o.length = 10;
|
|
||||||
var minLength = o.minUppercase + o.minLowercase + o.minNumber + o.minSpecial;
|
|
||||||
if (o.length < minLength) o.length = minLength;
|
|
||||||
|
|
||||||
var positions = [];
|
|
||||||
if (o.lowercase && o.minLowercase > 0) {
|
|
||||||
for (var i = 0; i < o.minLowercase; i++) {
|
|
||||||
positions.push('l');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (o.uppercase && o.minUppercase > 0) {
|
|
||||||
for (var j = 0; j < o.minUppercase; j++) {
|
|
||||||
positions.push('u');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (o.number && o.minNumber > 0) {
|
|
||||||
for (var k = 0; k < o.minNumber; k++) {
|
|
||||||
positions.push('n');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (o.special && o.minSpecial > 0) {
|
|
||||||
for (var l = 0; l < o.minSpecial; l++) {
|
|
||||||
positions.push('s');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
while (positions.length < o.length) {
|
|
||||||
positions.push('a');
|
|
||||||
}
|
|
||||||
|
|
||||||
// shuffle
|
|
||||||
positions.sort(function () {
|
|
||||||
return randomInt(0, 1) * 2 - 1;
|
|
||||||
});
|
|
||||||
|
|
||||||
// build out the char sets
|
|
||||||
var allCharSet = '';
|
|
||||||
|
|
||||||
var lowercaseCharSet = 'abcdefghijkmnopqrstuvwxyz';
|
|
||||||
if (o.ambiguous) lowercaseCharSet += 'l';
|
|
||||||
if (o.lowercase) allCharSet += lowercaseCharSet;
|
|
||||||
|
|
||||||
var uppercaseCharSet = 'ABCDEFGHIJKLMNPQRSTUVWXYZ';
|
|
||||||
if (o.ambiguous) uppercaseCharSet += 'O';
|
|
||||||
if (o.uppercase) allCharSet += uppercaseCharSet;
|
|
||||||
|
|
||||||
var numberCharSet = '23456789';
|
|
||||||
if (o.ambiguous) numberCharSet += '01';
|
|
||||||
if (o.number) allCharSet += numberCharSet;
|
|
||||||
|
|
||||||
var specialCharSet = '!@#$%^&*';
|
|
||||||
if (o.special) allCharSet += specialCharSet;
|
|
||||||
|
|
||||||
var password = '';
|
|
||||||
for (var m = 0; m < o.length; m++) {
|
|
||||||
var positionChars;
|
|
||||||
switch (positions[m]) {
|
|
||||||
case 'l': positionChars = lowercaseCharSet; break;
|
|
||||||
case 'u': positionChars = uppercaseCharSet; break;
|
|
||||||
case 'n': positionChars = numberCharSet; break;
|
|
||||||
case 's': positionChars = specialCharSet; break;
|
|
||||||
case 'a': positionChars = allCharSet; break;
|
|
||||||
}
|
|
||||||
|
|
||||||
var randomCharIndex = randomInt(0, positionChars.length - 1);
|
|
||||||
password += positionChars.charAt(randomCharIndex);
|
|
||||||
}
|
|
||||||
|
|
||||||
return password;
|
|
||||||
};
|
|
||||||
|
|
||||||
// EFForg/OpenWireless
|
|
||||||
// ref https://github.com/EFForg/OpenWireless/blob/master/app/js/diceware.js
|
|
||||||
function randomInt(min, max) {
|
|
||||||
var rval = 0;
|
|
||||||
var range = max - min + 1;
|
|
||||||
|
|
||||||
var bits_needed = Math.ceil(Math.log2(range));
|
|
||||||
if (bits_needed > 53) {
|
|
||||||
throw new Exception('We cannot generate numbers larger than 53 bits.');
|
|
||||||
}
|
|
||||||
var bytes_needed = Math.ceil(bits_needed / 8);
|
|
||||||
var mask = Math.pow(2, bits_needed) - 1;
|
|
||||||
// 7776 -> (2^13 = 8192) -1 == 8191 or 0x00001111 11111111
|
|
||||||
|
|
||||||
// Create byte array and fill with N random numbers
|
|
||||||
var byteArray = new Uint8Array(bytes_needed);
|
|
||||||
window.crypto.getRandomValues(byteArray);
|
|
||||||
|
|
||||||
var p = (bytes_needed - 1) * 8;
|
|
||||||
for (var i = 0; i < bytes_needed; i++) {
|
|
||||||
rval += byteArray[i] * Math.pow(2, p);
|
|
||||||
p -= 8;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Use & to apply the mask and reduce the number of recursive lookups
|
|
||||||
rval = rval & mask;
|
|
||||||
|
|
||||||
if (rval >= range) {
|
|
||||||
// Integer out of acceptable range
|
|
||||||
return randomInt(min, max);
|
|
||||||
}
|
|
||||||
// Return an integer that falls within the range
|
|
||||||
return min + rval;
|
|
||||||
}
|
|
||||||
|
|
||||||
function extend() {
|
|
||||||
for (var i = 1; i < arguments.length; i++) {
|
|
||||||
for (var key in arguments[i]) {
|
|
||||||
if (arguments[i].hasOwnProperty(key)) {
|
|
||||||
arguments[0][key] = arguments[i][key];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return arguments[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
PasswordGenerationService.prototype.getOptions = function () {
|
|
||||||
var deferred = Q.defer();
|
|
||||||
var self = this;
|
|
||||||
|
|
||||||
if (self.optionsCache) {
|
|
||||||
deferred.resolve(self.optionsCache);
|
|
||||||
return deferred.promise;
|
|
||||||
}
|
|
||||||
|
|
||||||
chrome.storage.local.get(optionsKey, function (obj) {
|
|
||||||
var options = obj[optionsKey];
|
|
||||||
if (!options) {
|
|
||||||
options = defaultOptions;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.optionsCache = options;
|
|
||||||
deferred.resolve(self.optionsCache);
|
|
||||||
});
|
|
||||||
|
|
||||||
return deferred.promise;
|
|
||||||
};
|
|
||||||
|
|
||||||
PasswordGenerationService.prototype.saveOptions = function (options) {
|
|
||||||
var deferred = Q.defer();
|
|
||||||
var self = this;
|
|
||||||
|
|
||||||
var obj = {};
|
|
||||||
obj[optionsKey] = options;
|
|
||||||
chrome.storage.local.set(obj, function () {
|
|
||||||
self.optionsCache = options;
|
|
||||||
deferred.resolve();
|
|
||||||
});
|
|
||||||
|
|
||||||
return deferred.promise;
|
|
||||||
};
|
|
||||||
|
|
||||||
// History
|
|
||||||
|
|
||||||
var historyKey = self.constantsService.generatedPasswordHistory;
|
|
||||||
var maxPasswordsInHistory = 100;
|
|
||||||
|
|
||||||
self.utilsService.getObjFromStorage(historyKey).then(function (encrypted) {
|
|
||||||
return decrypt(encrypted);
|
|
||||||
}).then(function (history) {
|
|
||||||
history.forEach(function (item) {
|
|
||||||
self.history.push(item);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
PasswordGenerationService.prototype.getHistory = function () {
|
|
||||||
return self.history;
|
|
||||||
};
|
|
||||||
|
|
||||||
PasswordGenerationService.prototype.addHistory = function (password) {
|
|
||||||
// Prevent duplicates
|
|
||||||
if (matchesPrevious(password)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.history.push({
|
|
||||||
password: password,
|
|
||||||
date: Date.now()
|
|
||||||
});
|
|
||||||
|
|
||||||
// Remove old items.
|
|
||||||
if (self.history.length > maxPasswordsInHistory) {
|
|
||||||
self.history.shift();
|
|
||||||
}
|
|
||||||
|
|
||||||
save();
|
|
||||||
};
|
|
||||||
|
|
||||||
PasswordGenerationService.prototype.clear = function () {
|
|
||||||
self.history = [];
|
|
||||||
self.utilsService.removeFromStorage(historyKey);
|
|
||||||
};
|
|
||||||
|
|
||||||
function save() {
|
|
||||||
return encryptHistory().then(function (history) {
|
|
||||||
return self.utilsService.saveObjToStorage(historyKey, history);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function encryptHistory() {
|
|
||||||
var promises = self.history.map(function (historyItem) {
|
|
||||||
return self.cryptoService.encrypt(historyItem.password).then(function (encrypted) {
|
|
||||||
return {
|
|
||||||
password: encrypted.encryptedString,
|
|
||||||
date: historyItem.date
|
|
||||||
};
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
return Q.all(promises);
|
|
||||||
}
|
|
||||||
|
|
||||||
function decrypt(history) {
|
|
||||||
var promises = history.map(function (item) {
|
|
||||||
return self.cryptoService.decrypt(new CipherString(item.password)).then(function (decrypted) {
|
|
||||||
return {
|
|
||||||
password: decrypted,
|
|
||||||
date: item.date
|
|
||||||
};
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
return Q.all(promises);
|
|
||||||
}
|
|
||||||
|
|
||||||
function matchesPrevious(password) {
|
|
||||||
var len = self.history.length;
|
|
||||||
return len !== 0 && self.history[len - 1].password === password;
|
|
||||||
}
|
|
||||||
}
|
|
@ -8,6 +8,55 @@ const AnalyticsIds = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export default class UtilsService {
|
export default class UtilsService {
|
||||||
|
static extendObject(...objects: any[]): any {
|
||||||
|
for (let i = 1; i < objects.length; i++) {
|
||||||
|
for (const key in objects[i]) {
|
||||||
|
if (objects[i].hasOwnProperty(key)) {
|
||||||
|
objects[0][key] = objects[i][key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return objects[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
// EFForg/OpenWireless
|
||||||
|
// ref https://github.com/EFForg/OpenWireless/blob/master/app/js/diceware.js
|
||||||
|
static secureRandomNumber(min: number, max: number): number {
|
||||||
|
let rval = 0;
|
||||||
|
const range = max - min + 1;
|
||||||
|
const bitsNeeded = Math.ceil(Math.log2(range));
|
||||||
|
if (bitsNeeded > 53) {
|
||||||
|
throw new Error('We cannot generate numbers larger than 53 bits.');
|
||||||
|
}
|
||||||
|
|
||||||
|
const bytesNeeded = Math.ceil(bitsNeeded / 8);
|
||||||
|
const mask = Math.pow(2, bitsNeeded) - 1;
|
||||||
|
// 7776 -> (2^13 = 8192) -1 == 8191 or 0x00001111 11111111
|
||||||
|
|
||||||
|
// Create byte array and fill with N random numbers
|
||||||
|
const byteArray = new Uint8Array(bytesNeeded);
|
||||||
|
window.crypto.getRandomValues(byteArray);
|
||||||
|
|
||||||
|
let p = (bytesNeeded - 1) * 8;
|
||||||
|
for (let i = 0; i < bytesNeeded; i++) {
|
||||||
|
rval += byteArray[i] * Math.pow(2, p);
|
||||||
|
p -= 8;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use & to apply the mask and reduce the number of recursive lookups
|
||||||
|
// tslint:disable-next-line
|
||||||
|
rval = rval & mask;
|
||||||
|
|
||||||
|
if (rval >= range) {
|
||||||
|
// Integer out of acceptable range
|
||||||
|
return UtilsService.secureRandomNumber(min, max);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return an integer that falls within the range
|
||||||
|
return min + rval;
|
||||||
|
}
|
||||||
|
|
||||||
static fromB64ToArray(str: string): Uint8Array {
|
static fromB64ToArray(str: string): Uint8Array {
|
||||||
const binaryString = window.atob(str);
|
const binaryString = window.atob(str);
|
||||||
const bytes = new Uint8Array(binaryString.length);
|
const bytes = new Uint8Array(binaryString.length);
|
||||||
|
Loading…
Reference in New Issue
Block a user