Skip to content

Commit e6865a2

Browse files
committed
feat(encryption): generate / accept encrytion keys
1 parent 4a26a98 commit e6865a2

File tree

7 files changed

+305
-42
lines changed

7 files changed

+305
-42
lines changed

electron/core/driver/connection/index.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ export const Connection: Connection = {
101101
},
102102
load: async ({ storedConnection }) => {
103103
if (storedConnection) {
104-
const uri = getConnectionUri(storedConnection);
104+
const uri = await getConnectionUri(storedConnection);
105105
return { ...storedConnection, uri };
106106
} else {
107107
throw new Error(ERR_CODES.CORE$DRIVER$NO_STORED_CONNECTION);
@@ -126,7 +126,7 @@ export const Connection: Connection = {
126126
}
127127
}
128128

129-
const connectionUri = getConnectionUri(storedConnection);
129+
const connectionUri = await getConnectionUri(storedConnection);
130130
const client = new MongoClient(connectionUri);
131131
const connection = await client.connect();
132132
const listDatabaseResult = await connection.db().admin().listDatabases();
@@ -191,7 +191,7 @@ export const Connection: Connection = {
191191
await sshTunnel(config.ssh, config.hosts);
192192
}
193193

194-
const connectionUri = getConnectionUri(config);
194+
const connectionUri = await getConnectionUri(config);
195195

196196
const client = new MongoClient(connectionUri);
197197

electron/core/driver/connection/library.ts

+78-19
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,17 @@ import { MongoClient, MongoClientOptions } from "mongodb";
22
import type { SrvRecord } from "dns";
33
import { promises as netPromises } from "dns";
44
import { nanoid } from "nanoid";
5-
import path from "path";
5+
import path, { dirname, resolve } from "path";
66
import mongoUri from "mongodb-uri";
77
import * as crypto from "crypto";
8+
import axios from 'axios';
89
import tunnel, { Config } from "tunnel-ssh";
10+
import { Buffer } from 'buffer';
911

1012
import { ARK_FOLDER_PATH } from "../../../utils/constants";
1113
import { Connection } from ".";
14+
import { mkdir, readFile, writeFile } from "fs/promises";
15+
import { existsSync } from "fs";
1216

1317
export const sshTunnel = async (
1418
sshConfig: Ark.StoredConnection["ssh"],
@@ -51,17 +55,22 @@ export const sshTunnel = async (
5155
});
5256
};
5357

