From 7a1e7b54743f065ee10eb27499186b629cce22b5 Mon Sep 17 00:00:00 2001 From: Kyle Spearrin Date: Tue, 12 Feb 2019 23:52:50 -0500 Subject: [PATCH] support for unlocking with PIN code --- src/abstractions/crypto.service.ts | 2 + src/abstractions/lock.service.ts | 1 + src/angular/components/lock.component.ts | 60 ++++++++++++++++++++---- src/services/constants.service.ts | 2 + src/services/crypto.service.ts | 10 ++++ src/services/lock.service.ts | 5 ++ 6 files changed, 72 insertions(+), 8 deletions(-) diff --git a/src/abstractions/crypto.service.ts b/src/abstractions/crypto.service.ts index 0d0d9b4701a..11eb21fcf14 100644 --- a/src/abstractions/crypto.service.ts +++ b/src/abstractions/crypto.service.ts @@ -26,11 +26,13 @@ export abstract class CryptoService { clearEncKey: (memoryOnly?: boolean) => Promise; clearKeyPair: (memoryOnly?: boolean) => Promise; clearOrgKeys: (memoryOnly?: boolean) => Promise; + clearPinProtectedKey: () => Promise; clearKeys: () => Promise; toggleKey: () => Promise; makeKey: (password: string, salt: string, kdf: KdfType, kdfIterations: number) => Promise; makeShareKey: () => Promise<[CipherString, SymmetricCryptoKey]>; makeKeyPair: (key?: SymmetricCryptoKey) => Promise<[string, CipherString]>; + makePinKey: (pin: string, salt: string) => Promise; hashPassword: (password: string, key: SymmetricCryptoKey) => Promise; makeEncKey: (key: SymmetricCryptoKey) => Promise<[SymmetricCryptoKey, CipherString]>; remakeEncKey: (key: SymmetricCryptoKey) => Promise<[SymmetricCryptoKey, CipherString]>; diff --git a/src/abstractions/lock.service.ts b/src/abstractions/lock.service.ts index 448a0a05b05..9c39c253517 100644 --- a/src/abstractions/lock.service.ts +++ b/src/abstractions/lock.service.ts @@ -2,4 +2,5 @@ export abstract class LockService { checkLock: () => Promise; lock: () => Promise; setLockOption: (lockOption: number) => Promise; + isPinLockSet: () => Promise; } diff --git a/src/angular/components/lock.component.ts b/src/angular/components/lock.component.ts index 8ebc938e01b..5dac6ea04a1 100644 --- a/src/angular/components/lock.component.ts +++ b/src/angular/components/lock.component.ts @@ -3,27 +3,67 @@ import { Router } from '@angular/router'; import { CryptoService } from '../../abstractions/crypto.service'; import { I18nService } from '../../abstractions/i18n.service'; +import { LockService } from '../../abstractions/lock.service'; import { MessagingService } from '../../abstractions/messaging.service'; import { PlatformUtilsService } from '../../abstractions/platformUtils.service'; +import { StorageService } from '../../abstractions/storage.service'; import { UserService } from '../../abstractions/user.service'; +import { ConstantsService } from '../../services/constants.service'; + +import { CipherString } from '../../models/domain/cipherString'; +import { SymmetricCryptoKey } from '../../models/domain/symmetricCryptoKey'; + export class LockComponent implements OnInit { masterPassword: string = ''; + pin: string = ''; showPassword: boolean = false; email: string; + pinLock: boolean = false; protected successRoute: string = 'vault'; protected onSuccessfulSubmit: () => void; + private invalidPinAttempts = 0; + constructor(protected router: Router, protected i18nService: I18nService, protected platformUtilsService: PlatformUtilsService, protected messagingService: MessagingService, - protected userService: UserService, protected cryptoService: CryptoService) { } + protected userService: UserService, protected cryptoService: CryptoService, + protected storageService: StorageService, protected lockService: LockService) { } async ngOnInit() { + this.pinLock = await this.lockService.isPinLockSet(); this.email = await this.userService.getEmail(); } async submit() { + // PIN + if (this.pinLock) { + if (this.pin == null || this.pin === '') { + this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'), + this.i18nService.t('pinRequired')); + return; + } + + const pinProtectedKey = await this.storageService.get(ConstantsService.pinProtectedKey); + try { + const protectedKeyCs = new CipherString(pinProtectedKey); + const pinKey = await this.cryptoService.makePinKey(this.pin, this.email); + const decKey = await this.cryptoService.decryptToBytes(protectedKeyCs, pinKey); + await this.setKeyAndContinue(new SymmetricCryptoKey(decKey)); + } catch { + this.invalidPinAttempts++; + if (this.invalidPinAttempts >= 5) { + this.messagingService.send('logout'); + return; + } + this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'), + this.i18nService.t('invalidPin')); + } + return; + } + + // Master Password if (this.masterPassword == null || this.masterPassword === '') { this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'), this.i18nService.t('masterPassRequired')); @@ -37,13 +77,7 @@ export class LockComponent implements OnInit { const storedKeyHash = await this.cryptoService.getKeyHash(); if (storedKeyHash != null && keyHash != null && storedKeyHash === keyHash) { - await this.cryptoService.setKey(key); - this.messagingService.send('unlocked'); - if (this.onSuccessfulSubmit != null) { - this.onSuccessfulSubmit(); - } else if (this.router != null) { - this.router.navigate([this.successRoute]); - } + this.setKeyAndContinue(key); } else { this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'), this.i18nService.t('invalidMasterPassword')); @@ -63,4 +97,14 @@ export class LockComponent implements OnInit { this.showPassword = !this.showPassword; document.getElementById('masterPassword').focus(); } + + private async setKeyAndContinue(key: SymmetricCryptoKey) { + await this.cryptoService.setKey(key); + this.messagingService.send('unlocked'); + if (this.onSuccessfulSubmit != null) { + this.onSuccessfulSubmit(); + } else if (this.router != null) { + this.router.navigate([this.successRoute]); + } + } } diff --git a/src/services/constants.service.ts b/src/services/constants.service.ts index dc6d0cf89db..41c5a1b3954 100644 --- a/src/services/constants.service.ts +++ b/src/services/constants.service.ts @@ -18,6 +18,7 @@ export class ConstantsService { static readonly dontShowCardsCurrentTab: string = 'dontShowCardsCurrentTab'; static readonly dontShowIdentitiesCurrentTab: string = 'dontShowIdentitiesCurrentTab'; static readonly defaultUriMatch: string = 'defaultUriMatch'; + static readonly pinProtectedKey: string = 'pinProtectedKey'; readonly environmentUrlsKey: string = ConstantsService.environmentUrlsKey; readonly disableGaKey: string = ConstantsService.disableGaKey; @@ -37,4 +38,5 @@ export class ConstantsService { readonly dontShowCardsCurrentTab: string = ConstantsService.dontShowCardsCurrentTab; readonly dontShowIdentitiesCurrentTab: string = ConstantsService.dontShowIdentitiesCurrentTab; readonly defaultUriMatch: string = ConstantsService.defaultUriMatch; + readonly pinProtectedKey: string = ConstantsService.pinProtectedKey; } diff --git a/src/services/crypto.service.ts b/src/services/crypto.service.ts index 8c5f68b0994..c9979e1d844 100644 --- a/src/services/crypto.service.ts +++ b/src/services/crypto.service.ts @@ -266,6 +266,10 @@ export class CryptoService implements CryptoServiceAbstraction { return this.storageService.remove(Keys.encOrgKeys); } + clearPinProtectedKey(): Promise { + return this.storageService.remove(ConstantsService.pinProtectedKey); + } + clearKeys(): Promise { return Promise.all([ this.clearKey(), @@ -273,6 +277,7 @@ export class CryptoService implements CryptoServiceAbstraction { this.clearOrgKeys(), this.clearEncKey(), this.clearKeyPair(), + this.clearPinProtectedKey(), ]); } @@ -319,6 +324,11 @@ export class CryptoService implements CryptoServiceAbstraction { return [publicB64, privateEnc]; } + async makePinKey(pin: string, salt: string): Promise { + const pinKey = await this.makeKey(pin, salt, KdfType.PBKDF2_SHA256, 100000); + return await this.stretchKey(pinKey); + } + async hashPassword(password: string, key: SymmetricCryptoKey): Promise { if (key == null) { key = await this.getKey(); diff --git a/src/services/lock.service.ts b/src/services/lock.service.ts index 194c45d1301..ff1c976ae66 100644 --- a/src/services/lock.service.ts +++ b/src/services/lock.service.ts @@ -87,4 +87,9 @@ export class LockService implements LockServiceAbstraction { await this.storageService.save(ConstantsService.lockOptionKey, lockOption); await this.cryptoService.toggleKey(); } + + async isPinLockSet(): Promise { + const pinProtectedKey = await this.storageService.get(ConstantsService.pinProtectedKey); + return pinProtectedKey != null; + } }