1
0
mirror of https://github.com/bitwarden/browser.git synced 2024-11-27 12:36:14 +01:00

secure storage with env session key

This commit is contained in:
Kyle Spearrin 2018-05-15 21:11:58 -04:00
parent e8a3325ec9
commit 807cca2bd2
6 changed files with 109 additions and 19 deletions

2
jslib

@ -1 +1 @@
Subproject commit f173001a411acb39c0d2ebb11d17ea90d70ec784
Subproject commit 7112911cb89bcf9bf9b9de32cb05ec2d3f78e3fc

View File

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

View File

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

View File

@ -34,7 +34,8 @@ export class Program {
.option('-m, --method <method>', 'Two-step login method.')
.option('-c, --code <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);
});

View File

@ -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<T>(key: string): Promise<T> {
const value = await this.storageService.get<string>(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<any> {
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<any> {
return this.storageService.remove(this.makeProtectedStorageKey(key));
}
private async encrypt(plainValue: string): Promise<string> {
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<string> {
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;
}
}

View File

@ -1,15 +0,0 @@
import { StorageService } from 'jslib/abstractions/storage.service';
export class NodeSecureStorageService implements StorageService {
get<T>(key: string): Promise<T> {
return Promise.resolve(null);
}
save(key: string, obj: any): Promise<any> {
return Promise.resolve();
}
remove(key: string): Promise<any> {
return Promise.resolve();
}
}