Skip to content

Commit

Permalink
feat: refactor wallet to use decorator pattern for optional encryption (
Browse files Browse the repository at this point in the history
#479)

* feat: refactor wallet to use decorator pattern for optional encryption

* Improve coverage
  • Loading branch information
Bushstar authored Dec 12, 2024
1 parent c13ea9b commit 1318582
Show file tree
Hide file tree
Showing 5 changed files with 74 additions and 57 deletions.
33 changes: 11 additions & 22 deletions packages/keymaster/src/db-wallet-json-enc.js
Original file line number Diff line number Diff line change
@@ -1,31 +1,24 @@
import fs from 'fs';
import crypto from 'crypto';

const dataFolder = 'data';
const walletName = `${dataFolder}/wallet.json`;

const algorithm = 'aes-256-cbc'; // Algorithm
const keyLength = 32; // 256 bit AES-256
const ivLength = 16; // 128-bit AES block size
const saltLength = 16; // 128-bit salt
const iterations = 200000; // PBKDF2 iterations
const digest = 'sha512'; // PBKDF2 hash function

let baseWallet;
let passphrase;

export function setPassphrase(pp) {
passphrase = pp;
}

export function saveWallet(wallet, overwrite = false) {
if (fs.existsSync(walletName) && !overwrite) {
return false;
}

if (!fs.existsSync(dataFolder)) {
fs.mkdirSync(dataFolder, { recursive: true });
}
export function setWallet(wallet) {
baseWallet = wallet;
}

export function saveWallet(wallet, overwrite = false) {
if (!passphrase) {
throw new Error('KC_ENCRYPTED_PASSPHRASE not set');
}
Expand All @@ -45,24 +38,20 @@ export function saveWallet(wallet, overwrite = false) {
data: encrypted
};

fs.writeFileSync(walletName, JSON.stringify(encryptedData, null, 4));

return true;
return baseWallet.saveWallet(encryptedData, overwrite);
}

export function loadWallet() {
if (!fs.existsSync(walletName)) {
return null;
}

if (!passphrase) {
throw new Error('KC_ENCRYPTED_PASSPHRASE not set');
}

const encryptedJson = fs.readFileSync(walletName, 'utf8');
const encryptedData = JSON.parse(encryptedJson);
const encryptedData = baseWallet.loadWallet();
if (!encryptedData) {
return null;
}

if (!encryptedData || !encryptedData.salt || !encryptedData.iv || !encryptedData.data) {
if (!encryptedData.salt || !encryptedData.iv || !encryptedData.data) {
throw new Error('Wallet not encrypted');
}

Expand Down
9 changes: 2 additions & 7 deletions packages/keymaster/src/db-wallet-json.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,6 @@ export function loadWallet() {
}

const walletJson = fs.readFileSync(walletName);
const walletData = JSON.parse(walletJson);

if (walletData && walletData.salt && walletData.iv && walletData.data) {
throw new Error('Wallet is encrypted');
}

return walletData;

return JSON.parse(walletJson);
}
2 changes: 1 addition & 1 deletion sample.env
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ KC_GATEKEEPER_PORT=4224
KC_GATEKEEPER_GC_INTERVAL=60

# Wallet
KC_ENCRYPTED_WALLET=false
# KC_ENCRYPTED_PASSPHRASE=

# CLI
KC_GATEKEEPER_URL=http://localhost:4224
Expand Down
43 changes: 32 additions & 11 deletions scripts/keychain-cli.js
Original file line number Diff line number Diff line change
Expand Up @@ -948,20 +948,37 @@ program
return;
}

db_wallet_enc.setPassphrase(keymasterPassphrase);
const wallet = db_wallet_json.loadWallet();
let wallet = db_wallet_json.loadWallet();
if (wallet && (wallet.salt && wallet.iv && wallet.data)) {
console.error('Wallet already encrypted');
return;
}

if (wallet === null) {
await keymaster.start({
gatekeeper: gatekeeper_sdk,
wallet: db_wallet_json,
cipher,
});
await keymaster.newWallet();
} else {
const ok = db_wallet_enc.saveWallet(wallet, true);
if (ok) {
console.log(UPDATE_OK);
}
else {
console.log(UPDATE_FAILED);
wallet = db_wallet_json.loadWallet();

if (wallet === null) {
console.error('Failed to create new wallet');
return;
}
}

db_wallet_enc.setPassphrase(keymasterPassphrase);
db_wallet_enc.setWallet(db_wallet_json);

const ok = db_wallet_enc.saveWallet(wallet, true);
if (ok) {
console.log(UPDATE_OK);
}
else {
console.log(UPDATE_FAILED);
}
} catch (error) {
console.error(error.message);
}
Expand Down Expand Up @@ -1006,11 +1023,15 @@ program
});

