@hyperbridge/simplex 0.16.8 → 0.16.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,186 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/config/storage.ts
21
+ var storage_exports = {};
22
+ __export(storage_exports, {
23
+ decryptConfig: () => decryptConfig,
24
+ encryptConfig: () => encryptConfig,
25
+ encryptedConfigStore: () => encryptedConfigStore,
26
+ isEncryptedConfig: () => isEncryptedConfig,
27
+ openSecret: () => openSecret,
28
+ passwordKey: () => passwordKey,
29
+ readConfigFile: () => readConfigFile,
30
+ readConfigKey: () => readConfigKey,
31
+ sealSecret: () => sealSecret,
32
+ writeSecretAtomic: () => writeSecretAtomic
33
+ });
34
+ module.exports = __toCommonJS(storage_exports);
35
+ var import_node_crypto = require("node:crypto");
36
+ var import_node_fs = require("node:fs");
37
+ var import_node_path = require("node:path");
38
+ var CONFIG_HEADER = "SIMPLEX-ENCRYPTED-CONFIG-V1\n";
39
+ var MAX_CONFIG_BYTES = 2 * 1024 * 1024;
40
+ function writeSecretAtomic(path, content) {
41
+ const temp = (0, import_node_path.join)((0, import_node_path.dirname)(path), `.${(0, import_node_path.basename)(path)}.${(0, import_node_crypto.randomBytes)(12).toString("hex")}.tmp`);
42
+ let fd;
43
+ try {
44
+ fd = (0, import_node_fs.openSync)(temp, "wx", 384);
45
+ (0, import_node_fs.writeFileSync)(fd, content);
46
+ (0, import_node_fs.fsyncSync)(fd);
47
+ (0, import_node_fs.closeSync)(fd);
48
+ fd = void 0;
49
+ (0, import_node_fs.renameSync)(temp, path);
50
+ if (process.platform !== "win32") {
51
+ const directory = (0, import_node_fs.openSync)((0, import_node_path.dirname)(path), "r");
52
+ try {
53
+ (0, import_node_fs.fsyncSync)(directory);
54
+ } finally {
55
+ (0, import_node_fs.closeSync)(directory);
56
+ }
57
+ }
58
+ } catch (error) {
59
+ if (fd !== void 0) (0, import_node_fs.closeSync)(fd);
60
+ try {
61
+ (0, import_node_fs.unlinkSync)(temp);
62
+ } catch {
63
+ }
64
+ throw error;
65
+ }
66
+ }
67
+ function bytes(value, length) {
68
+ if (typeof value !== "string" || value.length > MAX_CONFIG_BYTES * 2 || !/^(?:[a-f0-9]{2})*$/.test(value)) {
69
+ throw new Error("Invalid encrypted data");
70
+ }
71
+ const decoded = Buffer.from(value, "hex");
72
+ if (length !== void 0 && decoded.length !== length) throw new Error("Invalid encrypted data");
73
+ return decoded;
74
+ }
75
+ function sealSecret(plaintext, key, purpose) {
76
+ if (key.length !== 32) throw new Error("Invalid encryption key");
77
+ const iv = (0, import_node_crypto.randomBytes)(12);
78
+ const cipher = (0, import_node_crypto.createCipheriv)("aes-256-gcm", key, iv);
79
+ const aad = Buffer.from(purpose);
80
+ cipher.setAAD(aad);
81
+ const data = Buffer.concat([cipher.update(plaintext), cipher.final()]);
82
+ return { iv: iv.toString("hex"), tag: cipher.getAuthTag().toString("hex"), data: data.toString("hex") };
83
+ }
84
+ function openSecret(sealed, key, purpose) {
85
+ if (key.length !== 32 || !sealed || typeof sealed !== "object") throw new Error("Invalid encrypted data");
86
+ const decipher = (0, import_node_crypto.createDecipheriv)("aes-256-gcm", key, bytes(sealed.iv, 12));
87
+ decipher.setAAD(Buffer.from(purpose));
88
+ decipher.setAuthTag(bytes(sealed.tag, 16));
89
+ return Buffer.concat([decipher.update(bytes(sealed.data)), decipher.final()]);
90
+ }
91
+ function passwordKey(password, salt) {
92
+ const saltBytes = bytes(salt, 16);
93
+ return new Promise((resolveKey, reject) => {
94
+ (0, import_node_crypto.scrypt)(password, saltBytes, 32, { N: 131072, r: 8, p: 1, maxmem: 192 * 1024 * 1024 }, (error, key) => {
95
+ if (error) reject(error);
96
+ else resolveKey(key);
97
+ });
98
+ });
99
+ }
100
+ function isEncryptedConfig(content) {
101
+ return content.startsWith("SIMPLEX-ENCRYPTED-CONFIG-");
102
+ }
103
+ function encryptConfig(content, key) {
104
+ if (Buffer.byteLength(content) > MAX_CONFIG_BYTES) throw new Error("Config is too large");
105
+ const plaintext = Buffer.from(content);
106
+ try {
107
+ return CONFIG_HEADER + JSON.stringify(sealSecret(plaintext, key, "simplex/config/v1")) + "\n";
108
+ } finally {
109
+ plaintext.fill(0);
110
+ }
111
+ }
112
+ function decryptConfig(content, key) {
113
+ if (!content.startsWith(CONFIG_HEADER) || content.length > MAX_CONFIG_BYTES * 2 + 1024) {
114
+ throw new Error("Unsupported or invalid encrypted config");
115
+ }
116
+ const plaintext = openSecret(JSON.parse(content.slice(CONFIG_HEADER.length)), key, "simplex/config/v1");
117
+ try {
118
+ return plaintext.toString("utf8");
119
+ } finally {
120
+ plaintext.fill(0);
121
+ }
122
+ }
123
+ function readConfigFile(path, key) {
124
+ const content = (0, import_node_fs.readFileSync)(path, "utf8");
125
+ if (isEncryptedConfig(content)) {
126
+ if (!key) throw new Error("This config is encrypted. Open Simplex Desktop to unlock it.");
127
+ return decryptConfig(content, key);
128
+ }
129
+ if (key) throw new Error("Refusing to load a plaintext config in protected desktop mode");
130
+ return content;
131
+ }
132
+ function encryptedConfigStore(path, key) {
133
+ const target = (0, import_node_path.resolve)(path);
134
+ return {
135
+ path: target,
136
+ exists: () => (0, import_node_fs.existsSync)(target),
137
+ read: () => readConfigFile(target, key),
138
+ write: (requestedPath, content) => {
139
+ if ((0, import_node_path.resolve)(requestedPath) !== target)
140
+ throw new Error("Desktop config must stay in its protected data directory");
141
+ writeSecretAtomic(target, encryptConfig(content, key));
142
+ }
143
+ };
144
+ }
145
+ function readConfigKey(input, timeoutMs = 5e3) {
146
+ return new Promise((resolveKey, reject) => {
147
+ const key = Buffer.alloc(32);
148
+ let offset = 0;
149
+ const finish = (error) => {
150
+ clearTimeout(timer);
151
+ input.removeListener("data", onData);
152
+ input.removeListener("end", onEnd);
153
+ input.removeListener("error", onError);
154
+ input.destroy();
155
+ if (error) {
156
+ key.fill(0);
157
+ reject(error);
158
+ } else resolveKey(key);
159
+ };
160
+ const onData = (chunk) => {
161
+ if (!Buffer.isBuffer(chunk) || offset + chunk.length > 32)
162
+ return finish(new Error("Invalid desktop key handoff"));
163
+ chunk.copy(key, offset);
164
+ offset += chunk.length;
165
+ chunk.fill(0);
166
+ };
167
+ const onEnd = () => finish(offset === 32 ? void 0 : new Error("Incomplete desktop key handoff"));
168
+ const onError = () => finish(new Error("Desktop key handoff failed"));
169
+ const timer = setTimeout(() => finish(new Error("Desktop key handoff timed out")), timeoutMs);
170
+ input.on("data", onData).once("end", onEnd).once("error", onError);
171
+ });
172
+ }
173
+ // Annotate the CommonJS export names for ESM import in node:
174
+ 0 && (module.exports = {
175
+ decryptConfig,
176
+ encryptConfig,
177
+ encryptedConfigStore,
178
+ isEncryptedConfig,
179
+ openSecret,
180
+ passwordKey,
181
+ readConfigFile,
182
+ readConfigKey,
183
+ sealSecret,
184
+ writeSecretAtomic
185
+ });
186
+ //# sourceMappingURL=config-storage.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/config/storage.ts"],"sourcesContent":["import { createCipheriv, createDecipheriv, randomBytes, scrypt } from \"node:crypto\"\nimport {\n\tcloseSync,\n\texistsSync,\n\tfsyncSync,\n\topenSync,\n\treadFileSync,\n\trenameSync,\n\tunlinkSync,\n\twriteFileSync,\n} from \"node:fs\"\nimport { basename, dirname, join, resolve } from \"node:path\"\nimport type { Readable } from \"node:stream\"\n\nconst CONFIG_HEADER = \"SIMPLEX-ENCRYPTED-CONFIG-V1\\n\"\nconst MAX_CONFIG_BYTES = 2 * 1024 * 1024\nexport type SealedSecret = { iv: string; tag: string; data: string }\n\n/** Used for both the encrypted config and its wrapped key; no plaintext temp file is created. */\nexport function writeSecretAtomic(path: string, content: string): void {\n\tconst temp = join(dirname(path), `.${basename(path)}.${randomBytes(12).toString(\"hex\")}.tmp`)\n\tlet fd: number | undefined\n\ttry {\n\t\tfd = openSync(temp, \"wx\", 0o600)\n\t\twriteFileSync(fd, content)\n\t\tfsyncSync(fd)\n\t\tcloseSync(fd)\n\t\tfd = undefined\n\t\trenameSync(temp, path)\n\t\t// Persist the directory entry before a following encrypted write relies on\n\t\t// this file (notably the password-wrapped key written before migration).\n\t\tif (process.platform !== \"win32\") {\n\t\t\tconst directory = openSync(dirname(path), \"r\")\n\t\t\ttry {\n\t\t\t\tfsyncSync(directory)\n\t\t\t} finally {\n\t\t\t\tcloseSync(directory)\n\t\t\t}\n\t\t}\n\t} catch (error) {\n\t\tif (fd !== undefined) closeSync(fd)\n\t\ttry {\n\t\t\tunlinkSync(temp)\n\t\t} catch {}\n\t\tthrow error\n\t}\n}\n\nfunction bytes(value: unknown, length?: number): Buffer {\n\tif (typeof value !== \"string\" || value.length > MAX_CONFIG_BYTES * 2 || !/^(?:[a-f0-9]{2})*$/.test(value)) {\n\t\tthrow new Error(\"Invalid encrypted data\")\n\t}\n\tconst decoded = Buffer.from(value, \"hex\")\n\tif (length !== undefined && decoded.length !== length) throw new Error(\"Invalid encrypted data\")\n\treturn decoded\n}\n\nexport function sealSecret(plaintext: Buffer, key: Buffer, purpose: string): SealedSecret {\n\tif (key.length !== 32) throw new Error(\"Invalid encryption key\")\n\tconst iv = randomBytes(12)\n\tconst cipher = createCipheriv(\"aes-256-gcm\", key, iv)\n\tconst aad = Buffer.from(purpose)\n\tcipher.setAAD(aad)\n\tconst data = Buffer.concat([cipher.update(plaintext), cipher.final()])\n\treturn { iv: iv.toString(\"hex\"), tag: cipher.getAuthTag().toString(\"hex\"), data: data.toString(\"hex\") }\n}\n\nexport function openSecret(sealed: SealedSecret, key: Buffer, purpose: string): Buffer {\n\tif (key.length !== 32 || !sealed || typeof sealed !== \"object\") throw new Error(\"Invalid encrypted data\")\n\tconst decipher = createDecipheriv(\"aes-256-gcm\", key, bytes(sealed.iv, 12))\n\tdecipher.setAAD(Buffer.from(purpose))\n\tdecipher.setAuthTag(bytes(sealed.tag, 16))\n\treturn Buffer.concat([decipher.update(bytes(sealed.data)), decipher.final()])\n}\n\n/** Fixed work factors are versioned by the desktop vault, never supplied by an untrusted file. */\nexport function passwordKey(password: string, salt: string): Promise<Buffer> {\n\tconst saltBytes = bytes(salt, 16)\n\treturn new Promise((resolveKey, reject) => {\n\t\tscrypt(password, saltBytes, 32, { N: 131072, r: 8, p: 1, maxmem: 192 * 1024 * 1024 }, (error, key) => {\n\t\t\tif (error) reject(error)\n\t\t\telse resolveKey(key)\n\t\t})\n\t})\n}\n\nexport function isEncryptedConfig(content: string): boolean {\n\treturn content.startsWith(\"SIMPLEX-ENCRYPTED-CONFIG-\")\n}\n\nexport function encryptConfig(content: string, key: Buffer): string {\n\tif (Buffer.byteLength(content) > MAX_CONFIG_BYTES) throw new Error(\"Config is too large\")\n\tconst plaintext = Buffer.from(content)\n\ttry {\n\t\treturn CONFIG_HEADER + JSON.stringify(sealSecret(plaintext, key, \"simplex/config/v1\")) + \"\\n\"\n\t} finally {\n\t\tplaintext.fill(0)\n\t}\n}\n\nexport function decryptConfig(content: string, key: Buffer): string {\n\tif (!content.startsWith(CONFIG_HEADER) || content.length > MAX_CONFIG_BYTES * 2 + 1024) {\n\t\tthrow new Error(\"Unsupported or invalid encrypted config\")\n\t}\n\tconst plaintext = openSecret(JSON.parse(content.slice(CONFIG_HEADER.length)), key, \"simplex/config/v1\")\n\ttry {\n\t\treturn plaintext.toString(\"utf8\")\n\t} finally {\n\t\tplaintext.fill(0)\n\t}\n}\n\nexport function readConfigFile(path: string, key?: Buffer): string {\n\tconst content = readFileSync(path, \"utf8\")\n\tif (isEncryptedConfig(content)) {\n\t\tif (!key) throw new Error(\"This config is encrypted. Open Simplex Desktop to unlock it.\")\n\t\treturn decryptConfig(content, key)\n\t}\n\tif (key) throw new Error(\"Refusing to load a plaintext config in protected desktop mode\")\n\treturn content\n}\n\n/** One protected destination for initial setup and every subsequent runtime edit. */\nexport function encryptedConfigStore(path: string, key: Buffer) {\n\tconst target = resolve(path)\n\treturn {\n\t\tpath: target,\n\t\texists: () => existsSync(target),\n\t\tread: () => readConfigFile(target, key),\n\t\twrite: (requestedPath: string, content: string) => {\n\t\t\tif (resolve(requestedPath) !== target)\n\t\t\t\tthrow new Error(\"Desktop config must stay in its protected data directory\")\n\t\t\twriteSecretAtomic(target, encryptConfig(content, key))\n\t\t},\n\t}\n}\n\n/** A one-shot inherited pipe avoids keys in argv, the environment, or temporary files. */\nexport function readConfigKey(input: Readable, timeoutMs = 5_000): Promise<Buffer> {\n\treturn new Promise((resolveKey, reject) => {\n\t\tconst key = Buffer.alloc(32)\n\t\tlet offset = 0\n\t\tconst finish = (error?: Error) => {\n\t\t\tclearTimeout(timer)\n\t\t\tinput.removeListener(\"data\", onData)\n\t\t\tinput.removeListener(\"end\", onEnd)\n\t\t\tinput.removeListener(\"error\", onError)\n\t\t\tinput.destroy()\n\t\t\tif (error) {\n\t\t\t\tkey.fill(0)\n\t\t\t\treject(error)\n\t\t\t} else resolveKey(key)\n\t\t}\n\t\tconst onData = (chunk: Buffer) => {\n\t\t\tif (!Buffer.isBuffer(chunk) || offset + chunk.length > 32)\n\t\t\t\treturn finish(new Error(\"Invalid desktop key handoff\"))\n\t\t\tchunk.copy(key, offset)\n\t\t\toffset += chunk.length\n\t\t\tchunk.fill(0)\n\t\t}\n\t\tconst onEnd = () => finish(offset === 32 ? undefined : new Error(\"Incomplete desktop key handoff\"))\n\t\tconst onError = () => finish(new Error(\"Desktop key handoff failed\"))\n\t\tconst timer = setTimeout(() => finish(new Error(\"Desktop key handoff timed out\")), timeoutMs)\n\t\tinput.on(\"data\", onData).once(\"end\", onEnd).once(\"error\", onError)\n\t})\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,28 @@
1
+ import { Readable } from 'node:stream';
2
+
3
+ type SealedSecret = {
4
+ iv: string;
5
+ tag: string;
6
+ data: string;
7
+ };
8
+ /** Used for both the encrypted config and its wrapped key; no plaintext temp file is created. */
9
+ declare function writeSecretAtomic(path: string, content: string): void;
10
+ declare function sealSecret(plaintext: Buffer, key: Buffer, purpose: string): SealedSecret;
11
+ declare function openSecret(sealed: SealedSecret, key: Buffer, purpose: string): Buffer;
12
+ /** Fixed work factors are versioned by the desktop vault, never supplied by an untrusted file. */
13
+ declare function passwordKey(password: string, salt: string): Promise<Buffer>;
14
+ declare function isEncryptedConfig(content: string): boolean;
15
+ declare function encryptConfig(content: string, key: Buffer): string;
16
+ declare function decryptConfig(content: string, key: Buffer): string;
17
+ declare function readConfigFile(path: string, key?: Buffer): string;
18
+ /** One protected destination for initial setup and every subsequent runtime edit. */
19
+ declare function encryptedConfigStore(path: string, key: Buffer): {
20
+ path: string;
21
+ exists: () => boolean;
22
+ read: () => string;
23
+ write: (requestedPath: string, content: string) => void;
24
+ };
25
+ /** A one-shot inherited pipe avoids keys in argv, the environment, or temporary files. */
26
+ declare function readConfigKey(input: Readable, timeoutMs?: number): Promise<Buffer>;
27
+
28
+ export { type SealedSecret, decryptConfig, encryptConfig, encryptedConfigStore, isEncryptedConfig, openSecret, passwordKey, readConfigFile, readConfigKey, sealSecret, writeSecretAtomic };
@@ -0,0 +1,28 @@
1
+ import { Readable } from 'node:stream';
2
+
3
+ type SealedSecret = {
4
+ iv: string;
5
+ tag: string;
6
+ data: string;
7
+ };
8
+ /** Used for both the encrypted config and its wrapped key; no plaintext temp file is created. */
9
+ declare function writeSecretAtomic(path: string, content: string): void;
10
+ declare function sealSecret(plaintext: Buffer, key: Buffer, purpose: string): SealedSecret;
11
+ declare function openSecret(sealed: SealedSecret, key: Buffer, purpose: string): Buffer;
12
+ /** Fixed work factors are versioned by the desktop vault, never supplied by an untrusted file. */
13
+ declare function passwordKey(password: string, salt: string): Promise<Buffer>;
14
+ declare function isEncryptedConfig(content: string): boolean;
15
+ declare function encryptConfig(content: string, key: Buffer): string;
16
+ declare function decryptConfig(content: string, key: Buffer): string;
17
+ declare function readConfigFile(path: string, key?: Buffer): string;
18
+ /** One protected destination for initial setup and every subsequent runtime edit. */
19
+ declare function encryptedConfigStore(path: string, key: Buffer): {
20
+ path: string;
21
+ exists: () => boolean;
22
+ read: () => string;
23
+ write: (requestedPath: string, content: string) => void;
24
+ };
25
+ /** A one-shot inherited pipe avoids keys in argv, the environment, or temporary files. */
26
+ declare function readConfigKey(input: Readable, timeoutMs?: number): Promise<Buffer>;
27
+
28
+ export { type SealedSecret, decryptConfig, encryptConfig, encryptedConfigStore, isEncryptedConfig, openSecret, passwordKey, readConfigFile, readConfigKey, sealSecret, writeSecretAtomic };
@@ -0,0 +1,163 @@
1
+ import { createRequire } from 'module'; const require = createRequire(import.meta.url);
2
+
3
+ // src/config/storage.ts
4
+ import { createCipheriv, createDecipheriv, randomBytes, scrypt } from "node:crypto";
5
+ import {
6
+ closeSync,
7
+ existsSync,
8
+ fsyncSync,
9
+ openSync,
10
+ readFileSync,
11
+ renameSync,
12
+ unlinkSync,
13
+ writeFileSync
14
+ } from "node:fs";
15
+ import { basename, dirname, join, resolve } from "node:path";
16
+ var CONFIG_HEADER = "SIMPLEX-ENCRYPTED-CONFIG-V1\n";
17
+ var MAX_CONFIG_BYTES = 2 * 1024 * 1024;
18
+ function writeSecretAtomic(path, content) {
19
+ const temp = join(dirname(path), `.${basename(path)}.${randomBytes(12).toString("hex")}.tmp`);
20
+ let fd;
21
+ try {
22
+ fd = openSync(temp, "wx", 384);
23
+ writeFileSync(fd, content);
24
+ fsyncSync(fd);
25
+ closeSync(fd);
26
+ fd = void 0;
27
+ renameSync(temp, path);
28
+ if (process.platform !== "win32") {
29
+ const directory = openSync(dirname(path), "r");
30
+ try {
31
+ fsyncSync(directory);
32
+ } finally {
33
+ closeSync(directory);
34
+ }
35
+ }
36
+ } catch (error) {
37
+ if (fd !== void 0) closeSync(fd);
38
+ try {
39
+ unlinkSync(temp);
40
+ } catch {
41
+ }
42
+ throw error;
43
+ }
44
+ }
45
+ function bytes(value, length) {
46
+ if (typeof value !== "string" || value.length > MAX_CONFIG_BYTES * 2 || !/^(?:[a-f0-9]{2})*$/.test(value)) {
47
+ throw new Error("Invalid encrypted data");
48
+ }
49
+ const decoded = Buffer.from(value, "hex");
50
+ if (length !== void 0 && decoded.length !== length) throw new Error("Invalid encrypted data");
51
+ return decoded;
52
+ }
53
+ function sealSecret(plaintext, key, purpose) {
54
+ if (key.length !== 32) throw new Error("Invalid encryption key");
55
+ const iv = randomBytes(12);
56
+ const cipher = createCipheriv("aes-256-gcm", key, iv);
57
+ const aad = Buffer.from(purpose);
58
+ cipher.setAAD(aad);
59
+ const data = Buffer.concat([cipher.update(plaintext), cipher.final()]);
60
+ return { iv: iv.toString("hex"), tag: cipher.getAuthTag().toString("hex"), data: data.toString("hex") };
61
+ }
62
+ function openSecret(sealed, key, purpose) {
63
+ if (key.length !== 32 || !sealed || typeof sealed !== "object") throw new Error("Invalid encrypted data");
64
+ const decipher = createDecipheriv("aes-256-gcm", key, bytes(sealed.iv, 12));
65
+ decipher.setAAD(Buffer.from(purpose));
66
+ decipher.setAuthTag(bytes(sealed.tag, 16));
67
+ return Buffer.concat([decipher.update(bytes(sealed.data)), decipher.final()]);
68
+ }
69
+ function passwordKey(password, salt) {
70
+ const saltBytes = bytes(salt, 16);
71
+ return new Promise((resolveKey, reject) => {
72
+ scrypt(password, saltBytes, 32, { N: 131072, r: 8, p: 1, maxmem: 192 * 1024 * 1024 }, (error, key) => {
73
+ if (error) reject(error);
74
+ else resolveKey(key);
75
+ });
76
+ });
77
+ }
78
+ function isEncryptedConfig(content) {
79
+ return content.startsWith("SIMPLEX-ENCRYPTED-CONFIG-");
80
+ }
81
+ function encryptConfig(content, key) {
82
+ if (Buffer.byteLength(content) > MAX_CONFIG_BYTES) throw new Error("Config is too large");
83
+ const plaintext = Buffer.from(content);
84
+ try {
85
+ return CONFIG_HEADER + JSON.stringify(sealSecret(plaintext, key, "simplex/config/v1")) + "\n";
86
+ } finally {
87
+ plaintext.fill(0);
88
+ }
89
+ }
90
+ function decryptConfig(content, key) {
91
+ if (!content.startsWith(CONFIG_HEADER) || content.length > MAX_CONFIG_BYTES * 2 + 1024) {
92
+ throw new Error("Unsupported or invalid encrypted config");
93
+ }
94
+ const plaintext = openSecret(JSON.parse(content.slice(CONFIG_HEADER.length)), key, "simplex/config/v1");
95
+ try {
96
+ return plaintext.toString("utf8");
97
+ } finally {
98
+ plaintext.fill(0);
99
+ }
100
+ }
101
+ function readConfigFile(path, key) {
102
+ const content = readFileSync(path, "utf8");
103
+ if (isEncryptedConfig(content)) {
104
+ if (!key) throw new Error("This config is encrypted. Open Simplex Desktop to unlock it.");
105
+ return decryptConfig(content, key);
106
+ }
107
+ if (key) throw new Error("Refusing to load a plaintext config in protected desktop mode");
108
+ return content;
109
+ }
110
+ function encryptedConfigStore(path, key) {
111
+ const target = resolve(path);
112
+ return {
113
+ path: target,
114
+ exists: () => existsSync(target),
115
+ read: () => readConfigFile(target, key),
116
+ write: (requestedPath, content) => {
117
+ if (resolve(requestedPath) !== target)
118
+ throw new Error("Desktop config must stay in its protected data directory");
119
+ writeSecretAtomic(target, encryptConfig(content, key));
120
+ }
121
+ };
122
+ }
123
+ function readConfigKey(input, timeoutMs = 5e3) {
124
+ return new Promise((resolveKey, reject) => {
125
+ const key = Buffer.alloc(32);
126
+ let offset = 0;
127
+ const finish = (error) => {
128
+ clearTimeout(timer);
129
+ input.removeListener("data", onData);
130
+ input.removeListener("end", onEnd);
131
+ input.removeListener("error", onError);
132
+ input.destroy();
133
+ if (error) {
134
+ key.fill(0);
135
+ reject(error);
136
+ } else resolveKey(key);
137
+ };
138
+ const onData = (chunk) => {
139
+ if (!Buffer.isBuffer(chunk) || offset + chunk.length > 32)
140
+ return finish(new Error("Invalid desktop key handoff"));
141
+ chunk.copy(key, offset);
142
+ offset += chunk.length;
143
+ chunk.fill(0);
144
+ };
145
+ const onEnd = () => finish(offset === 32 ? void 0 : new Error("Incomplete desktop key handoff"));
146
+ const onError = () => finish(new Error("Desktop key handoff failed"));
147
+ const timer = setTimeout(() => finish(new Error("Desktop key handoff timed out")), timeoutMs);
148
+ input.on("data", onData).once("end", onEnd).once("error", onError);
149
+ });
150
+ }
151
+ export {
152
+ decryptConfig,
153
+ encryptConfig,
154
+ encryptedConfigStore,
155
+ isEncryptedConfig,
156
+ openSecret,
157
+ passwordKey,
158
+ readConfigFile,
159
+ readConfigKey,
160
+ sealSecret,
161
+ writeSecretAtomic
162
+ };
163
+ //# sourceMappingURL=config-storage.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/config/storage.ts"],"sourcesContent":["import { createCipheriv, createDecipheriv, randomBytes, scrypt } from \"node:crypto\"\nimport {\n\tcloseSync,\n\texistsSync,\n\tfsyncSync,\n\topenSync,\n\treadFileSync,\n\trenameSync,\n\tunlinkSync,\n\twriteFileSync,\n} from \"node:fs\"\nimport { basename, dirname, join, resolve } from \"node:path\"\nimport type { Readable } from \"node:stream\"\n\nconst CONFIG_HEADER = \"SIMPLEX-ENCRYPTED-CONFIG-V1\\n\"\nconst MAX_CONFIG_BYTES = 2 * 1024 * 1024\nexport type SealedSecret = { iv: string; tag: string; data: string }\n\n/** Used for both the encrypted config and its wrapped key; no plaintext temp file is created. */\nexport function writeSecretAtomic(path: string, content: string): void {\n\tconst temp = join(dirname(path), `.${basename(path)}.${randomBytes(12).toString(\"hex\")}.tmp`)\n\tlet fd: number | undefined\n\ttry {\n\t\tfd = openSync(temp, \"wx\", 0o600)\n\t\twriteFileSync(fd, content)\n\t\tfsyncSync(fd)\n\t\tcloseSync(fd)\n\t\tfd = undefined\n\t\trenameSync(temp, path)\n\t\t// Persist the directory entry before a following encrypted write relies on\n\t\t// this file (notably the password-wrapped key written before migration).\n\t\tif (process.platform !== \"win32\") {\n\t\t\tconst directory = openSync(dirname(path), \"r\")\n\t\t\ttry {\n\t\t\t\tfsyncSync(directory)\n\t\t\t} finally {\n\t\t\t\tcloseSync(directory)\n\t\t\t}\n\t\t}\n\t} catch (error) {\n\t\tif (fd !== undefined) closeSync(fd)\n\t\ttry {\n\t\t\tunlinkSync(temp)\n\t\t} catch {}\n\t\tthrow error\n\t}\n}\n\nfunction bytes(value: unknown, length?: number): Buffer {\n\tif (typeof value !== \"string\" || value.length > MAX_CONFIG_BYTES * 2 || !/^(?:[a-f0-9]{2})*$/.test(value)) {\n\t\tthrow new Error(\"Invalid encrypted data\")\n\t}\n\tconst decoded = Buffer.from(value, \"hex\")\n\tif (length !== undefined && decoded.length !== length) throw new Error(\"Invalid encrypted data\")\n\treturn decoded\n}\n\nexport function sealSecret(plaintext: Buffer, key: Buffer, purpose: string): SealedSecret {\n\tif (key.length !== 32) throw new Error(\"Invalid encryption key\")\n\tconst iv = randomBytes(12)\n\tconst cipher = createCipheriv(\"aes-256-gcm\", key, iv)\n\tconst aad = Buffer.from(purpose)\n\tcipher.setAAD(aad)\n\tconst data = Buffer.concat([cipher.update(plaintext), cipher.final()])\n\treturn { iv: iv.toString(\"hex\"), tag: cipher.getAuthTag().toString(\"hex\"), data: data.toString(\"hex\") }\n}\n\nexport function openSecret(sealed: SealedSecret, key: Buffer, purpose: string): Buffer {\n\tif (key.length !== 32 || !sealed || typeof sealed !== \"object\") throw new Error(\"Invalid encrypted data\")\n\tconst decipher = createDecipheriv(\"aes-256-gcm\", key, bytes(sealed.iv, 12))\n\tdecipher.setAAD(Buffer.from(purpose))\n\tdecipher.setAuthTag(bytes(sealed.tag, 16))\n\treturn Buffer.concat([decipher.update(bytes(sealed.data)), decipher.final()])\n}\n\n/** Fixed work factors are versioned by the desktop vault, never supplied by an untrusted file. */\nexport function passwordKey(password: string, salt: string): Promise<Buffer> {\n\tconst saltBytes = bytes(salt, 16)\n\treturn new Promise((resolveKey, reject) => {\n\t\tscrypt(password, saltBytes, 32, { N: 131072, r: 8, p: 1, maxmem: 192 * 1024 * 1024 }, (error, key) => {\n\t\t\tif (error) reject(error)\n\t\t\telse resolveKey(key)\n\t\t})\n\t})\n}\n\nexport function isEncryptedConfig(content: string): boolean {\n\treturn content.startsWith(\"SIMPLEX-ENCRYPTED-CONFIG-\")\n}\n\nexport function encryptConfig(content: string, key: Buffer): string {\n\tif (Buffer.byteLength(content) > MAX_CONFIG_BYTES) throw new Error(\"Config is too large\")\n\tconst plaintext = Buffer.from(content)\n\ttry {\n\t\treturn CONFIG_HEADER + JSON.stringify(sealSecret(plaintext, key, \"simplex/config/v1\")) + \"\\n\"\n\t} finally {\n\t\tplaintext.fill(0)\n\t}\n}\n\nexport function decryptConfig(content: string, key: Buffer): string {\n\tif (!content.startsWith(CONFIG_HEADER) || content.length > MAX_CONFIG_BYTES * 2 + 1024) {\n\t\tthrow new Error(\"Unsupported or invalid encrypted config\")\n\t}\n\tconst plaintext = openSecret(JSON.parse(content.slice(CONFIG_HEADER.length)), key, \"simplex/config/v1\")\n\ttry {\n\t\treturn plaintext.toString(\"utf8\")\n\t} finally {\n\t\tplaintext.fill(0)\n\t}\n}\n\nexport function readConfigFile(path: string, key?: Buffer): string {\n\tconst content = readFileSync(path, \"utf8\")\n\tif (isEncryptedConfig(content)) {\n\t\tif (!key) throw new Error(\"This config is encrypted. Open Simplex Desktop to unlock it.\")\n\t\treturn decryptConfig(content, key)\n\t}\n\tif (key) throw new Error(\"Refusing to load a plaintext config in protected desktop mode\")\n\treturn content\n}\n\n/** One protected destination for initial setup and every subsequent runtime edit. */\nexport function encryptedConfigStore(path: string, key: Buffer) {\n\tconst target = resolve(path)\n\treturn {\n\t\tpath: target,\n\t\texists: () => existsSync(target),\n\t\tread: () => readConfigFile(target, key),\n\t\twrite: (requestedPath: string, content: string) => {\n\t\t\tif (resolve(requestedPath) !== target)\n\t\t\t\tthrow new Error(\"Desktop config must stay in its protected data directory\")\n\t\t\twriteSecretAtomic(target, encryptConfig(content, key))\n\t\t},\n\t}\n}\n\n/** A one-shot inherited pipe avoids keys in argv, the environment, or temporary files. */\nexport function readConfigKey(input: Readable, timeoutMs = 5_000): Promise<Buffer> {\n\treturn new Promise((resolveKey, reject) => {\n\t\tconst key = Buffer.alloc(32)\n\t\tlet offset = 0\n\t\tconst finish = (error?: Error) => {\n\t\t\tclearTimeout(timer)\n\t\t\tinput.removeListener(\"data\", onData)\n\t\t\tinput.removeListener(\"end\", onEnd)\n\t\t\tinput.removeListener(\"error\", onError)\n\t\t\tinput.destroy()\n\t\t\tif (error) {\n\t\t\t\tkey.fill(0)\n\t\t\t\treject(error)\n\t\t\t} else resolveKey(key)\n\t\t}\n\t\tconst onData = (chunk: Buffer) => {\n\t\t\tif (!Buffer.isBuffer(chunk) || offset + chunk.length > 32)\n\t\t\t\treturn finish(new Error(\"Invalid desktop key handoff\"))\n\t\t\tchunk.copy(key, offset)\n\t\t\toffset += chunk.length\n\t\t\tchunk.fill(0)\n\t\t}\n\t\tconst onEnd = () => finish(offset === 32 ? undefined : new Error(\"Incomplete desktop key handoff\"))\n\t\tconst onError = () => finish(new Error(\"Desktop key handoff failed\"))\n\t\tconst timer = setTimeout(() => finish(new Error(\"Desktop key handoff timed out\")), timeoutMs)\n\t\tinput.on(\"data\", onData).once(\"end\", onEnd).once(\"error\", onError)\n\t})\n}\n"],"mappings":";;;AAAA,SAAS,gBAAgB,kBAAkB,aAAa,cAAc;AACtE;AAAA,EACC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACM;AACP,SAAS,UAAU,SAAS,MAAM,eAAe;AAGjD,IAAM,gBAAgB;AACtB,IAAM,mBAAmB,IAAI,OAAO;AAI7B,SAAS,kBAAkB,MAAc,SAAuB;AACtE,QAAM,OAAO,KAAK,QAAQ,IAAI,GAAG,IAAI,SAAS,IAAI,CAAC,IAAI,YAAY,EAAE,EAAE,SAAS,KAAK,CAAC,MAAM;AAC5F,MAAI;AACJ,MAAI;AACH,SAAK,SAAS,MAAM,MAAM,GAAK;AAC/B,kBAAc,IAAI,OAAO;AACzB,cAAU,EAAE;AACZ,cAAU,EAAE;AACZ,SAAK;AACL,eAAW,MAAM,IAAI;AAGrB,QAAI,QAAQ,aAAa,SAAS;AACjC,YAAM,YAAY,SAAS,QAAQ,IAAI,GAAG,GAAG;AAC7C,UAAI;AACH,kBAAU,SAAS;AAAA,MACpB,UAAE;AACD,kBAAU,SAAS;AAAA,MACpB;AAAA,IACD;AAAA,EACD,SAAS,OAAO;AACf,QAAI,OAAO,OAAW,WAAU,EAAE;AAClC,QAAI;AACH,iBAAW,IAAI;AAAA,IAChB,QAAQ;AAAA,IAAC;AACT,UAAM;AAAA,EACP;AACD;AAEA,SAAS,MAAM,OAAgB,QAAyB;AACvD,MAAI,OAAO,UAAU,YAAY,MAAM,SAAS,mBAAmB,KAAK,CAAC,qBAAqB,KAAK,KAAK,GAAG;AAC1G,UAAM,IAAI,MAAM,wBAAwB;AAAA,EACzC;AACA,QAAM,UAAU,OAAO,KAAK,OAAO,KAAK;AACxC,MAAI,WAAW,UAAa,QAAQ,WAAW,OAAQ,OAAM,IAAI,MAAM,wBAAwB;AAC/F,SAAO;AACR;AAEO,SAAS,WAAW,WAAmB,KAAa,SAA+B;AACzF,MAAI,IAAI,WAAW,GAAI,OAAM,IAAI,MAAM,wBAAwB;AAC/D,QAAM,KAAK,YAAY,EAAE;AACzB,QAAM,SAAS,eAAe,eAAe,KAAK,EAAE;AACpD,QAAM,MAAM,OAAO,KAAK,OAAO;AAC/B,SAAO,OAAO,GAAG;AACjB,QAAM,OAAO,OAAO,OAAO,CAAC,OAAO,OAAO,SAAS,GAAG,OAAO,MAAM,CAAC,CAAC;AACrE,SAAO,EAAE,IAAI,GAAG,SAAS,KAAK,GAAG,KAAK,OAAO,WAAW,EAAE,SAAS,KAAK,GAAG,MAAM,KAAK,SAAS,KAAK,EAAE;AACvG;AAEO,SAAS,WAAW,QAAsB,KAAa,SAAyB;AACtF,MAAI,IAAI,WAAW,MAAM,CAAC,UAAU,OAAO,WAAW,SAAU,OAAM,IAAI,MAAM,wBAAwB;AACxG,QAAM,WAAW,iBAAiB,eAAe,KAAK,MAAM,OAAO,IAAI,EAAE,CAAC;AAC1E,WAAS,OAAO,OAAO,KAAK,OAAO,CAAC;AACpC,WAAS,WAAW,MAAM,OAAO,KAAK,EAAE,CAAC;AACzC,SAAO,OAAO,OAAO,CAAC,SAAS,OAAO,MAAM,OAAO,IAAI,CAAC,GAAG,SAAS,MAAM,CAAC,CAAC;AAC7E;AAGO,SAAS,YAAY,UAAkB,MAA+B;AAC5E,QAAM,YAAY,MAAM,MAAM,EAAE;AAChC,SAAO,IAAI,QAAQ,CAAC,YAAY,WAAW;AAC1C,WAAO,UAAU,WAAW,IAAI,EAAE,GAAG,QAAQ,GAAG,GAAG,GAAG,GAAG,QAAQ,MAAM,OAAO,KAAK,GAAG,CAAC,OAAO,QAAQ;AACrG,UAAI,MAAO,QAAO,KAAK;AAAA,UAClB,YAAW,GAAG;AAAA,IACpB,CAAC;AAAA,EACF,CAAC;AACF;AAEO,SAAS,kBAAkB,SAA0B;AAC3D,SAAO,QAAQ,WAAW,2BAA2B;AACtD;AAEO,SAAS,cAAc,SAAiB,KAAqB;AACnE,MAAI,OAAO,WAAW,OAAO,IAAI,iBAAkB,OAAM,IAAI,MAAM,qBAAqB;AACxF,QAAM,YAAY,OAAO,KAAK,OAAO;AACrC,MAAI;AACH,WAAO,gBAAgB,KAAK,UAAU,WAAW,WAAW,KAAK,mBAAmB,CAAC,IAAI;AAAA,EAC1F,UAAE;AACD,cAAU,KAAK,CAAC;AAAA,EACjB;AACD;AAEO,SAAS,cAAc,SAAiB,KAAqB;AACnE,MAAI,CAAC,QAAQ,WAAW,aAAa,KAAK,QAAQ,SAAS,mBAAmB,IAAI,MAAM;AACvF,UAAM,IAAI,MAAM,yCAAyC;AAAA,EAC1D;AACA,QAAM,YAAY,WAAW,KAAK,MAAM,QAAQ,MAAM,cAAc,MAAM,CAAC,GAAG,KAAK,mBAAmB;AACtG,MAAI;AACH,WAAO,UAAU,SAAS,MAAM;AAAA,EACjC,UAAE;AACD,cAAU,KAAK,CAAC;AAAA,EACjB;AACD;AAEO,SAAS,eAAe,MAAc,KAAsB;AAClE,QAAM,UAAU,aAAa,MAAM,MAAM;AACzC,MAAI,kBAAkB,OAAO,GAAG;AAC/B,QAAI,CAAC,IAAK,OAAM,IAAI,MAAM,8DAA8D;AACxF,WAAO,cAAc,SAAS,GAAG;AAAA,EAClC;AACA,MAAI,IAAK,OAAM,IAAI,MAAM,+DAA+D;AACxF,SAAO;AACR;AAGO,SAAS,qBAAqB,MAAc,KAAa;AAC/D,QAAM,SAAS,QAAQ,IAAI;AAC3B,SAAO;AAAA,IACN,MAAM;AAAA,IACN,QAAQ,MAAM,WAAW,MAAM;AAAA,IAC/B,MAAM,MAAM,eAAe,QAAQ,GAAG;AAAA,IACtC,OAAO,CAAC,eAAuB,YAAoB;AAClD,UAAI,QAAQ,aAAa,MAAM;AAC9B,cAAM,IAAI,MAAM,0DAA0D;AAC3E,wBAAkB,QAAQ,cAAc,SAAS,GAAG,CAAC;AAAA,IACtD;AAAA,EACD;AACD;AAGO,SAAS,cAAc,OAAiB,YAAY,KAAwB;AAClF,SAAO,IAAI,QAAQ,CAAC,YAAY,WAAW;AAC1C,UAAM,MAAM,OAAO,MAAM,EAAE;AAC3B,QAAI,SAAS;AACb,UAAM,SAAS,CAAC,UAAkB;AACjC,mBAAa,KAAK;AAClB,YAAM,eAAe,QAAQ,MAAM;AACnC,YAAM,eAAe,OAAO,KAAK;AACjC,YAAM,eAAe,SAAS,OAAO;AACrC,YAAM,QAAQ;AACd,UAAI,OAAO;AACV,YAAI,KAAK,CAAC;AACV,eAAO,KAAK;AAAA,MACb,MAAO,YAAW,GAAG;AAAA,IACtB;AACA,UAAM,SAAS,CAAC,UAAkB;AACjC,UAAI,CAAC,OAAO,SAAS,KAAK,KAAK,SAAS,MAAM,SAAS;AACtD,eAAO,OAAO,IAAI,MAAM,6BAA6B,CAAC;AACvD,YAAM,KAAK,KAAK,MAAM;AACtB,gBAAU,MAAM;AAChB,YAAM,KAAK,CAAC;AAAA,IACb;AACA,UAAM,QAAQ,MAAM,OAAO,WAAW,KAAK,SAAY,IAAI,MAAM,gCAAgC,CAAC;AAClG,UAAM,UAAU,MAAM,OAAO,IAAI,MAAM,4BAA4B,CAAC;AACpE,UAAM,QAAQ,WAAW,MAAM,OAAO,IAAI,MAAM,+BAA+B,CAAC,GAAG,SAAS;AAC5F,UAAM,GAAG,QAAQ,MAAM,EAAE,KAAK,OAAO,KAAK,EAAE,KAAK,SAAS,OAAO;AAAA,EAClE,CAAC;AACF;","names":[]}