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