forked from extern/bruno
feat(#199): electron safeStorage util for storing secrets with aes256 fallback
This commit is contained in:
parent
7a1b44858d
commit
f78c1640e9
@ -10,7 +10,8 @@
|
|||||||
"clean": "rimraf dist",
|
"clean": "rimraf dist",
|
||||||
"dev": "electron .",
|
"dev": "electron .",
|
||||||
"dist": "electron-builder --mac --config electron-builder-config.js",
|
"dist": "electron-builder --mac --config electron-builder-config.js",
|
||||||
"pack": "electron-builder --dir"
|
"pack": "electron-builder --dir",
|
||||||
|
"test": "jest"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@usebruno/js": "0.4.0",
|
"@usebruno/js": "0.4.0",
|
||||||
@ -31,6 +32,7 @@
|
|||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"mustache": "^4.2.0",
|
"mustache": "^4.2.0",
|
||||||
"nanoid": "3.3.4",
|
"nanoid": "3.3.4",
|
||||||
|
"node-machine-id": "^1.1.12",
|
||||||
"qs": "^6.11.0",
|
"qs": "^6.11.0",
|
||||||
"uuid": "^9.0.0",
|
"uuid": "^9.0.0",
|
||||||
"vm2": "^3.9.13",
|
"vm2": "^3.9.13",
|
||||||
|
73
packages/bruno-electron/src/utils/encryption.js
Normal file
73
packages/bruno-electron/src/utils/encryption.js
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
const crypto = require('crypto');
|
||||||
|
const { machineIdSync } = require('node-machine-id');
|
||||||
|
const { safeStorage } = require('electron');
|
||||||
|
|
||||||
|
// Constants for algorithm identification
|
||||||
|
const ELECTRONSAFESTORAGE_ALGO = '00';
|
||||||
|
const AES256_ALGO = '01';
|
||||||
|
|
||||||
|
// AES-256 encryption and decryption functions
|
||||||
|
function aes256Encrypt(data, key) {
|
||||||
|
const cipher = crypto.createCipher('aes-256-cbc', key);
|
||||||
|
let encrypted = cipher.update(data, 'utf8', 'hex');
|
||||||
|
encrypted += cipher.final('hex');
|
||||||
|
|
||||||
|
return encrypted;
|
||||||
|
}
|
||||||
|
function aes256Decrypt(data, key) {
|
||||||
|
const decipher = crypto.createDecipher('aes-256-cbc', key);
|
||||||
|
let decrypted = decipher.update(data, 'hex', 'utf8');
|
||||||
|
decrypted += decipher.final('utf8');
|
||||||
|
|
||||||
|
return decrypted;
|
||||||
|
}
|
||||||
|
|
||||||
|
function encryptString(str) {
|
||||||
|
if (!str || typeof str !== 'string' || str.length === 0) {
|
||||||
|
throw new Error('Encrypt failed: invalid string');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (safeStorage && safeStorage.isEncryptionAvailable()) {
|
||||||
|
let encryptedString = safeStorage.encryptString(str);
|
||||||
|
|
||||||
|
return `$${ELECTRONSAFESTORAGE_ALGO}:${encryptedString}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// fallback to aes256
|
||||||
|
const key = machineIdSync();
|
||||||
|
let encryptedString = aes256Encrypt(str, key);
|
||||||
|
|
||||||
|
return `$${AES256_ALGO}:${encryptedString}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function decryptString(str) {
|
||||||
|
if (!str) {
|
||||||
|
throw new Error('Decrypt failed: unrecognized string format');
|
||||||
|
}
|
||||||
|
|
||||||
|
const match = str.match(/^\$(.*?):(.*)$/);
|
||||||
|
if (!match) {
|
||||||
|
throw new Error('Decrypt failed: unrecognized string format');
|
||||||
|
}
|
||||||
|
|
||||||
|
const algo = match[1];
|
||||||
|
const encryptedString = match[2];
|
||||||
|
|
||||||
|
if ([ELECTRONSAFESTORAGE_ALGO, AES256_ALGO].indexOf(algo) === -1) {
|
||||||
|
throw new Error('Decrypt failed: Invalid algo');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (algo === ELECTRONSAFESTORAGE_ALGO) {
|
||||||
|
return safeStorage.decryptString(encryptedString);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (algo === AES256_ALGO) {
|
||||||
|
const key = machineIdSync();
|
||||||
|
return aes256Decrypt(encryptedString, key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
encryptString,
|
||||||
|
decryptString
|
||||||
|
};
|
31
packages/bruno-electron/tests/utils/encryption.spec.js
Normal file
31
packages/bruno-electron/tests/utils/encryption.spec.js
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
const { encryptString, decryptString } = require('../../src/utils/encryption');
|
||||||
|
|
||||||
|
// We can only unit test aes 256 fallback as safeStorage is only available
|
||||||
|
// in the main process
|
||||||
|
|
||||||
|
describe('Encryption and Decryption Tests', () => {
|
||||||
|
it('should encrypt and decrypt using AES-256', () => {
|
||||||
|
const plaintext = 'bruno is awesome';
|
||||||
|
const encrypted = encryptString(plaintext);
|
||||||
|
const decrypted = decryptString(encrypted);
|
||||||
|
|
||||||
|
expect(decrypted).toBe(plaintext);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('encrypt should throw an error for invalid string', () => {
|
||||||
|
expect(() => encryptString(null)).toThrow('Encrypt failed: invalid string');
|
||||||
|
expect(() => encryptString('')).toThrow('Encrypt failed: invalid string');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('decrypt should throw an error for invalid string', () => {
|
||||||
|
expect(() => decryptString(null)).toThrow('Decrypt failed: unrecognized string format');
|
||||||
|
expect(() => decryptString('')).toThrow('Decrypt failed: unrecognized string format');
|
||||||
|
expect(() => decryptString('garbage')).toThrow('Decrypt failed: unrecognized string format');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('decrypt should throw an error for invalid algorithm', () => {
|
||||||
|
const invalidAlgo = '$99:abcdefg';
|
||||||
|
|
||||||
|
expect(() => decryptString(invalidAlgo)).toThrow('Decrypt failed: Invalid algo');
|
||||||
|
});
|
||||||
|
});
|
Loading…
Reference in New Issue
Block a user