From 54d852f30823c59146d75d33f05f35f2c66a228f Mon Sep 17 00:00:00 2001 From: Sarah Vaupel Date: Sat, 1 Feb 2020 14:10:04 +0100 Subject: [PATCH] feat(storage-manager): add en-/decryption stub (WIP) and restructure --- .../lib/storage-manager/storage-manager.js | 101 ++++++++++++++---- 1 file changed, 82 insertions(+), 19 deletions(-) diff --git a/frontend/src/lib/storage-manager/storage-manager.js b/frontend/src/lib/storage-manager/storage-manager.js index 2f977b5ac..ccc880de6 100644 --- a/frontend/src/lib/storage-manager/storage-manager.js +++ b/frontend/src/lib/storage-manager/storage-manager.js @@ -1,6 +1,7 @@ /* global global:writable */ import * as semver from 'semver'; +import sodium from 'sodium-javascript'; import { HttpClient } from '../../services/http-client/http-client'; @@ -45,7 +46,7 @@ export class StorageManager { throw new Error('Cannot setup StorageManager without window or global'); if (this._options.encryption) { - Object.values(LOCATION).forEach((location) => { + [LOCATION.LOCAL, LOCATION.SESSION].forEach((location) => { const encryption = this._options.encryption.all || this._options.encryption[location]; if (encryption) this._requestStorageKey({ location: location, encryption: encryption }); }); @@ -65,15 +66,15 @@ export class StorageManager { switch (location) { case LOCATION.LOCAL: { - this._saveToLocalStorage({ ...this._getFromLocalStorage(), [key]: value }); + this._saveToLocalStorage(this._updateStorage(this._getFromLocalStorage(options), { [key]: value }, LOCATION.LOCAL, options)); break; } case LOCATION.SESSION: { - this._saveToSessionStorage({ ...this._getFromSessionStorage(), [key]: value }); + this._saveToSessionStorage(this._updateStorage(this._getFromSessionStorage(options), { [key]: value }, LOCATION.SESSION, options)); break; } case LOCATION.WINDOW: { - this._saveToWindow({ ...this._getFromLocalStorage(), [key]: value }); + this._saveToWindow({ ...this._getFromWindow(), [key]: value }); break; } default: @@ -94,11 +95,11 @@ export class StorageManager { switch (location) { case LOCATION.LOCAL: { - val = this._getFromLocalStorage()[key]; + val = this._getFromLocalStorage(options)[key]; break; } case LOCATION.SESSION: { - val = this._getFromSessionStorage()[key]; + val = this._getFromSessionStorage(options)[key]; break; } case LOCATION.WINDOW: { @@ -125,14 +126,14 @@ export class StorageManager { for (const location of locations) { switch (location) { case LOCATION.LOCAL: { - let val = this._getFromLocalStorage(); + let val = this._getFromLocalStorage(options); delete val[key]; return this._saveToLocalStorage(val); } case LOCATION.SESSION: { - let val = this._getFromSessionStorage(); + let val = this._getFromSessionStorage(options); delete val[key]; @@ -174,7 +175,7 @@ export class StorageManager { } - _getFromLocalStorage() { + _getFromLocalStorage(options=this._options) { let state; try { @@ -190,10 +191,10 @@ export class StorageManager { } if ('state' in state) - return state.state; + return this._getFromStorage(state.state, LOCATION.LOCAL, options); else { delete state.version; - return state; + return this._getFromStorage(state, LOCATION.LOCAL, options); } } @@ -224,12 +225,12 @@ export class StorageManager { if (!this._global.App.Storage) this._global.App.Storage = {}; - return this._global.App.Storage[this.namespace] || {}; + return this._global.App.Storage; } _saveToWindow(value) { if (!this._global || !this._global.App) { - throw new Error('StorageManager._saveToWindow called when window.App is not available'); + return console.error('StorageManager._saveToWindow called when window.App is not available'); } if (!value) @@ -243,7 +244,7 @@ export class StorageManager { _clearWindow() { if (!this._global || !this._global.App) { - throw new Error('StorageManager._saveToWindow called when window.App is not available'); + return console.error('StorageManager._saveToWindow called when window.App is not available'); } if (this._global.App.Storage) { @@ -252,7 +253,8 @@ export class StorageManager { } - _getFromSessionStorage() { + _getFromSessionStorage(options=this._options) { + console.log('_getFromSessionStorage with args', options); let state; try { @@ -268,10 +270,10 @@ export class StorageManager { } if ('state' in state) - return state.state; + return this._getFromStorage(state.state, LOCATION.SESSION, options); else { delete state.version; - return state; + return this._getFromStorage(state, LOCATION.SESSION, options); } } @@ -294,6 +296,26 @@ export class StorageManager { window.sessionStorage.removeItem(this.namespace); } + + _getFromStorage(storage, location, options=this._options) { + const encryption = options.encryption && (options.encryption.all || options.encryption[location]); + if (encryption && storage.encryption) { + return { ...storage, ...JSON.parse(decrypt(storage.encryption.ciphertext, encryption.key) || '{}') }; + } else { + return storage; + } + } + + _updateStorage(storage, update, location, options=this._options) { + const encryption = options.encryption && (options.encryption.all || options.encryption[location]); + if (encryption && storage.encryption) { + const updatedDecryptedStorage = { ...JSON.parse(decrypt(storage.encryption.ciphertext, this._encryptionKey[location]) || '{}'), ...update }; + return { ...storage, encryption: { ...storage.encryption, ciphertext: encrypt(JSON.stringify(updatedDecryptedStorage), this._encryptionKey[location]) } }; + } else { + return { ...storage, ...update }; + } + } + _requestStorageKey(options=this._options) { if (!(options && options.location && options.encryption)) throw new Error('Storage Manager cannot request storage key with unsupported options!'); @@ -301,7 +323,7 @@ export class StorageManager { const requestBody = { type : options.encryption, length : 42, - ...this.load('encryption', options), + ...this.load('encryption', { ...options, encryption: false }), }; this._global.App.httpClient.post({ @@ -318,9 +340,50 @@ export class StorageManager { if (response.salt !== requestBody.salt || response.timestamp !== requestBody.timestamp) { this.clear(options); } - this.save('encryption', { salt: response.salt, timestamp: response.timestamp }, options); + this.save('encryption', { salt: response.salt, timestamp: response.timestamp }, { ...options, encryption: false }); this._encryptionKey[options.location] = response.key; }).catch(console.error); } } + + +// TODO debug unnecessary calls of encrypt +function encrypt(plaintext, key) { + console.log('args encrypt', plaintext, key); + + if (!plaintext) return ''; + if (!key) throw new Error('Cannot encrypt plaintext without a valid key!'); + + /* eslint-disable no-undef */ + // TODO use const if possible + let plaintextB = Buffer.from(plaintext); + let cipherB = Buffer.alloc(plaintextB.length + sodium.crypto_secretbox_MACBYTES); + let nonceB = Buffer.alloc(sodium.crypto_secretbox_NONCEBYTES); + let keyB = Buffer.from(key); + /* eslint-enable no-undef */ + + sodium.crypto_secretbox_easy(cipherB, plaintextB, nonceB, keyB); + + return cipherB.toString('base64'); +} + +// TODO debug unnecessary calls of decrypt +function decrypt(ciphertext, key) { + console.log('args decrypt', ciphertext, key); + + if (!ciphertext) return ''; + if (!key) throw new Error('Cannot decrypt ciphertext without a valid key!'); + + /* eslint-disable no-undef */ + // TODO use const if possible + let cipherB = Buffer.from(ciphertext); + let plaintextB = Buffer.alloc(cipherB.length - sodium.crypto_secretbox_MACBYTES); + let nonceB = Buffer.alloc(sodium.crypto_secretbox_NONCEBYTES); + let keyB = Buffer.from(key); + /* eslint-enable no-undef */ + + sodium.crypto_secretbox_open_easy(plaintextB, cipherB, nonceB, keyB); + + return plaintextB.toString(); +}