Skip to content

Commit b3b5052

Browse files
committed
Merge branch 'feat/encryption' into develop
2 parents 2df644d + 5ec3aae commit b3b5052

File tree

8 files changed

+608
-424
lines changed

8 files changed

+608
-424
lines changed

app.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ class ArcInit {
2222
/* global ipc, ArcContextMenu, ArcElectronDrive, OAuth2Handler,
2323
ThemeManager, ArcPreferencesProxy, CookieBridge, WorkspaceManager,
2424
FilesystemProxy, ElectronAmfService, versionInfo, WindowSearchService,
25-
UpgradeHelper, ImportFilePrePprocessor */
25+
UpgradeHelper, ImportFilePrePprocessor, EncryptionService */
2626
this.created = false;
2727
this.contextActions = new ArcContextMenu();
2828
this.driveBridge = new ArcElectronDrive();
@@ -33,6 +33,7 @@ class ArcInit {
3333
this.fs = new FilesystemProxy();
3434
this.amfService = new ElectronAmfService();
3535
this.search = new WindowSearchService();
36+
this.encryption = new EncryptionService();
3637
}
3738
/**
3839
* @return {ImportFilePrePprocessor} Instance of import processor class.
@@ -66,6 +67,7 @@ class ArcInit {
6667
this.fs.listen();
6768
this.amfService.listen();
6869
this.search.listen();
70+
this.encryption.listen();
6971

7072
ipc.on('checking-for-update', () => {
7173
this.updateEventHandler('checking-for-update');

package-lock.json

Lines changed: 457 additions & 423 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,9 @@
7878
"amf-client-js": "^3.5.1",
7979
"camelcase": "^4.1.0",
8080
"codemirror": "^5.49.0",
81+
"crypto-js": "^3.1.9-1",
8182
"electron-log": "^3.0.8",
83+
"electron-prompt": "^1.3.1",
8284
"electron-updater": "4.1.2",
8385
"form-data": "^2.5.1",
8486
"fs-extra": "^8.1.0",
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
const AES = require('crypto-js/aes.js');
2+
const CryptoJS = require('crypto-js/crypto-js.js');
3+
const prompt = require('electron-prompt');
4+
/**
5+
* A class that handles `encryption-*` web events in the renderer process
6+
* and performs data encryption/decryption.
7+
*
8+
* TODO: consider spawning another process for data encryption / decryption.
9+
* Compare gain/loss on running encryption/decryption in separate process
10+
* and check whether data passing from process to another process is more costly.
11+
*/
12+
class EncryptionService {
13+
constructor() {
14+
this._decodeHandler = this._decodeHandler.bind(this);
15+
this._encodeHandler = this._encodeHandler.bind(this);
16+
}
17+
18+
listen() {
19+
window.addEventListener('encryption-decode', this._decodeHandler);
20+
window.addEventListener('encryption-encode', this._encodeHandler);
21+
}
22+
23+
unlisten() {
24+
window.removeEventListener('encryption-decode', this._decodeHandler);
25+
window.removeEventListener('encryption-encode', this._encodeHandler);
26+
}
27+
28+
_decodeHandler(e) {
29+
const { method } = e.detail;
30+
e.preventDefault();
31+
e.detail.result = this.decode(method, e.detail);
32+
}
33+
34+
_encodeHandler(e) {
35+
const { method } = e.detail;
36+
e.preventDefault();
37+
e.detail.result = this.encode(method, e.detail);
38+
}
39+
40+
async encode(method, opts) {
41+
switch (method) {
42+
case 'aes': return await this.encodeAes(opts.data, opts.passphrase);
43+
default: throw new Error(`Unknown encryption method`);
44+
}
45+
}
46+
47+
async encodeAes(data, passphrase) {
48+
// Todo: this looks really dangerous to run file encryption in the main
49+
// thread (of the renderer process). Consider other options.
50+
const encrypted = AES.encrypt(data, passphrase);
51+
return encrypted.toString();
52+
}
53+
54+
async decode(method, opts) {
55+
switch (method) {
56+
case 'aes': return await this.decodeAes(opts.data, opts.passphrase);
57+
default: throw new Error(`Unknown decryption method`);
58+
}
59+
}
60+
61+
async decodeAes(data, passphrase) {
62+
if (passphrase === undefined) {
63+
const win = require('electron').remote.getCurrentWindow();
64+
passphrase = await prompt({
65+
title: 'File password',
66+
label: 'Enter password to open the file',
67+
}, win);
68+
if (passphrase === null) {
69+
throw new Error('Password is required to open the file.');
70+
}
71+
}
72+
try {
73+
const bytes = AES.decrypt(data, passphrase);
74+
return bytes.toString(CryptoJS.enc.Utf8);
75+
} catch (_) {
76+
throw new Error('Invalid password.');
77+
}
78+
}
79+
}
80+
exports.EncryptionService = EncryptionService;
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
exports.EncryptionService = require('./encryption').EncryptionService;
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
const { assert } = require('chai');
2+
const { EncryptionService } = require('../renderer');
3+
4+
describe('EncryptionService', function() {
5+
describe('Encrypting content', () => {
6+
let instance;
7+
before(() => {
8+
instance = new EncryptionService();
9+
instance.listen();
10+
});
11+
12+
after(() => {
13+
instance.unlisten();
14+
});
15+
16+
it('encodes a content using AES.encrypt', async () => {
17+
const e = new CustomEvent('encryption-encode', {
18+
bubbles: true,
19+
cancelable: true,
20+
detail: {
21+
method: 'aes',
22+
data: 'test-data',
23+
passphrase: 'test'
24+
}
25+
});
26+
document.body.dispatchEvent(e);
27+
const result = await e.detail.result;
28+
assert.typeOf(result, 'string');
29+
assert.notEqual(result, 'test-data');
30+
});
31+
});
32+
33+
34+
describe('Decrypting content', () => {
35+
const encoded = 'U2FsdGVkX19MRBu8liXK/sbNBOQWG1mewbjEBb8cTAw=';
36+
let instance;
37+
before(() => {
38+
instance = new EncryptionService();
39+
instance.listen();
40+
});
41+
42+
after(() => {
43+
instance.unlisten();
44+
});
45+
46+
it('decodes a content using AES.decrypt and provided password', async () => {
47+
const e = new CustomEvent('encryption-decode', {
48+
bubbles: true,
49+
cancelable: true,
50+
detail: {
51+
method: 'aes',
52+
data: encoded,
53+
passphrase: 'test'
54+
}
55+
});
56+
document.body.dispatchEvent(e);
57+
const result = await e.detail.result;
58+
assert.typeOf(result, 'string');
59+
assert.equal(result, 'test-data');
60+
});
61+
});
62+
});

scripts/renderer/preload.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ const { ArcContextMenu } = require('../packages/context-actions/renderer');
1414
const { FilesystemProxy } = require('./filesystem-proxy');
1515
const { ElectronAmfService } = require('@advanced-rest-client/electron-amf-service');
1616
const { WindowSearchService } = require('../packages/search-service/renderer');
17+
const { EncryptionService } = require('../packages/encryption/renderer/encryption.js');
1718
const { UpgradeHelper } = require('./upgrade-helper');
1819
const { ImportFilePrePprocessor } = require('./import-file-preprocessor');
1920
const setImmediateFn = setImmediate;
@@ -49,6 +50,7 @@ process.once('loaded', () => {
4950
global.Jexl = Jexl;
5051
global.UpgradeHelper = UpgradeHelper;
5152
global.ImportFilePrePprocessor = ImportFilePrePprocessor;
53+
global.EncryptionService = EncryptionService;
5254
global.versionInfo = {
5355
chrome: versions.chrome,
5456
appVersion: app.getVersion()

src/arc-electron.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ class ArcElectron extends ArcAppMixin(LitElement) {
5757
/* global log */
5858
this.log = log;
5959
this.sysVars = process.env;
60+
this.withEncrypt = true;
6061
}
6162

6263
connectedCallback() {

0 commit comments

Comments
 (0)