secure storage with env session key
This commit is contained in:
parent
e8a3325ec9
commit
807cca2bd2
2
jslib
2
jslib
|
@ -1 +1 @@
|
||||||
Subproject commit f173001a411acb39c0d2ebb11d17ea90d70ec784
|
Subproject commit 7112911cb89bcf9bf9b9de32cb05ec2d3f78e3fc
|
|
@ -1,6 +1,7 @@
|
||||||
import { AuthService } from 'jslib/services/auth.service';
|
import { AuthService } from 'jslib/services/auth.service';
|
||||||
|
|
||||||
import { I18nService } from './services/i18n.service';
|
import { I18nService } from './services/i18n.service';
|
||||||
|
import { NodeEnvSecureStorageService } from './services/nodeEnvSecureStorage.service';
|
||||||
import { NodeMessagingService } from './services/nodeMessaging.service';
|
import { NodeMessagingService } from './services/nodeMessaging.service';
|
||||||
import { NodePlatformUtilsService } from './services/nodePlatformUtils.service';
|
import { NodePlatformUtilsService } from './services/nodePlatformUtils.service';
|
||||||
import { NodeStorageService } from './services/nodeStorage.service';
|
import { NodeStorageService } from './services/nodeStorage.service';
|
||||||
|
@ -58,7 +59,9 @@ export class Main {
|
||||||
this.platformUtilsService = new NodePlatformUtilsService();
|
this.platformUtilsService = new NodePlatformUtilsService();
|
||||||
this.cryptoFunctionService = new NodeCryptoFunctionService();
|
this.cryptoFunctionService = new NodeCryptoFunctionService();
|
||||||
this.storageService = new NodeStorageService('Bitwarden CLI');
|
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.appIdService = new AppIdService(this.storageService);
|
||||||
this.tokenService = new TokenService(this.storageService);
|
this.tokenService = new TokenService(this.storageService);
|
||||||
this.messagingService = new NodeMessagingService();
|
this.messagingService = new NodeMessagingService();
|
||||||
|
|
|
@ -8,11 +8,15 @@ import { TwoFactorEmailRequest } from 'jslib/models/request/twoFactorEmailReques
|
||||||
|
|
||||||
import { ApiService } from 'jslib/abstractions/api.service';
|
import { ApiService } from 'jslib/abstractions/api.service';
|
||||||
import { AuthService } from 'jslib/abstractions/auth.service';
|
import { AuthService } from 'jslib/abstractions/auth.service';
|
||||||
|
import { CryptoFunctionService } from 'jslib/abstractions/cryptoFunction.service';
|
||||||
|
|
||||||
import { Response } from '../models/response';
|
import { Response } from '../models/response';
|
||||||
|
|
||||||
|
import { Utils } from 'jslib/misc/utils';
|
||||||
|
|
||||||
export class LoginCommand {
|
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) {
|
async run(email: string, password: string, cmd: program.Command) {
|
||||||
if (email == null || email === '') {
|
if (email == null || email === '') {
|
||||||
|
@ -46,6 +50,7 @@ export class LoginCommand {
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
await this.setNewSessionKey();
|
||||||
let response: AuthResult = null;
|
let response: AuthResult = null;
|
||||||
if (twoFactorToken != null && twoFactorMethod != null) {
|
if (twoFactorToken != null && twoFactorMethod != null) {
|
||||||
response = await this.authService.logInComplete(email, password, twoFactorMethod,
|
response = await this.authService.logInComplete(email, password, twoFactorMethod,
|
||||||
|
@ -106,4 +111,9 @@ export class LoginCommand {
|
||||||
return Response.error(e);
|
return Response.error(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async setNewSessionKey() {
|
||||||
|
const key = await this.cryptoFunctionService.randomBytes(64);
|
||||||
|
process.env.BW_SESSION = Utils.fromBufferToB64(key);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,7 +34,8 @@ export class Program {
|
||||||
.option('-m, --method <method>', 'Two-step login method.')
|
.option('-m, --method <method>', 'Two-step login method.')
|
||||||
.option('-c, --code <code>', 'Two-step login code.')
|
.option('-c, --code <code>', 'Two-step login code.')
|
||||||
.action(async (email: string, password: string, cmd: program.Command) => {
|
.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);
|
const response = await command.run(email, password, cmd);
|
||||||
this.processResponse(response, cmd);
|
this.processResponse(response, cmd);
|
||||||
});
|
});
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in New Issue