@sebastienrousseau/crypto-cli 0.0.9 → 0.0.25

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,247 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.registerCliKmsProvider = registerCliKmsProvider;
4
+ exports.resetCliKmsProviders = resetCliKmsProviders;
5
+ exports.resolveCliKmsProvider = resolveCliKmsProvider;
6
+ exports.registerKms = registerKms;
7
+ const commander_1 = require("commander");
8
+ const crypto_kms_1 = require("@sebastienrousseau/crypto-kms");
9
+ const io_1 = require("./io");
10
+ const providerRegistry = new Map();
11
+ providerRegistry.set("local", new crypto_kms_1.LocalKmsProvider());
12
+ function registerCliKmsProvider(name, provider) {
13
+ providerRegistry.set(name.toLowerCase(), provider);
14
+ }
15
+ function resetCliKmsProviders() {
16
+ providerRegistry.clear();
17
+ providerRegistry.set("local", new crypto_kms_1.LocalKmsProvider());
18
+ }
19
+ function createCliAwsProvider() {
20
+ return new crypto_kms_1.AwsKmsProvider({
21
+ region: process.env["AWS_REGION"] ?? "us-east-1",
22
+ });
23
+ }
24
+ function createCliGcpProvider() {
25
+ return new crypto_kms_1.GcpKmsProvider({
26
+ projectId: process.env["GCP_PROJECT_ID"] ?? "test-project",
27
+ locationId: process.env["GCP_LOCATION_ID"] ?? "global",
28
+ keyRingId: process.env["GCP_KEY_RING_ID"] ?? "test-ring",
29
+ token: process.env["GCP_AUTH_TOKEN"] ?? "test-token",
30
+ });
31
+ }
32
+ function createCliVaultProvider() {
33
+ return new crypto_kms_1.VaultKmsProvider({
34
+ address: process.env["VAULT_ADDR"] ?? "http://localhost:8200",
35
+ token: process.env["VAULT_TOKEN"] ?? "test-token",
36
+ });
37
+ }
38
+ function createCliAzureProvider() {
39
+ return new crypto_kms_1.AzureKmsProvider({
40
+ vaultUrl: process.env["AZURE_VAULT_URL"] ?? "https://my-vault.vault.azure.net",
41
+ });
42
+ }
43
+ function createDefaultCliProvider(key, name) {
44
+ switch (key) {
45
+ case "aws":
46
+ return createCliAwsProvider();
47
+ case "gcp":
48
+ return createCliGcpProvider();
49
+ case "vault":
50
+ return createCliVaultProvider();
51
+ case "azure":
52
+ return createCliAzureProvider();
53
+ default:
54
+ throw new Error(`Unsupported KMS provider: ${name}`);
55
+ }
56
+ }
57
+ function resolveCliKmsProvider(name) {
58
+ const key = (name ?? "local").toLowerCase();
59
+ const existing = providerRegistry.get(key);
60
+ if (existing)
61
+ return existing;
62
+ const provider = createDefaultCliProvider(key, name);
63
+ providerRegistry.set(key, provider);
64
+ return provider;
65
+ }
66
+ function parseBytes(text) {
67
+ const isHex = /^[0-9a-fA-F]*$/.test(text) && text.length % 2 === 0;
68
+ return isHex
69
+ ? new Uint8Array(Buffer.from(text, "hex"))
70
+ : new Uint8Array(Buffer.from(text, "utf8"));
71
+ }
72
+ async function executeCreateKey(opts, ctx) {
73
+ const provider = resolveCliKmsProvider(opts.provider);
74
+ const meta = await provider.createKey(opts.algorithm, opts.usage);
75
+ const desc = [
76
+ `Key ID: ${meta.keyId}`,
77
+ `Algorithm: ${meta.algorithm}`,
78
+ `Usage: ${meta.usage}`,
79
+ `Provider: ${meta.provider}`,
80
+ ].join("\n");
81
+ (0, io_1.emit)(ctx.io, Boolean(opts.json), meta, desc);
82
+ }
83
+ async function executeWrap(file, opts, ctx) {
84
+ const rawKey = opts.key
85
+ ? Buffer.from(opts.key, "utf8")
86
+ : await (0, io_1.readInput)(file, ctx.io);
87
+ const text = (0, io_1.valueOrJsonField)(rawKey, "key").trim();
88
+ const bytes = parseBytes(text);
89
+ const provider = resolveCliKmsProvider(opts.provider);
90
+ if (!provider.wrapKey) {
91
+ throw new Error(`Provider ${provider.name} does not support wrapKey`);
92
+ }
93
+ const res = await provider.wrapKey(opts.keyId, bytes);
94
+ const desc = [
95
+ `Key ID: ${res.keyId}`,
96
+ `Wrapped Key: ${res.wrappedKey}`,
97
+ `Provider: ${provider.name}`,
98
+ ].join("\n");
99
+ (0, io_1.emit)(ctx.io, Boolean(opts.json), { ...res, provider: provider.name }, desc);
100
+ }
101
+ async function executeUnwrap(file, opts, ctx) {
102
+ const rawWk = opts.wrappedKey
103
+ ? Buffer.from(opts.wrappedKey, "utf8")
104
+ : await (0, io_1.readInput)(file, ctx.io);
105
+ const wrappedKey = (0, io_1.valueOrJsonField)(rawWk, "wrappedKey").trim();
106
+ const provider = resolveCliKmsProvider(opts.provider);
107
+ if (!provider.unwrapKey) {
108
+ throw new Error(`Provider ${provider.name} does not support unwrapKey`);
109
+ }
110
+ const res = await provider.unwrapKey(opts.keyId, wrappedKey);
111
+ const hex = Buffer.from(res.unwrappedKey).toString("hex");
112
+ const data = { unwrappedKey: hex, keyId: res.keyId, provider: provider.name };
113
+ const desc = [
114
+ `Key ID: ${res.keyId}`,
115
+ `Unwrapped Key: ${hex}`,
116
+ `Provider: ${provider.name}`,
117
+ ].join("\n");
118
+ (0, io_1.emit)(ctx.io, Boolean(opts.json), data, desc);
119
+ }
120
+ async function executeDataKey(opts, ctx) {
121
+ const provider = resolveCliKmsProvider(opts.provider);
122
+ const res = await provider.generateDataKey(opts.keyId, opts.keySpec);
123
+ const hex = Buffer.from(res.plaintext).toString("hex");
124
+ const data = {
125
+ plaintext: hex,
126
+ ciphertext: res.ciphertext,
127
+ keyId: opts.keyId,
128
+ provider: provider.name,
129
+ };
130
+ const desc = [
131
+ `Key ID: ${opts.keyId}`,
132
+ `Plaintext: ${hex}`,
133
+ `Ciphertext: ${res.ciphertext}`,
134
+ `Provider: ${provider.name}`,
135
+ ].join("\n");
136
+ (0, io_1.emit)(ctx.io, Boolean(opts.json), data, desc);
137
+ }
138
+ async function executeEncrypt(file, opts, ctx) {
139
+ const raw = opts.data
140
+ ? Buffer.from(opts.data, "utf8")
141
+ : await (0, io_1.readInput)(file, ctx.io);
142
+ const text = (0, io_1.valueOrJsonField)(raw, "data").trim();
143
+ const bytes = parseBytes(text);
144
+ const provider = resolveCliKmsProvider(opts.provider);
145
+ const res = await provider.encrypt(opts.keyId, bytes);
146
+ const data = { ...res, provider: provider.name };
147
+ const desc = [
148
+ `Key ID: ${res.keyId}`,
149
+ `Ciphertext: ${res.ciphertext}`,
150
+ `Provider: ${provider.name}`,
151
+ ].join("\n");
152
+ (0, io_1.emit)(ctx.io, Boolean(opts.json), data, desc);
153
+ }
154
+ async function executeDecrypt(file, opts, ctx) {
155
+ const raw = opts.ciphertext
156
+ ? Buffer.from(opts.ciphertext, "utf8")
157
+ : await (0, io_1.readInput)(file, ctx.io);
158
+ const ct = (0, io_1.valueOrJsonField)(raw, "ciphertext").trim();
159
+ const provider = resolveCliKmsProvider(opts.provider);
160
+ const res = await provider.decrypt(opts.keyId, ct);
161
+ const text = Buffer.from(res.plaintext).toString("utf8");
162
+ const hex = Buffer.from(res.plaintext).toString("hex");
163
+ const data = {
164
+ plaintext: text,
165
+ hex,
166
+ keyId: res.keyId,
167
+ provider: provider.name,
168
+ };
169
+ (0, io_1.emit)(ctx.io, Boolean(opts.json), data, text);
170
+ }
171
+ function registerKmsKeyOps(kms, ctx) {
172
+ kms
173
+ .command("create-key")
174
+ .description("Create a new managed key in KMS")
175
+ .option("-a, --algorithm <alg>", "Key algorithm", "aes-256-gcm")
176
+ .addOption(new commander_1.Option("-u, --usage <usage>", "Key usage")
177
+ .choices(["encrypt", "sign", "wrap"])
178
+ .default("encrypt"))
179
+ .option("-p, --provider <name>", "KMS provider (local, aws, gcp, vault, azure)", "local")
180
+ .option("--json", "Emit output as JSON")
181
+ .action(async (opts) => {
182
+ await executeCreateKey(opts, ctx);
183
+ });
184
+ kms
185
+ .command("generate-data-key")
186
+ .description("Generate a Data Encryption Key (DEK) via KMS")
187
+ .requiredOption("-k, --key-id <id>", "Key ID")
188
+ .option("-s, --key-spec <spec>", "Key spec (e.g. AES_256)", "AES_256")
189
+ .option("-p, --provider <name>", "KMS provider", "local")
190
+ .option("--json", "Emit output as JSON")
191
+ .action(async (opts) => {
192
+ await executeDataKey(opts, ctx);
193
+ });
194
+ }
195
+ function registerKmsWrapOps(kms, ctx) {
196
+ kms
197
+ .command("wrap [file]")
198
+ .description("Wrap (encrypt) a key using a KMS key")
199
+ .requiredOption("-k, --key-id <id>", "Wrapping Key ID")
200
+ .option("-w, --key <hex>", "Raw key material to wrap")
201
+ .option("-p, --provider <name>", "KMS provider", "local")
202
+ .option("--json", "Emit output as JSON")
203
+ .action(async (file, opts) => {
204
+ await executeWrap(file, opts, ctx);
205
+ });
206
+ kms
207
+ .command("unwrap [file]")
208
+ .description("Unwrap a wrapped key using a KMS key")
209
+ .requiredOption("-k, --key-id <id>", "Wrapping Key ID")
210
+ .option("-w, --wrapped-key <str>", "Wrapped key string")
211
+ .option("-p, --provider <name>", "KMS provider", "local")
212
+ .option("--json", "Emit output as JSON")
213
+ .action(async (file, opts) => {
214
+ await executeUnwrap(file, opts, ctx);
215
+ });
216
+ }
217
+ function registerKmsCipherOps(kms, ctx) {
218
+ kms
219
+ .command("encrypt [file]")
220
+ .description("Encrypt data using a KMS key")
221
+ .requiredOption("-k, --key-id <id>", "Key ID")
222
+ .option("-d, --data <text>", "Plaintext data to encrypt")
223
+ .option("-p, --provider <name>", "KMS provider", "local")
224
+ .option("--json", "Emit output as JSON")
225
+ .action(async (file, opts) => {
226
+ await executeEncrypt(file, opts, ctx);
227
+ });
228
+ kms
229
+ .command("decrypt [file]")
230
+ .description("Decrypt ciphertext using a KMS key")
231
+ .requiredOption("-k, --key-id <id>", "Key ID")
232
+ .option("-c, --ciphertext <str>", "Ciphertext to decrypt")
233
+ .option("-p, --provider <name>", "KMS provider", "local")
234
+ .option("--json", "Emit output as JSON")
235
+ .action(async (file, opts) => {
236
+ await executeDecrypt(file, opts, ctx);
237
+ });
238
+ }
239
+ function registerKms(program, ctx) {
240
+ const kms = program
241
+ .command("kms")
242
+ .description("Key Management Service (KMS) operations across native cloud providers");
243
+ registerKmsKeyOps(kms, ctx);
244
+ registerKmsWrapOps(kms, ctx);
245
+ registerKmsCipherOps(kms, ctx);
246
+ }
247
+ //# sourceMappingURL=kms.js.map
@@ -0,0 +1,4 @@
1
+ import { Command } from "commander";
2
+ import { type RunContext } from "./io";
3
+ export declare const registerOpaque: (program: Command, ctx: RunContext) => Command;
4
+ //# sourceMappingURL=opaque.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"opaque.d.ts","sourceRoot":"","sources":["../../src/program/opaque.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,OAAO,EAAU,MAAM,WAAW,CAAC;AAE5C,OAAO,EAAoB,KAAK,UAAU,EAAE,MAAM,MAAM,CAAC;AAwUzD,eAAO,MAAM,cAAc,GAAI,SAAS,OAAO,EAAE,KAAK,UAAU,KAAG,OASlE,CAAC"}
@@ -0,0 +1,209 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.registerOpaque = void 0;
4
+ const commander_1 = require("commander");
5
+ const crypto_lib_1 = require("@sebastienrousseau/crypto-lib");
6
+ const io_1 = require("./io");
7
+ const secrets_1 = require("./secrets");
8
+ const { pake } = crypto_lib_1.protocols;
9
+ const SUITE_CHOICES = [
10
+ "P256-SHA256",
11
+ "ristretto255-SHA512",
12
+ ];
13
+ const DEFAULT_SERVER_URL = "http://localhost:3000";
14
+ function parseHex(hex) {
15
+ return new Uint8Array(Buffer.from(hex, "hex"));
16
+ }
17
+ function toHex(bytes) {
18
+ return Buffer.from(bytes).toString("hex");
19
+ }
20
+ function getServerUrl(opts) {
21
+ return (opts.serverUrl || process.env["CRYPTO_SERVER_URL"] || DEFAULT_SERVER_URL);
22
+ }
23
+ async function postJson(baseUrl, path, body) {
24
+ const url = `${baseUrl.replace(/\/+$/, "")}${path}`;
25
+ let res;
26
+ try {
27
+ res = await fetch(url, {
28
+ method: "POST",
29
+ headers: { "Content-Type": "application/json" },
30
+ body: JSON.stringify(body),
31
+ });
32
+ }
33
+ catch (err) {
34
+ throw new Error(`failed to connect to server at ${url}: ${err.message}`, { cause: err });
35
+ }
36
+ const text = await res.text();
37
+ let json;
38
+ try {
39
+ json = JSON.parse(text);
40
+ }
41
+ catch (parseErr) {
42
+ throw new Error(`server returned non-JSON response (HTTP ${res.status}): ${text.slice(0, 100)}`, { cause: parseErr });
43
+ }
44
+ if (!res.ok) {
45
+ const p = json;
46
+ const msg = p.detail ?? p.title ?? `HTTP ${res.status}`;
47
+ throw new Error(`OPAQUE server error (${res.status}): ${msg}`);
48
+ }
49
+ return json;
50
+ }
51
+ async function promptPassword(ctx, confirm) {
52
+ const ask = ctx.io.promptSecret;
53
+ if (ask === undefined) {
54
+ throw new io_1.UsageError("missing password: pass --password-file <path> or " +
55
+ "--password-stdin (a prompt needs a terminal on stdin)");
56
+ }
57
+ const password = (await ask("Password")) ?? "";
58
+ if (confirm && (await ask("Repeat the password")) !== password) {
59
+ throw new Error("the passwords do not match");
60
+ }
61
+ return password;
62
+ }
63
+ async function readPassword(ctx, opts, confirm) {
64
+ const source = {
65
+ file: opts.passwordFile,
66
+ stdin: opts.passwordStdin,
67
+ };
68
+ const raw = await (0, secrets_1.readSecret)({
69
+ io: ctx.io,
70
+ source,
71
+ flags: secrets_1.PASSWORD_FLAGS,
72
+ dataOnStdin: false,
73
+ checkMode: true,
74
+ });
75
+ const password = raw === undefined
76
+ ? await promptPassword(ctx, confirm)
77
+ : raw.toString("utf8").replace(/\r?\n$/, "");
78
+ if (password === "")
79
+ throw new Error("the password is empty");
80
+ return password;
81
+ }
82
+ async function handleSetup(ctx, opts) {
83
+ const suite = opts.suite;
84
+ const setup = pake.createServerSetup({ suite });
85
+ const fake = pake.createFakeRecord({ suite });
86
+ const result = {
87
+ suite,
88
+ serverPublicKey: toHex(setup.serverPublicKey),
89
+ serverPrivateKey: toHex(setup.serverPrivateKey),
90
+ oprfSeed: toHex(setup.oprfSeed),
91
+ fakeRecord: toHex(pake.serializeRegistrationRecord(fake)),
92
+ };
93
+ const formatted = [
94
+ `OPAQUE Server Setup (${suite}):`,
95
+ `Server Public Key: ${result.serverPublicKey}`,
96
+ `Server Private Key: ${result.serverPrivateKey}`,
97
+ `OPRF Seed: ${result.oprfSeed}`,
98
+ `Fake Record: ${result.fakeRecord}`,
99
+ ].join("\n");
100
+ (0, io_1.emit)(ctx.io, Boolean(opts.json), result, formatted);
101
+ }
102
+ async function handleRegister(id, ctx, opts) {
103
+ const suite = opts.suite;
104
+ const serverUrl = getServerUrl(opts);
105
+ const password = await readPassword(ctx, opts, true);
106
+ const { request, blind } = pake.createRegistrationRequest(password, {
107
+ suite,
108
+ });
109
+ const initRes = await postJson(serverUrl, "/v2/opaque/register/init", {
110
+ credentialIdentifier: id,
111
+ request: toHex(pake.serializeRegistrationRequest(request)),
112
+ suite,
113
+ });
114
+ const responseBytes = parseHex(initRes.data.response);
115
+ const regResponse = pake.deserializeRegistrationResponse(responseBytes, suite);
116
+ const { record, exportKey } = pake.finalizeRegistrationRequest(password, blind, regResponse, {}, { suite });
117
+ await postJson(serverUrl, "/v2/opaque/register/finish", {
118
+ credentialIdentifier: id,
119
+ record: toHex(pake.serializeRegistrationRecord(record)),
120
+ suite,
121
+ });
122
+ const result = {
123
+ status: "registered",
124
+ credentialIdentifier: id,
125
+ suite,
126
+ exportKey: toHex(exportKey),
127
+ };
128
+ (0, io_1.emit)(ctx.io, Boolean(opts.json), result, `Registered credential '${id}' successfully`);
129
+ }
130
+ async function handleLogin(id, ctx, opts) {
131
+ const suite = opts.suite;
132
+ const serverUrl = getServerUrl(opts);
133
+ const password = await readPassword(ctx, opts, false);
134
+ const client = pake.generateKE1(password, { suite });
135
+ const initRes = await postJson(serverUrl, "/v2/opaque/login/init", {
136
+ credentialIdentifier: id,
137
+ ke1: toHex(pake.serializeKE1(client.ke1)),
138
+ suite,
139
+ });
140
+ const ke2Bytes = parseHex(initRes.data.ke2);
141
+ const ke2 = pake.deserializeKE2(ke2Bytes, suite);
142
+ const { ke3, sessionKey, exportKey } = pake.generateKE3(client.state, ke2, {});
143
+ const finishRes = await postJson(serverUrl, "/v2/opaque/login/finish", {
144
+ sessionId: initRes.data.sessionId,
145
+ ke3: toHex(pake.serializeKE3(ke3)),
146
+ });
147
+ const expectedKey = toHex(sessionKey);
148
+ if (finishRes.data.sessionKey !== expectedKey) {
149
+ throw new Error("server session key does not match client session key");
150
+ }
151
+ const result = {
152
+ status: "authenticated",
153
+ credentialIdentifier: id,
154
+ suite,
155
+ sessionKey: expectedKey,
156
+ exportKey: toHex(exportKey),
157
+ };
158
+ (0, io_1.emit)(ctx.io, Boolean(opts.json), result, `Authenticated as '${id}'. Session key: ${expectedKey}`);
159
+ }
160
+ function registerSetup(parent, ctx) {
161
+ parent
162
+ .command("setup")
163
+ .description("Generate server long-term keys and fake record")
164
+ .addOption(new commander_1.Option("-s, --suite <name>", "ciphersuite")
165
+ .choices(SUITE_CHOICES)
166
+ .default("P256-SHA256"))
167
+ .option("--json", "print result as JSON")
168
+ .action(async (opts) => handleSetup(ctx, opts));
169
+ }
170
+ function registerRegister(parent, ctx) {
171
+ (0, secrets_1.withSecretOptions)(parent
172
+ .command("register <id>")
173
+ .description("Register a password with an OPAQUE server")
174
+ .option("-u, --server-url <url>", "server base URL")
175
+ .addOption(new commander_1.Option("-s, --suite <name>", "ciphersuite")
176
+ .choices(SUITE_CHOICES)
177
+ .default("P256-SHA256"))
178
+ .option("--json", "print result as JSON"), secrets_1.PASSWORD_FLAGS, {
179
+ what: "password",
180
+ format: "one trailing line break is ignored",
181
+ takesData: false,
182
+ }).action(async (id, opts) => handleRegister(id, ctx, opts));
183
+ }
184
+ function registerLogin(parent, ctx) {
185
+ (0, secrets_1.withSecretOptions)(parent
186
+ .command("login <id>")
187
+ .description("Authenticate against an OPAQUE server")
188
+ .option("-u, --server-url <url>", "server base URL")
189
+ .addOption(new commander_1.Option("-s, --suite <name>", "ciphersuite")
190
+ .choices(SUITE_CHOICES)
191
+ .default("P256-SHA256"))
192
+ .option("--json", "print result as JSON"), secrets_1.PASSWORD_FLAGS, {
193
+ what: "password",
194
+ format: "one trailing line break is ignored",
195
+ takesData: false,
196
+ }).action(async (id, opts) => handleLogin(id, ctx, opts));
197
+ }
198
+ const registerOpaque = (program, ctx) => {
199
+ const opaque = program
200
+ .command("opaque")
201
+ .description("OPAQUE zero-knowledge password authentication (RFC 9807)")
202
+ .helpCommand(false);
203
+ registerSetup(opaque, ctx);
204
+ registerRegister(opaque, ctx);
205
+ registerLogin(opaque, ctx);
206
+ return opaque;
207
+ };
208
+ exports.registerOpaque = registerOpaque;
209
+ //# sourceMappingURL=opaque.js.map
@@ -0,0 +1,7 @@
1
+ import { Command } from "commander";
2
+ import { type MultiPqRecipient } from "@sebastienrousseau/crypto-lib/streaming";
3
+ import { type RunContext } from "./io";
4
+ export declare const parseMultiChunkSize: (value?: string) => number | undefined;
5
+ export declare const parseRecipients: (value?: string) => MultiPqRecipient[];
6
+ export declare const registerStreamMulti: (stream: Command, ctx: RunContext) => void;
7
+ //# sourceMappingURL=stream-multi.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stream-multi.d.ts","sourceRoot":"","sources":["../../src/program/stream-multi.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEpC,OAAO,EAGL,KAAK,gBAAgB,EACtB,MAAM,yCAAyC,CAAC;AACjD,OAAO,EAA+B,KAAK,UAAU,EAAE,MAAM,MAAM,CAAC;AAIpE,eAAO,MAAM,mBAAmB,GAAI,QAAQ,MAAM,KAAG,MAAM,GAAG,SAS7D,CAAC;AAoCF,eAAO,MAAM,eAAe,GAAI,QAAQ,MAAM,KAAG,gBAAgB,EAsBhE,CAAC;AA4HF,eAAO,MAAM,mBAAmB,GAAI,QAAQ,OAAO,EAAE,KAAK,UAAU,KAAG,IAGtE,CAAC"}
@@ -0,0 +1,163 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.registerStreamMulti = exports.parseRecipients = exports.parseMultiChunkSize = void 0;
37
+ const fs = __importStar(require("node:fs"));
38
+ const crypto_lib_1 = require("@sebastienrousseau/crypto-lib");
39
+ const streaming_1 = require("@sebastienrousseau/crypto-lib/streaming");
40
+ const io_1 = require("./io");
41
+ const stream_1 = require("./stream");
42
+ const parseMultiChunkSize = (value) => {
43
+ if (value === undefined)
44
+ return undefined;
45
+ const size = Number(value);
46
+ if (!Number.isSafeInteger(size) || size < 1024 || size > 16777216) {
47
+ throw new io_1.UsageError("--chunk-size must be an integer between 1024 and 16777216");
48
+ }
49
+ return size;
50
+ };
51
+ exports.parseMultiChunkSize = parseMultiChunkSize;
52
+ const validateRecipientItem = (item) => {
53
+ if (!item ||
54
+ typeof item !== "object" ||
55
+ typeof item.recipientId !== "string" ||
56
+ typeof item.recipientX25519Public !==
57
+ "string" ||
58
+ typeof item.recipientMlKemPublic !== "string") {
59
+ throw new io_1.UsageError("each recipient must have recipientId, recipientX25519Public, and recipientMlKemPublic strings");
60
+ }
61
+ return item;
62
+ };
63
+ const parseRecipients = (value) => {
64
+ if (!value) {
65
+ throw new io_1.UsageError("missing recipients: --recipients is required (JSON array or file path)");
66
+ }
67
+ let raw = value.trim();
68
+ if (!raw.startsWith("[") && fs.existsSync(raw)) {
69
+ raw = fs.readFileSync(raw, "utf8").trim();
70
+ }
71
+ let parsed;
72
+ try {
73
+ parsed = JSON.parse(raw);
74
+ }
75
+ catch {
76
+ throw new io_1.UsageError("invalid JSON in --recipients");
77
+ }
78
+ if (!Array.isArray(parsed) || parsed.length === 0) {
79
+ throw new io_1.UsageError("--recipients must be a non-empty array of recipient objects");
80
+ }
81
+ return parsed.map(validateRecipientItem);
82
+ };
83
+ exports.parseRecipients = parseRecipients;
84
+ const registerStreamMultiEncrypt = (stream, ctx) => {
85
+ stream
86
+ .command("multi-encrypt")
87
+ .description("Encrypt for multiple recipients with post-quantum hybrid STREAM AEAD")
88
+ .argument("[file]", "file to encrypt; '-' or omitted reads standard input")
89
+ .option("-r, --recipients <json-or-path>", "JSON array string or file path containing recipient public key descriptors")
90
+ .option("-c, --chunk-size <bytes>", "chunk size in bytes (minimum 1024, default 65536)")
91
+ .option("--json", "print the result as one line of JSON")
92
+ .action(async (file, opts) => {
93
+ const recipients = (0, exports.parseRecipients)(opts.recipients);
94
+ const chunkSize = (0, exports.parseMultiChunkSize)(opts.chunkSize);
95
+ const input = await (0, io_1.readInput)(file, ctx.io);
96
+ const result = (0, streaming_1.streamMultiPqEncrypt)({
97
+ recipients,
98
+ plaintext: input,
99
+ ...(chunkSize !== undefined ? { chunkSize } : {}),
100
+ });
101
+ const b64 = Buffer.from(result.ciphertext).toString("base64");
102
+ (0, io_1.emit)(ctx.io, Boolean(opts.json), {
103
+ ciphertext: b64,
104
+ algorithm: result.algorithm,
105
+ recipientCount: result.recipientCount,
106
+ }, b64);
107
+ });
108
+ };
109
+ const executeStreamMultiDecrypt = (ctx, opts, ciphertext, chunkSize) => {
110
+ let decResult;
111
+ try {
112
+ decResult = (0, streaming_1.streamMultiPqDecrypt)({
113
+ recipientX25519Secret: opts.x25519Secret,
114
+ recipientMlKemSecret: opts.mlKemSecret,
115
+ ...(opts.recipientId !== undefined
116
+ ? { recipientId: opts.recipientId }
117
+ : {}),
118
+ ciphertext,
119
+ ...(chunkSize !== undefined ? { chunkSize } : {}),
120
+ });
121
+ }
122
+ catch {
123
+ throw new Error("decryption failed: wrong key, or the input was modified or truncated");
124
+ }
125
+ if (!opts.json) {
126
+ ctx.io.stdout(decResult.plaintext);
127
+ (0, crypto_lib_1.wipeMemory)(decResult.plaintext);
128
+ return;
129
+ }
130
+ const b64 = Buffer.from(decResult.plaintext).toString("base64");
131
+ (0, crypto_lib_1.wipeMemory)(decResult.plaintext);
132
+ (0, io_1.emit)(ctx.io, true, {
133
+ plaintext: b64,
134
+ recipientId: decResult.recipientId,
135
+ encoding: "base64",
136
+ algorithm: "multi-x25519-ml-kem-768-xchacha20-poly1305-stream",
137
+ }, "");
138
+ };
139
+ const registerStreamMultiDecrypt = (stream, ctx) => {
140
+ stream
141
+ .command("multi-decrypt")
142
+ .description("Decrypt multi-recipient post-quantum hybrid STREAM ciphertext; exits 1 on error")
143
+ .argument("[file]", "output of stream multi-encrypt (base64 or its --json line)")
144
+ .option("-x, --x25519-secret <hex>", "recipient X25519 secret key (hex)")
145
+ .option("-m, --ml-kem-secret <hex>", "recipient ML-KEM-768 secret key (hex)")
146
+ .option("-i, --recipient-id <id>", "optional recipient identifier for direct slot lookup")
147
+ .option("-c, --chunk-size <bytes>", "chunk size in bytes")
148
+ .option("--json", "print the result as one line of JSON")
149
+ .action(async (file, opts) => {
150
+ if (!opts.x25519Secret || !opts.mlKemSecret) {
151
+ throw new io_1.UsageError("missing recipient secret key: both --x25519-secret and --ml-kem-secret are required");
152
+ }
153
+ const chunkSize = (0, exports.parseMultiChunkSize)(opts.chunkSize);
154
+ const ciphertext = (0, stream_1.parseCiphertext)(await (0, io_1.readInput)(file, ctx.io));
155
+ executeStreamMultiDecrypt(ctx, opts, ciphertext, chunkSize);
156
+ });
157
+ };
158
+ const registerStreamMulti = (stream, ctx) => {
159
+ registerStreamMultiEncrypt(stream, ctx);
160
+ registerStreamMultiDecrypt(stream, ctx);
161
+ };
162
+ exports.registerStreamMulti = registerStreamMulti;
163
+ //# sourceMappingURL=stream-multi.js.map
@@ -0,0 +1,6 @@
1
+ import { Command } from "commander";
2
+ import { type RunContext } from "./io";
3
+ export declare const parseChunkSize: (value?: string) => number | undefined;
4
+ export declare const parseCiphertext: (input: Buffer) => Buffer;
5
+ export declare const registerStream: (program: Command, ctx: RunContext) => void;
6
+ //# sourceMappingURL=stream.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stream.d.ts","sourceRoot":"","sources":["../../src/program/stream.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAMpC,OAAO,EAKL,KAAK,UAAU,EAChB,MAAM,MAAM,CAAC;AAuBd,eAAO,MAAM,cAAc,GAAI,QAAQ,MAAM,KAAG,MAAM,GAAG,SAOxD,CAAC;AAGF,eAAO,MAAM,eAAe,GAAI,OAAO,MAAM,KAAG,MAM/C,CAAC;AAqGF,eAAO,MAAM,cAAc,GAAI,SAAS,OAAO,EAAE,KAAK,UAAU,KAAG,IAOlE,CAAC"}