Use 2 iterations for local password hashing (#404)

* Use 2 iterations for local password hashing

* fix typo
This commit is contained in:
Thomas Rittson 2021-06-09 14:24:31 -07:00 committed by GitHub
parent 5ba1416679
commit 8797924bd1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 51 additions and 23 deletions

View File

@ -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 {

View File

@ -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 { }
}
}

View File

@ -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 {

View File

@ -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>;

View File

@ -0,0 +1,4 @@
export enum HashPurpose {
ServerAuthorization = 1,
LocalAuthorization = 2,
}

View File

@ -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;

View File

@ -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);
}

View File

@ -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) {