54-
export const getConnectionUri = (
58+
export const getConnectionUri = async (
5559
{
5660
hosts,
5761
database = "admin",
5862
username,
5963
password,
6064
options,
6165
iv,
62-
key
66+
key,
67+
encryptionKeySource
6368
}: Ark.StoredConnection
6469
) => {
70+
const pwd = (password && key && iv && encryptionKeySource)
71+
? await decrypt(password, key, encryptionKeySource, iv)
72+
: password;
73+
6574
const uri = mongoUri.format({
6675
hosts: hosts.map((host) => ({
6776
host: host.split(":")[0],
@@ -71,9 +80,7 @@ export const getConnectionUri = (
7180
database,
7281
options,
7382
username,
74-
password: (password && key && iv)
75-
? decrypt(password, key, iv)
76-
: password,
83+
password: pwd,
7784
});
7885

7986
return uri;
@@ -128,24 +135,53 @@ export const getReplicaSetDetails = async (
128135
}
129136
};
130137

131-
export const encrypt = (password: string) => {
138+
export const encrypt = async (
139+
password: string,
140+
encryptionKey?: string,
141+
encryptionKeySourceType?: string,
142+
) => {
132143
const iv = crypto.randomBytes(16);
133-
const key = crypto.randomBytes(32);
144+
const key = encryptionKey
145+
? encryptionKeySourceType === "url"
146+
? (await axios.get(encryptionKey)).data.toString()
147+
: (await readFile(encryptionKey)).toString()
148+
: crypto.generateKeySync("aes", { length: 256 });
134149

135-
const cipher = crypto.createCipheriv("aes-256-cbc", Buffer.from(key), iv);
150+
const cipherKey = encryptionKey
151+
? crypto.createSecretKey(Buffer.from(key, "hex").toString("hex"), "hex")
152+
: key;
153+
154+
const cipher = crypto.createCipheriv("aes-256-cbc", cipherKey, iv);
136155
return {
137-
pwd: Buffer.concat([cipher.update(password), cipher.final()]).toString(
156+
pwd: password && Buffer.concat([cipher.update(password), cipher.final()]).toString(
138157
"hex"
139158
),
140-
key: key.toString("hex"),
159+
key: encryptionKey
160+
? key.toString("hex")
161+
: key.export().toString("hex"),
141162
iv: iv.toString("hex"),
142163
};
143164
};
144165

145-
export const decrypt = (password: string, key: string, iv: string) => {
166+
export const decrypt = async (
167+
password: string,
168+
key: string,
169+
keySource: string,
170+
iv: string
171+
) => {
172+
173+
const encryptionKey = keySource === "url"
174+
? (await axios.get(key)).data
175+
: await readFile(key);
176+
177+
const secret = crypto.createSecretKey(
178+
Buffer.from(encryptionKey, 'hex').toString(),
179+
"hex"
180+
);
181+
146182
const decipher = crypto.createDecipheriv(
147183
"aes-256-cbc",
148-
Buffer.from(key, "hex"),
184+
secret,
149185
Buffer.from(iv, "hex")
150186
);
151187
return decipher.update(password, "hex", "utf8") + decipher.final("utf8");
@@ -186,9 +222,14 @@ export const createConnectionConfigurations = async ({
186222
}
187223

188224
const encryption = parsedUri.password
189-
? encrypt(parsedUri.password)
225+
? await encrypt(parsedUri.password)
190226
: undefined;
191227

228+
const filePath = resolve(ARK_FOLDER_PATH, 'keys', config.name);
229+
if (encryption && encryption.key) {
230+
await writeFile(filePath, encryption.key);
231+
}
232+
192233
return {
193234
id,
194235
protocol: parsedUri.scheme,
@@ -197,11 +238,13 @@ export const createConnectionConfigurations = async ({
197238
hosts: hosts,
198239
username: parsedUri.username,
199240
password: encryption?.pwd,
200-
key: encryption?.key,
241+
key: filePath,
201242
iv: encryption?.iv,
202243
database: parsedUri.database,
203244
options: { ...parsedUri.options, ...options },
204245
ssh: { useSSH: false },
246+
encryptionKeySource: 'generated',
247+
encryptionKeySourceType: 'file'
205248
};
206249
} else {
207250
const id = config.id || nanoid();
@@ -222,14 +265,30 @@ export const createConnectionConfigurations = async ({
222265
config.options = { ...opts };
223266
}
224267

225-
const encryption = config.password ? encrypt(config.password) : undefined;
268+
const encryption = config.password
269+
? !config.encryptionKeySource || config.encryptionKeySource === 'generated'
270+
? await encrypt(config.password)
271+
: await encrypt(config.password, config.key, config.encryptionKeySourceType)
272+
: undefined;
273+
274+
const filePath = config.encryptionKeySource === 'generated' && !config.key
275+
? resolve(ARK_FOLDER_PATH, 'keys', config.name)
276+
: config.key as string;
226277

227-
config.password = encryption?.pwd;
228-
config.key = encryption?.key;
229-
config.iv = encryption?.iv;
278+
if (config.encryptionKeySource === 'generated') {
279+
if (!existsSync(dirname(filePath))) {
280+
await mkdir(dirname(filePath));
281+
}
282+
await writeFile(filePath, encryption?.key, { flag: 'w' });
283+
}
230284

231285
return {
232286
...config,
287+
password: encryption?.pwd,
288+
iv: encryption?.iv,
289+
key: filePath,
290+
encryptionKeySource: 'generated',
291+
encryptionKeySourceType: 'file',
233292
id,
234293
};
235294
}

electron/core/shell-manager/index.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ export function createShellManager(params: CreateShellParams) {
5151
args: { id: connectionId },
5252
});
5353

54-
const uri = getConnectionUri(storedConnection);
54+
const uri = await getConnectionUri(storedConnection);
5555

5656
const mongoOptions = {
5757
...storedConnection.options,

global.d.ts

+8-4
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ import { Query } from "./electron/core/driver/query";
1515

1616
declare global {
1717
namespace Ark {
18-
1918
interface StoredIcon {
2019
name: string;
2120
path: string;
@@ -29,7 +28,6 @@ declare global {
2928
[k: string]: any;
3029
}
3130

32-
3331
interface DriverStores {
3432
memoryStore: MemoryStore<MemEntry>;
3533
diskStore: DiskStore<StoredConnection>;
@@ -54,6 +52,8 @@ declare global {
5452
password?: string;
5553
icon?: boolean;
5654
type: "directConnection" | "replicaSet";
55+
encryptionKeySource: "userDefined" | "generated";
56+
encryptionKeySourceType: "file" | "url";
5757
options: Pick<
5858
MongoClientOptions,
5959
| "authSource"
@@ -196,11 +196,15 @@ declare global {
196196
) => Promise<{ path: string }>;
197197
copyText(text: string): void;
198198
getIcon(id: string): Promise<StoredIcon>;
199-
copyIcon(cacheFolder: string, name: string, path: string): Promise<{ path: string }>;
199+
copyIcon(
200+
cacheFolder: string,
201+
name: string,
202+
path: string
203+
): Promise<{ path: string }>;
200204
rmIcon(path: string): Promise<void>;
201205
scripts: Scripts;
202206
driver: {
203-
run: Driver["run"]
207+
run: Driver["run"];
204208
};
205209
settings: GeneralSettings;
206210
shell: Shell;

0 commit comments

Comments
 (0)