From 8797924bd1496671170e723e355640dc707f170d Mon Sep 17 00:00:00 2001 From: Thomas Rittson <31796059+eliykat@users.noreply.github.com> Date: Wed, 9 Jun 2021 14:24:31 -0700 Subject: [PATCH] Use 2 iterations for local password hashing (#404) * Use 2 iterations for local password hashing * fix typo --- angular/src/components/export.component.ts | 4 ++- angular/src/components/lock.component.ts | 15 ++++++--- .../src/components/set-password.component.ts | 6 +++- common/src/abstractions/crypto.service.ts | 3 +- common/src/enums/hashPurpose.ts | 4 +++ common/src/services/auth.service.ts | 32 ++++++++++++------- common/src/services/crypto.service.ts | 6 ++-- .../src/services/passwordReprompt.service.ts | 4 ++- 8 files changed, 51 insertions(+), 23 deletions(-) create mode 100644 common/src/enums/hashPurpose.ts diff --git a/angular/src/components/export.component.ts b/angular/src/components/export.component.ts index 731c2c3e5b..5ea15fb358 100644 --- a/angular/src/components/export.component.ts +++ b/angular/src/components/export.component.ts @@ -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 { diff --git a/angular/src/components/lock.component.ts b/angular/src/components/lock.component.ts index 48ec2eb022..597bbca965 100644 --- a/angular/src/components/lock.component.ts +++ b/angular/src/components/lock.component.ts @@ -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 { } } } diff --git a/angular/src/components/set-password.component.ts b/angular/src/components/set-password.component.ts index 5d5eee7b41..4bcd4675cf 100644 --- a/angular/src/components/set-password.component.ts +++ b/angular/src/components/set-password.component.ts @@ -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 { diff --git a/common/src/abstractions/crypto.service.ts b/common/src/abstractions/crypto.service.ts index 8172a7c983..6ebedd3b2c 100644 --- a/common/src/abstractions/crypto.service.ts +++ b/common/src/abstractions/crypto.service.ts @@ -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; makeSendKey: (keyMaterial: ArrayBuffer) => Promise; - hashPassword: (password: string, key: SymmetricCryptoKey) => Promise; + hashPassword: (password: string, key: SymmetricCryptoKey, hashPurpose?: HashPurpose) => Promise; makeEncKey: (key: SymmetricCryptoKey) => Promise<[SymmetricCryptoKey, EncString]>; remakeEncKey: (key: SymmetricCryptoKey, encKey?: SymmetricCryptoKey) => Promise<[SymmetricCryptoKey, EncString]>; encrypt: (plainValue: string | ArrayBuffer, key?: SymmetricCryptoKey) => Promise; diff --git a/common/src/enums/hashPurpose.ts b/common/src/enums/hashPurpose.ts new file mode 100644 index 0000000000..90a1db9d9e --- /dev/null +++ b/common/src/enums/hashPurpose.ts @@ -0,0 +1,4 @@ +export enum HashPurpose { + ServerAuthorization = 1, + LocalAuthorization = 2, +} diff --git a/common/src/services/auth.service.ts b/common/src/services/auth.service.ts index 2a60670bdc..6536a94c3d 100644 --- a/common/src/services/auth.service.ts +++ b/common/src/services/auth.service.ts @@ -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 { 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 { 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 { - 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 { 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 { 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 { 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; diff --git a/common/src/services/crypto.service.ts b/common/src/services/crypto.service.ts index 7d1b94a4f3..fd3c129bb1 100644 --- a/common/src/services/crypto.service.ts +++ b/common/src/services/crypto.service.ts @@ -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 { + async hashPassword(password: string, key: SymmetricCryptoKey, hashPurpose?: HashPurpose): Promise { 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); } diff --git a/common/src/services/passwordReprompt.service.ts b/common/src/services/passwordReprompt.service.ts index f09727c9a5..d5955f79f0 100644 --- a/common/src/services/passwordReprompt.service.ts +++ b/common/src/services/passwordReprompt.service.ts @@ -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) {