function getDBWallet() {
let wallet = db_wallet_json;

if (keymasterPassphrase) {
db_wallet_enc.setPassphrase(keymasterPassphrase);
return db_wallet_enc;
db_wallet_enc.setWallet(wallet);
wallet = db_wallet_enc;
}
return db_wallet_json;

return wallet;
}

async function run() {
Expand Down
44 changes: 28 additions & 16 deletions tests/keymaster.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ describe('loadWallet', () => {
afterEach(async () => {
mockFs.restore();
wallet_enc.setPassphrase(undefined);
wallet_enc.setWallet(undefined);
await keymaster.start({ gatekeeper, wallet, cipher });
});

Expand Down Expand Up @@ -123,28 +124,17 @@ describe('loadWallet', () => {
it('loading non-existing encrypted wallet returns null', async () => {
mockFs({});

const wallet = wallet_enc.loadWallet();
expect(wallet).toBe(null);
});

it('regular wallet should throw when loading encrypted wallet', async () => {
mockFs({});
const mockWallet = { salt: 1, iv: 1, data: 1 };

const ok = await keymaster.saveWallet(mockWallet);
wallet_enc.setPassphrase('passphrase');
wallet_enc.setWallet(wallet);

try {
await keymaster.loadWallet();
throw new ExpectedExceptionError();
} catch (error) {
expect(ok).toBe(true);
expect(error.message).toBe('Wallet is encrypted');
}
const check_wallet = wallet_enc.loadWallet();
expect(check_wallet).toBe(null);
});

it('wallet should throw when passphrase not set', async () => {
mockFs({});
const mockWallet = { mock: 1 };
wallet_enc.setWallet(wallet);

await keymaster.start({ gatekeeper, wallet: wallet_enc, cipher });

Expand All @@ -167,6 +157,7 @@ describe('loadWallet', () => {

await keymaster.start({ gatekeeper, wallet: wallet_enc, cipher });

wallet_enc.setWallet(wallet);
wallet_enc.setPassphrase('passphrase');

const ok = await keymaster.saveWallet(mockWallet);
Expand All @@ -191,6 +182,25 @@ describe('saveWallet', () => {
await keymaster.start({ gatekeeper, wallet, cipher });
});

it('test saving directly on the unencrypted wallet', async () => {
mockFs({});
const mockWallet = { mock: 0 };

const ok = wallet.saveWallet(mockWallet);
expect(ok).toBe(true);
});

it('test saving directly on the encrypted wallet', async () => {
mockFs({});
const mockWallet = { mock: 0 };

wallet_enc.setWallet(wallet);
wallet_enc.setPassphrase('passphrase');

const ok = wallet_enc.saveWallet(mockWallet);
expect(ok).toBe(true);
});

it('should save a wallet', async () => {
mockFs({});
const mockWallet = { mock: 0 };
Expand Down Expand Up @@ -270,6 +280,7 @@ describe('saveWallet', () => {
mockFs({});

await keymaster.start({ gatekeeper, wallet: wallet_enc, cipher });
wallet_enc.setWallet(wallet);
wallet_enc.setPassphrase('passphrase');

const mockWallet1 = { mock: 1 };
Expand Down Expand Up @@ -306,6 +317,7 @@ describe('saveWallet', () => {
fs.writeFileSync(walletFile, JSON.stringify(mockWallet, null, 4));

await keymaster.start({ gatekeeper, wallet: wallet_enc, cipher });
wallet_enc.setWallet(wallet);
wallet_enc.setPassphrase('passphrase');

try {
Expand Down

0 comments on commit 1318582

Please sign in to comment.