mirror of
https://github.com/bitwarden/browser.git
synced 2025-02-24 02:41:54 +01:00
Use 2 iterations for local password hashing (#404)
* Use 2 iterations for local password hashing * fix typo
This commit is contained in:
parent
5ba1416679
commit
8797924bd1
@ -9,7 +9,9 @@ import { EventService } from 'jslib-common/abstractions/event.service';
|
||||
import { ExportService } from 'jslib-common/abstractions/export.service';
|
||||
import { I18nService } from 'jslib-common/abstractions/i18n.service';
|
||||
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
|
||||
|
||||
import { EventType } from 'jslib-common/enums/eventType';
|
||||
import { HashPurpose } from 'jslib-common/enums/hashPurpose';
|
||||
|
||||
@Directive()
|
||||
export class ExportComponent {
|
||||
@ -40,7 +42,7 @@ export class ExportComponent {
|
||||
return;
|
||||
}
|
||||
|
||||
const keyHash = await this.cryptoService.hashPassword(this.masterPassword, null);
|
||||
const keyHash = await this.cryptoService.hashPassword(this.masterPassword, null, HashPurpose.LocalAuthorization);
|
||||
const storedKeyHash = await this.cryptoService.getKeyHash();
|
||||
if (storedKeyHash != null && keyHash != null && storedKeyHash === keyHash) {
|
||||
try {
|
||||
|
@ -21,6 +21,8 @@ import { PasswordVerificationRequest } from 'jslib-common/models/request/passwor
|
||||
|
||||
import { Utils } from 'jslib-common/misc/utils';
|
||||
|
||||
import { HashPurpose } from 'jslib-common/enums/hashPurpose';
|
||||
|
||||
@Directive()
|
||||
export class LockComponent implements OnInit {
|
||||
masterPassword: string = '';
|
||||
@ -110,22 +112,25 @@ export class LockComponent implements OnInit {
|
||||
}
|
||||
} else {
|
||||
const key = await this.cryptoService.makeKey(this.masterPassword, this.email, kdf, kdfIterations);
|
||||
const keyHash = await this.cryptoService.hashPassword(this.masterPassword, key);
|
||||
const localKeyHash = await this.cryptoService.hashPassword(this.masterPassword, key,
|
||||
HashPurpose.LocalAuthorization);
|
||||
|
||||
let passwordValid = false;
|
||||
|
||||
if (keyHash != null) {
|
||||
if (localKeyHash != null) {
|
||||
const storedKeyHash = await this.cryptoService.getKeyHash();
|
||||
if (storedKeyHash != null) {
|
||||
passwordValid = storedKeyHash === keyHash;
|
||||
passwordValid = storedKeyHash === localKeyHash;
|
||||
} else {
|
||||
const request = new PasswordVerificationRequest();
|
||||
request.masterPasswordHash = keyHash;
|
||||
const serverKeyHash = await this.cryptoService.hashPassword(this.masterPassword, key,
|
||||
HashPurpose.ServerAuthorization);
|
||||
request.masterPasswordHash = serverKeyHash;
|
||||
try {
|
||||
this.formPromise = this.apiService.postAccountVerifyPassword(request);
|
||||
await this.formPromise;
|
||||
passwordValid = true;
|
||||
await this.cryptoService.setKeyHash(keyHash);
|
||||
await this.cryptoService.setKeyHash(localKeyHash);
|
||||
} catch { }
|
||||
}
|
||||
}
|
||||
|
@ -22,6 +22,7 @@ import { SetPasswordRequest } from 'jslib-common/models/request/setPasswordReque
|
||||
|
||||
import { ChangePasswordComponent as BaseChangePasswordComponent } from './change-password.component';
|
||||
|
||||
import { HashPurpose } from 'jslib-common/enums/hashPurpose';
|
||||
import { KdfType } from 'jslib-common/enums/kdfType';
|
||||
|
||||
@Directive()
|
||||
@ -86,10 +87,13 @@ export class SetPasswordComponent extends BaseChangePasswordComponent {
|
||||
await this.userService.setInformation(await this.userService.getUserId(), await this.userService.getEmail(),
|
||||
this.kdf, this.kdfIterations);
|
||||
await this.cryptoService.setKey(key);
|
||||
await this.cryptoService.setKeyHash(masterPasswordHash);
|
||||
await this.cryptoService.setEncKey(encKey[1].encryptedString);
|
||||
await this.cryptoService.setEncPrivateKey(keys[1].encryptedString);
|
||||
|
||||
const localKeyHash = await this.cryptoService.hashPassword(this.masterPassword, key,
|
||||
HashPurpose.LocalAuthorization);
|
||||
await this.cryptoService.setKeyHash(localKeyHash);
|
||||
|
||||
if (this.onSuccessfulChangePassword != null) {
|
||||
this.onSuccessfulChangePassword();
|
||||
} else {
|
||||
|
@ -4,6 +4,7 @@ import { SymmetricCryptoKey } from '../models/domain/symmetricCryptoKey';
|
||||
|
||||
import { ProfileOrganizationResponse } from '../models/response/profileOrganizationResponse';
|
||||
|
||||
import { HashPurpose } from '../enums/hashPurpose';
|
||||
import { KdfType } from '../enums/kdfType';
|
||||
import { KeySuffixOptions } from './storage.service';
|
||||
|
||||
@ -40,7 +41,7 @@ export abstract class CryptoService {
|
||||
makeKeyPair: (key?: SymmetricCryptoKey) => Promise<[string, EncString]>;
|
||||
makePinKey: (pin: string, salt: string, kdf: KdfType, kdfIterations: number) => Promise<SymmetricCryptoKey>;
|
||||
makeSendKey: (keyMaterial: ArrayBuffer) => Promise<SymmetricCryptoKey>;
|
||||
hashPassword: (password: string, key: SymmetricCryptoKey) => Promise<string>;
|
||||
hashPassword: (password: string, key: SymmetricCryptoKey, hashPurpose?: HashPurpose) => Promise<string>;
|
||||
makeEncKey: (key: SymmetricCryptoKey) => Promise<[SymmetricCryptoKey, EncString]>;
|
||||
remakeEncKey: (key: SymmetricCryptoKey, encKey?: SymmetricCryptoKey) => Promise<[SymmetricCryptoKey, EncString]>;
|
||||
encrypt: (plainValue: string | ArrayBuffer, key?: SymmetricCryptoKey) => Promise<EncString>;
|
||||
|
4
common/src/enums/hashPurpose.ts
Normal file
4
common/src/enums/hashPurpose.ts
Normal file
@ -0,0 +1,4 @@
|
||||
export enum HashPurpose {
|
||||
ServerAuthorization = 1,
|
||||
LocalAuthorization = 2,
|
||||
}
|
@ -1,3 +1,4 @@
|
||||
import { HashPurpose } from '../enums/hashPurpose';
|
||||
import { KdfType } from '../enums/kdfType';
|
||||
import { TwoFactorProviderType } from '../enums/twoFactorProviderType';
|
||||
|
||||
@ -78,6 +79,7 @@ export const TwoFactorProviders = {
|
||||
export class AuthService implements AuthServiceAbstraction {
|
||||
email: string;
|
||||
masterPasswordHash: string;
|
||||
localMasterPasswordHash: string;
|
||||
code: string;
|
||||
codeVerifier: string;
|
||||
ssoRedirectUrl: string;
|
||||
@ -122,26 +124,28 @@ export class AuthService implements AuthServiceAbstraction {
|
||||
this.selectedTwoFactorProviderType = null;
|
||||
const key = await this.makePreloginKey(masterPassword, email);
|
||||
const hashedPassword = await this.cryptoService.hashPassword(masterPassword, key);
|
||||
return await this.logInHelper(email, hashedPassword, null, null, null, null, null,
|
||||
const localHashedPassword = await this.cryptoService.hashPassword(masterPassword, key,
|
||||
HashPurpose.LocalAuthorization);
|
||||
return await this.logInHelper(email, hashedPassword, localHashedPassword, null, null, null, null, null,
|
||||
key, null, null, null);
|
||||
}
|
||||
|
||||
async logInSso(code: string, codeVerifier: string, redirectUrl: string): Promise<AuthResult> {
|
||||
this.selectedTwoFactorProviderType = null;
|
||||
return await this.logInHelper(null, null, code, codeVerifier, redirectUrl, null, null,
|
||||
return await this.logInHelper(null, null, null, code, codeVerifier, redirectUrl, null, null,
|
||||
null, null, null, null);
|
||||
}
|
||||
|
||||
async logInApiKey(clientId: string, clientSecret: string): Promise<AuthResult> {
|
||||
this.selectedTwoFactorProviderType = null;
|
||||
return await this.logInHelper(null, null, null, null, null, clientId, clientSecret,
|
||||
return await this.logInHelper(null, null, null, null, null, null, clientId, clientSecret,
|
||||
null, null, null, null);
|
||||
}
|
||||
|
||||
async logInTwoFactor(twoFactorProvider: TwoFactorProviderType, twoFactorToken: string,
|
||||
remember?: boolean): Promise<AuthResult> {
|
||||
return await this.logInHelper(this.email, this.masterPasswordHash, this.code, this.codeVerifier,
|
||||
this.ssoRedirectUrl, this.clientId, this.clientSecret, this.key, twoFactorProvider,
|
||||
return await this.logInHelper(this.email, this.masterPasswordHash, this.localMasterPasswordHash, this.code,
|
||||
this.codeVerifier, this.ssoRedirectUrl, this.clientId, this.clientSecret, this.key, twoFactorProvider,
|
||||
twoFactorToken, remember);
|
||||
}
|
||||
|
||||
@ -150,21 +154,23 @@ export class AuthService implements AuthServiceAbstraction {
|
||||
this.selectedTwoFactorProviderType = null;
|
||||
const key = await this.makePreloginKey(masterPassword, email);
|
||||
const hashedPassword = await this.cryptoService.hashPassword(masterPassword, key);
|
||||
return await this.logInHelper(email, hashedPassword, null, null, null, null, null, key,
|
||||
const localHashedPassword = await this.cryptoService.hashPassword(masterPassword, key,
|
||||
HashPurpose.LocalAuthorization);
|
||||
return await this.logInHelper(email, hashedPassword, localHashedPassword, null, null, null, null, null, key,
|
||||
twoFactorProvider, twoFactorToken, remember);
|
||||
}
|
||||
|
||||
async logInSsoComplete(code: string, codeVerifier: string, redirectUrl: string,
|
||||
twoFactorProvider: TwoFactorProviderType, twoFactorToken: string, remember?: boolean): Promise<AuthResult> {
|
||||
this.selectedTwoFactorProviderType = null;
|
||||
return await this.logInHelper(null, null, code, codeVerifier, redirectUrl, null,
|
||||
return await this.logInHelper(null, null, null, code, codeVerifier, redirectUrl, null,
|
||||
null, null, twoFactorProvider, twoFactorToken, remember);
|
||||
}
|
||||
|
||||
async logInApiKeyComplete(clientId: string, clientSecret: string, twoFactorProvider: TwoFactorProviderType,
|
||||
twoFactorToken: string, remember?: boolean): Promise<AuthResult> {
|
||||
this.selectedTwoFactorProviderType = null;
|
||||
return await this.logInHelper(null, null, null, null, null, clientId, clientSecret, null,
|
||||
return await this.logInHelper(null, null, null, null, null, null, clientId, clientSecret, null,
|
||||
twoFactorProvider, twoFactorToken, remember);
|
||||
}
|
||||
|
||||
@ -264,8 +270,8 @@ export class AuthService implements AuthServiceAbstraction {
|
||||
return this.email != null && this.masterPasswordHash != null;
|
||||
}
|
||||
|
||||
private async logInHelper(email: string, hashedPassword: string, code: string, codeVerifier: string,
|
||||
redirectUrl: string, clientId: string, clientSecret: string, key: SymmetricCryptoKey,
|
||||
private async logInHelper(email: string, hashedPassword: string, localHashedPassword: string, code: string,
|
||||
codeVerifier: string, redirectUrl: string, clientId: string, clientSecret: string, key: SymmetricCryptoKey,
|
||||
twoFactorProvider?: TwoFactorProviderType, twoFactorToken?: string, remember?: boolean): Promise<AuthResult> {
|
||||
const storedTwoFactorToken = await this.tokenService.getTwoFactorToken(email);
|
||||
const appId = await this.appIdService.getAppId();
|
||||
@ -314,6 +320,7 @@ export class AuthService implements AuthServiceAbstraction {
|
||||
const twoFactorResponse = response as IdentityTwoFactorResponse;
|
||||
this.email = email;
|
||||
this.masterPasswordHash = hashedPassword;
|
||||
this.localMasterPasswordHash = localHashedPassword;
|
||||
this.code = code;
|
||||
this.codeVerifier = codeVerifier;
|
||||
this.ssoRedirectUrl = redirectUrl;
|
||||
@ -338,8 +345,8 @@ export class AuthService implements AuthServiceAbstraction {
|
||||
if (key != null) {
|
||||
await this.cryptoService.setKey(key);
|
||||
}
|
||||
if (hashedPassword != null) {
|
||||
await this.cryptoService.setKeyHash(hashedPassword);
|
||||
if (localHashedPassword != null) {
|
||||
await this.cryptoService.setKeyHash(localHashedPassword);
|
||||
}
|
||||
|
||||
// Skip this step during SSO new user flow. No key is returned from server.
|
||||
@ -373,6 +380,7 @@ export class AuthService implements AuthServiceAbstraction {
|
||||
this.key = null;
|
||||
this.email = null;
|
||||
this.masterPasswordHash = null;
|
||||
this.localMasterPasswordHash = null;
|
||||
this.code = null;
|
||||
this.codeVerifier = null;
|
||||
this.ssoRedirectUrl = null;
|
||||
|
@ -1,6 +1,7 @@
|
||||
import * as bigInt from 'big-integer';
|
||||
|
||||
import { EncryptionType } from '../enums/encryptionType';
|
||||
import { HashPurpose } from '../enums/hashPurpose';
|
||||
import { KdfType } from '../enums/kdfType';
|
||||
|
||||
import { EncArrayBuffer } from '../models/domain/encArrayBuffer';
|
||||
@ -384,7 +385,7 @@ export class CryptoService implements CryptoServiceAbstraction {
|
||||
return new SymmetricCryptoKey(sendKey);
|
||||
}
|
||||
|
||||
async hashPassword(password: string, key: SymmetricCryptoKey): Promise<string> {
|
||||
async hashPassword(password: string, key: SymmetricCryptoKey, hashPurpose?: HashPurpose): Promise<string> {
|
||||
if (key == null) {
|
||||
key = await this.getKey();
|
||||
}
|
||||
@ -392,7 +393,8 @@ export class CryptoService implements CryptoServiceAbstraction {
|
||||
throw new Error('Invalid parameters.');
|
||||
}
|
||||
|
||||
const hash = await this.cryptoFunctionService.pbkdf2(key.key, password, 'sha256', 1);
|
||||
const iterations = hashPurpose === HashPurpose.LocalAuthorization ? 2 : 1;
|
||||
const hash = await this.cryptoFunctionService.pbkdf2(key.key, password, 'sha256', iterations);
|
||||
return Utils.fromBufferToB64(hash);
|
||||
}
|
||||
|
||||
|
@ -4,6 +4,8 @@ import { CryptoService } from '../abstractions/crypto.service';
|
||||
import { I18nService } from '../abstractions/i18n.service';
|
||||
import { PasswordRepromptService as PasswordRepromptServiceAbstraction } from '../abstractions/passwordReprompt.service';
|
||||
|
||||
import { HashPurpose } from '../enums/hashPurpose';
|
||||
|
||||
export class PasswordRepromptService implements PasswordRepromptServiceAbstraction {
|
||||
constructor(private i18nService: I18nService, private cryptoService: CryptoService,
|
||||
private platformUtilService: PlatformUtilsService) { }
|
||||
@ -14,7 +16,7 @@ export class PasswordRepromptService implements PasswordRepromptServiceAbstracti
|
||||
|
||||
async showPasswordPrompt() {
|
||||
const passwordValidator = async (value: string) => {
|
||||
const keyHash = await this.cryptoService.hashPassword(value, null);
|
||||
const keyHash = await this.cryptoService.hashPassword(value, null, HashPurpose.LocalAuthorization);
|
||||
const storedKeyHash = await this.cryptoService.getKeyHash();
|
||||
|
||||
if (storedKeyHash == null || keyHash == null || storedKeyHash !== keyHash) {
|
||||
|
Loading…
Reference in New Issue
Block a user