From 807cca2bd231fb2befe2803cac835c510767ea0f Mon Sep 17 00:00:00 2001 From: Kyle Spearrin Date: Tue, 15 May 2018 21:11:58 -0400 Subject: [PATCH] secure storage with env session key --- jslib | 2 +- src/bw.ts | 5 +- src/commands/login.command.ts | 12 ++- src/program.ts | 3 +- src/services/nodeEnvSecureStorage.service.ts | 91 ++++++++++++++++++++ src/services/nodeSecureStorage.service.ts | 15 ---- 6 files changed, 109 insertions(+), 19 deletions(-) create mode 100644 src/services/nodeEnvSecureStorage.service.ts delete mode 100644 src/services/nodeSecureStorage.service.ts diff --git a/jslib b/jslib index f173001a41..7112911cb8 160000 --- a/jslib +++ b/jslib @@ -1 +1 @@ -Subproject commit f173001a411acb39c0d2ebb11d17ea90d70ec784 +Subproject commit 7112911cb89bcf9bf9b9de32cb05ec2d3f78e3fc diff --git a/src/bw.ts b/src/bw.ts index 90e9809278..3754818737 100644 --- a/src/bw.ts +++ b/src/bw.ts @@ -1,6 +1,7 @@ import { AuthService } from 'jslib/services/auth.service'; import { I18nService } from './services/i18n.service'; +import { NodeEnvSecureStorageService } from './services/nodeEnvSecureStorage.service'; import { NodeMessagingService } from './services/nodeMessaging.service'; import { NodePlatformUtilsService } from './services/nodePlatformUtils.service'; import { NodeStorageService } from './services/nodeStorage.service'; @@ -58,7 +59,9 @@ export class Main { this.platformUtilsService = new NodePlatformUtilsService(); this.cryptoFunctionService = new NodeCryptoFunctionService(); this.storageService = new NodeStorageService('Bitwarden CLI'); - this.cryptoService = new CryptoService(this.storageService, this.storageService, this.cryptoFunctionService); + this.secureStorageService = new NodeEnvSecureStorageService(this.storageService, () => this.cryptoService); + this.cryptoService = new CryptoService(this.storageService, this.secureStorageService, + this.cryptoFunctionService); this.appIdService = new AppIdService(this.storageService); this.tokenService = new TokenService(this.storageService); this.messagingService = new NodeMessagingService(); diff --git a/src/commands/login.command.ts b/src/commands/login.command.ts index 6c6e6f96d2..3e8319b3fd 100644 --- a/src/commands/login.command.ts +++ b/src/commands/login.command.ts @@ -8,11 +8,15 @@ import { TwoFactorEmailRequest } from 'jslib/models/request/twoFactorEmailReques import { ApiService } from 'jslib/abstractions/api.service'; import { AuthService } from 'jslib/abstractions/auth.service'; +import { CryptoFunctionService } from 'jslib/abstractions/cryptoFunction.service'; import { Response } from '../models/response'; +import { Utils } from 'jslib/misc/utils'; + export class LoginCommand { - constructor(private authService: AuthService, private apiService: ApiService) { } + constructor(private authService: AuthService, private apiService: ApiService, + private cryptoFunctionService: CryptoFunctionService) { } async run(email: string, password: string, cmd: program.Command) { if (email == null || email === '') { @@ -46,6 +50,7 @@ export class LoginCommand { } try { + await this.setNewSessionKey(); let response: AuthResult = null; if (twoFactorToken != null && twoFactorMethod != null) { response = await this.authService.logInComplete(email, password, twoFactorMethod, @@ -106,4 +111,9 @@ export class LoginCommand { return Response.error(e); } } + + private async setNewSessionKey() { + const key = await this.cryptoFunctionService.randomBytes(64); + process.env.BW_SESSION = Utils.fromBufferToB64(key); + } } diff --git a/src/program.ts b/src/program.ts index 0506439ac7..700a7dfc4b 100644 --- a/src/program.ts +++ b/src/program.ts @@ -34,7 +34,8 @@ export class Program { .option('-m, --method ', 'Two-step login method.') .option('-c, --code ', 'Two-step login code.') .action(async (email: string, password: string, cmd: program.Command) => { - const command = new LoginCommand(this.main.authService, this.main.apiService); + const command = new LoginCommand(this.main.authService, this.main.apiService, + this.main.cryptoFunctionService); const response = await command.run(email, password, cmd); this.processResponse(response, cmd); }); diff --git a/src/services/nodeEnvSecureStorage.service.ts b/src/services/nodeEnvSecureStorage.service.ts new file mode 100644 index 0000000000..04a38537fe --- /dev/null +++ b/src/services/nodeEnvSecureStorage.service.ts @@ -0,0 +1,91 @@ +import { CryptoService } from 'jslib/abstractions/crypto.service'; +import { StorageService } from 'jslib/abstractions/storage.service'; +import { SymmetricCryptoKey } from 'jslib/models/domain'; +import { ErrorResponse } from 'jslib/models/response'; + +import { Utils } from 'jslib/misc/utils'; + +export class NodeEnvSecureStorageService implements StorageService { + constructor(private storageService: StorageService, private cryptoService: () => CryptoService) { } + + async get(key: string): Promise { + const value = await this.storageService.get(this.makeProtectedStorageKey(key)); + if (value == null) { + return null; + } + const obj = await this.decrypt(value); + return obj as any; + } + + async save(key: string, obj: any): Promise { + if (typeof (obj) !== 'string') { + throw new Error('Only string storage is allowed.'); + } + const protectedObj = await this.encrypt(obj); + await this.storageService.save(this.makeProtectedStorageKey(key), protectedObj); + } + + remove(key: string): Promise { + return this.storageService.remove(this.makeProtectedStorageKey(key)); + } + + private async encrypt(plainValue: string): Promise { + const sessionKey = this.getSessionKey(); + if (sessionKey == null) { + throw new Error('No session key available.'); + } + const encValue = await this.cryptoService().encryptToBytes( + Utils.fromB64ToArray(plainValue).buffer, sessionKey); + if (encValue == null) { + throw new Error('Value didn\'t encrypt.'); + } + + return Utils.fromBufferToB64(encValue); + } + + private async decrypt(encValue: string): Promise { + try { + const sessionKey = this.getSessionKey(); + if (sessionKey == null) { + return null; + } + + const decValue = await this.cryptoService().decryptFromBytes( + Utils.fromB64ToArray(encValue).buffer, sessionKey); + if (decValue == null) { + // tslint:disable-next-line + console.log('Failed to decrypt.'); + return null; + } + + return Utils.fromBufferToB64(decValue); + } catch (e) { + // tslint:disable-next-line + console.log('Decrypt error.'); + return null; + } + } + + private getSessionKey() { + try { + if (process.env.BW_SESSION != null) { + const sessionBuffer = Utils.fromB64ToArray(process.env.BW_SESSION).buffer; + if (sessionBuffer != null) { + const sessionKey = new SymmetricCryptoKey(sessionBuffer); + if (sessionBuffer != null) { + return sessionKey; + } + } + } + } catch (e) { + // tslint:disable-next-line + console.log('Session key is invalid.'); + } + + return null; + } + + private makeProtectedStorageKey(key: string) { + return '__PROTECTED__' + key; + } +} diff --git a/src/services/nodeSecureStorage.service.ts b/src/services/nodeSecureStorage.service.ts deleted file mode 100644 index 1b13a6cb02..0000000000 --- a/src/services/nodeSecureStorage.service.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { StorageService } from 'jslib/abstractions/storage.service'; - -export class NodeSecureStorageService implements StorageService { - get(key: string): Promise { - return Promise.resolve(null); - } - - save(key: string, obj: any): Promise { - return Promise.resolve(); - } - - remove(key: string): Promise { - return Promise.resolve(); - } -}