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",
|
||||
"dev": "electron .",
|
||||
"dist": "electron-builder --mac --config electron-builder-config.js",
|
||||
"pack": "electron-builder --dir"
|
||||
"pack": "electron-builder --dir",
|
||||
"test": "jest"
|
||||
},
|
||||
"dependencies": {
|
||||
"@usebruno/js": "0.4.0",
|
||||
@ -31,6 +32,7 @@
|
||||
"lodash": "^4.17.21",
|
||||
"mustache": "^4.2.0",
|
||||
"nanoid": "3.3.4",
|
||||
"node-machine-id": "^1.1.12",
|
||||
"qs": "^6.11.0",
|
||||
"uuid": "^9.0.0",
|
||||
"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