soft locking with protected pin

This commit is contained in:
Kyle Spearrin 2019-02-13 21:36:36 -05:00
parent 76c53bc641
commit 0bdbfd7984
6 changed files with 79 additions and 20 deletions

View File

@ -1,6 +1,9 @@
export abstract class LockService { export abstract class LockService {
pinLocked: boolean;
isLocked: () => Promise<boolean>;
checkLock: () => Promise<void>; checkLock: () => Promise<void>;
lock: () => Promise<void>; lock: (allowSoftLock?: boolean) => Promise<void>;
setLockOption: (lockOption: number) => Promise<void>; setLockOption: (lockOption: number) => Promise<void>;
isPinLockSet: () => Promise<boolean>; isPinLockSet: () => Promise<[boolean, boolean]>;
clear: () => Promise<any>;
} }

View File

@ -25,6 +25,7 @@ export class LockComponent implements OnInit {
protected onSuccessfulSubmit: () => void; protected onSuccessfulSubmit: () => void;
private invalidPinAttempts = 0; private invalidPinAttempts = 0;
private pinSet: [boolean, boolean];
constructor(protected router: Router, protected i18nService: I18nService, constructor(protected router: Router, protected i18nService: I18nService,
protected platformUtilsService: PlatformUtilsService, protected messagingService: MessagingService, protected platformUtilsService: PlatformUtilsService, protected messagingService: MessagingService,
@ -32,7 +33,9 @@ export class LockComponent implements OnInit {
protected storageService: StorageService, protected lockService: LockService) { } protected storageService: StorageService, protected lockService: LockService) { }
async ngOnInit() { async ngOnInit() {
this.pinLock = await this.lockService.isPinLockSet(); this.pinSet = await this.lockService.isPinLockSet();
const hasKey = await this.cryptoService.hasKey();
this.pinLock = (this.pinSet[0] && hasKey) || this.pinSet[1];
this.email = await this.userService.getEmail(); this.email = await this.userService.getEmail();
} }
@ -52,13 +55,25 @@ export class LockComponent implements OnInit {
const kdfIterations = await this.userService.getKdfIterations(); const kdfIterations = await this.userService.getKdfIterations();
if (this.pinLock) { if (this.pinLock) {
const pinProtectedKey = await this.storageService.get<string>(ConstantsService.pinProtectedKey); let failed = true;
try { try {
const protectedKeyCs = new CipherString(pinProtectedKey); if (this.pinSet[0]) {
const pinKey = await this.cryptoService.makePinKey(this.pin, this.email, kdf, kdfIterations); const protectedPin = await this.storageService.get<string>(ConstantsService.protectedPin);
const decKey = await this.cryptoService.decryptToBytes(protectedKeyCs, pinKey); const decPin = await this.cryptoService.decryptToUtf8(new CipherString(protectedPin));
await this.setKeyAndContinue(new SymmetricCryptoKey(decKey)); this.lockService.pinLocked = false;
} catch { failed = decPin !== this.pin;
this.doContinue();
} else {
const pinProtectedKey = await this.storageService.get<string>(ConstantsService.pinProtectedKey);
const protectedKeyCs = new CipherString(pinProtectedKey);
const pinKey = await this.cryptoService.makePinKey(this.pin, this.email, kdf, kdfIterations);
const decKey = await this.cryptoService.decryptToBytes(protectedKeyCs, pinKey);
failed = false;
await this.setKeyAndContinue(new SymmetricCryptoKey(decKey));
}
} catch { }
if (failed) {
this.invalidPinAttempts++; this.invalidPinAttempts++;
if (this.invalidPinAttempts >= 5) { if (this.invalidPinAttempts >= 5) {
this.messagingService.send('logout'); this.messagingService.send('logout');
@ -97,6 +112,10 @@ export class LockComponent implements OnInit {
private async setKeyAndContinue(key: SymmetricCryptoKey) { private async setKeyAndContinue(key: SymmetricCryptoKey) {
await this.cryptoService.setKey(key); await this.cryptoService.setKey(key);
this.doContinue();
}
private doContinue() {
this.messagingService.send('unlocked'); this.messagingService.send('unlocked');
if (this.onSuccessfulSubmit != null) { if (this.onSuccessfulSubmit != null) {
this.onSuccessfulSubmit(); this.onSuccessfulSubmit();

View File

@ -4,13 +4,13 @@ import {
Router, Router,
} from '@angular/router'; } from '@angular/router';
import { CryptoService } from '../../abstractions/crypto.service'; import { LockService } from '../../abstractions/lock.service';
import { MessagingService } from '../../abstractions/messaging.service'; import { MessagingService } from '../../abstractions/messaging.service';
import { UserService } from '../../abstractions/user.service'; import { UserService } from '../../abstractions/user.service';
@Injectable() @Injectable()
export class AuthGuardService implements CanActivate { export class AuthGuardService implements CanActivate {
constructor(private cryptoService: CryptoService, private userService: UserService, private router: Router, constructor(private lockService: LockService, private userService: UserService, private router: Router,
private messagingService: MessagingService) { } private messagingService: MessagingService) { }
async canActivate() { async canActivate() {
@ -20,8 +20,8 @@ export class AuthGuardService implements CanActivate {
return false; return false;
} }
const hasKey = await this.cryptoService.hasKey(); const locked = await this.lockService.isLocked();
if (!hasKey) { if (locked) {
this.router.navigate(['lock']); this.router.navigate(['lock']);
return false; return false;
} }

View File

@ -19,6 +19,7 @@ export class ConstantsService {
static readonly dontShowIdentitiesCurrentTab: string = 'dontShowIdentitiesCurrentTab'; static readonly dontShowIdentitiesCurrentTab: string = 'dontShowIdentitiesCurrentTab';
static readonly defaultUriMatch: string = 'defaultUriMatch'; static readonly defaultUriMatch: string = 'defaultUriMatch';
static readonly pinProtectedKey: string = 'pinProtectedKey'; static readonly pinProtectedKey: string = 'pinProtectedKey';
static readonly protectedPin: string = 'protectedPin';
readonly environmentUrlsKey: string = ConstantsService.environmentUrlsKey; readonly environmentUrlsKey: string = ConstantsService.environmentUrlsKey;
readonly disableGaKey: string = ConstantsService.disableGaKey; readonly disableGaKey: string = ConstantsService.disableGaKey;
@ -39,4 +40,5 @@ export class ConstantsService {
readonly dontShowIdentitiesCurrentTab: string = ConstantsService.dontShowIdentitiesCurrentTab; readonly dontShowIdentitiesCurrentTab: string = ConstantsService.dontShowIdentitiesCurrentTab;
readonly defaultUriMatch: string = ConstantsService.defaultUriMatch; readonly defaultUriMatch: string = ConstantsService.defaultUriMatch;
readonly pinProtectedKey: string = ConstantsService.pinProtectedKey; readonly pinProtectedKey: string = ConstantsService.pinProtectedKey;
readonly protectedPin: string = ConstantsService.protectedPin;
} }

View File

@ -11,6 +11,8 @@ import { SearchService } from '../abstractions/search.service';
import { StorageService } from '../abstractions/storage.service'; import { StorageService } from '../abstractions/storage.service';
export class LockService implements LockServiceAbstraction { export class LockService implements LockServiceAbstraction {
pinLocked = false;
private inited = false; private inited = false;
constructor(private cipherService: CipherService, private folderService: FolderService, constructor(private cipherService: CipherService, private folderService: FolderService,
@ -32,12 +34,24 @@ export class LockService implements LockServiceAbstraction {
} }
} }
async isLocked(): Promise<boolean> {
if (this.pinLocked) {
return true;
}
const hasKey = await this.cryptoService.hasKey();
return !hasKey;
}
async checkLock(): Promise<void> { async checkLock(): Promise<void> {
if (this.platformUtilsService.isViewOpen()) { if (this.platformUtilsService.isViewOpen()) {
// Do not lock // Do not lock
return; return;
} }
if (this.pinLocked) {
return;
}
const hasKey = await this.cryptoService.hasKey(); const hasKey = await this.cryptoService.hasKey();
if (!hasKey) { if (!hasKey) {
// no key so no need to lock // no key so no need to lock
@ -61,11 +75,19 @@ export class LockService implements LockServiceAbstraction {
const diffSeconds = ((new Date()).getTime() - lastActive) / 1000; const diffSeconds = ((new Date()).getTime() - lastActive) / 1000;
if (diffSeconds >= lockOptionSeconds) { if (diffSeconds >= lockOptionSeconds) {
// need to lock now // need to lock now
await this.lock(); await this.lock(true);
} }
} }
async lock(): Promise<void> { async lock(allowSoftLock = false): Promise<void> {
if (allowSoftLock) {
const pinSet = await this.isPinLockSet();
if (pinSet[0]) {
await this.pinLock();
return;
}
}
await Promise.all([ await Promise.all([
this.cryptoService.clearKey(), this.cryptoService.clearKey(),
this.cryptoService.clearOrgKeys(true), this.cryptoService.clearOrgKeys(true),
@ -88,8 +110,21 @@ export class LockService implements LockServiceAbstraction {
await this.cryptoService.toggleKey(); await this.cryptoService.toggleKey();
} }
async isPinLockSet(): Promise<boolean> { async isPinLockSet(): Promise<[boolean, boolean]> {
const protectedPin = await this.storageService.get<string>(ConstantsService.protectedPin);
const pinProtectedKey = await this.storageService.get<string>(ConstantsService.pinProtectedKey); const pinProtectedKey = await this.storageService.get<string>(ConstantsService.pinProtectedKey);
return pinProtectedKey != null; return [protectedPin != null, pinProtectedKey != null];
}
clear(): Promise<any> {
return this.storageService.remove(ConstantsService.protectedPin);
}
private async pinLock(): Promise<void> {
this.pinLocked = true;
this.messagingService.send('locked');
if (this.lockedCallback != null) {
await this.lockedCallback();
}
} }
} }

View File

@ -5,8 +5,8 @@ import { NotificationType } from '../enums/notificationType';
import { ApiService } from '../abstractions/api.service'; import { ApiService } from '../abstractions/api.service';
import { AppIdService } from '../abstractions/appId.service'; import { AppIdService } from '../abstractions/appId.service';
import { CryptoService } from '../abstractions/crypto.service';
import { EnvironmentService } from '../abstractions/environment.service'; import { EnvironmentService } from '../abstractions/environment.service';
import { LockService } from '../abstractions/lock.service';
import { NotificationsService as NotificationsServiceAbstraction } from '../abstractions/notifications.service'; import { NotificationsService as NotificationsServiceAbstraction } from '../abstractions/notifications.service';
import { SyncService } from '../abstractions/sync.service'; import { SyncService } from '../abstractions/sync.service';
import { UserService } from '../abstractions/user.service'; import { UserService } from '../abstractions/user.service';
@ -27,7 +27,7 @@ export class NotificationsService implements NotificationsServiceAbstraction {
constructor(private userService: UserService, private syncService: SyncService, constructor(private userService: UserService, private syncService: SyncService,
private appIdService: AppIdService, private apiService: ApiService, private appIdService: AppIdService, private apiService: ApiService,
private cryptoService: CryptoService, private logoutCallback: () => Promise<void>) { } private lockService: LockService, private logoutCallback: () => Promise<void>) { }
async init(environmentService: EnvironmentService): Promise<void> { async init(environmentService: EnvironmentService): Promise<void> {
this.inited = false; this.inited = false;
@ -185,7 +185,7 @@ export class NotificationsService implements NotificationsServiceAbstraction {
private async isAuthedAndUnlocked() { private async isAuthedAndUnlocked() {
if (await this.userService.isAuthenticated()) { if (await this.userService.isAuthenticated()) {
return this.cryptoService.hasKey(); return this.lockService.isLocked();
} }
return false; return false;
} }