@ecmaos/coreutils 0.6.2 → 0.6.4

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,1322 @@
1
+ import path from 'path';
2
+ import chalk from 'chalk';
3
+ import { TerminalCommand } from '../shared/terminal-command.js';
4
+ import { writelnStdout, writelnStderr } from '../shared/helpers.js';
5
+ const SUPPORTED_SYMMETRIC_ALGORITHMS = {
6
+ 'aes-gcm': 'AES-GCM',
7
+ 'aes-cbc': 'AES-CBC',
8
+ 'aes-ctr': 'AES-CTR',
9
+ 'aes-kw': 'AES-KW'
10
+ };
11
+ const SUPPORTED_ASYMMETRIC_ALGORITHMS = {
12
+ 'rsa-oaep': 'RSA-OAEP',
13
+ 'rsa-pss': 'RSA-PSS',
14
+ 'rsassa-pkcs1-v1_5': 'RSASSA-PKCS1-v1_5',
15
+ 'rsassa-pkcs1-v1-5': 'RSASSA-PKCS1-v1_5',
16
+ 'ecdsa': 'ECDSA',
17
+ 'ecdh': 'ECDH'
18
+ };
19
+ const SUPPORTED_SIGN_ALGORITHMS = {
20
+ 'ecdsa': 'ECDSA',
21
+ 'rsa-pss': 'RSA-PSS',
22
+ 'rsassa-pkcs1-v1_5': 'RSASSA-PKCS1-v1_5',
23
+ 'rsassa-pkcs1-v1-5': 'RSASSA-PKCS1-v1_5',
24
+ 'hmac': 'HMAC'
25
+ };
26
+ const SUPPORTED_DERIVE_ALGORITHMS = {
27
+ 'pbkdf2': 'PBKDF2',
28
+ 'hkdf': 'HKDF',
29
+ 'ecdh': 'ECDH'
30
+ };
31
+ const SUPPORTED_HASH_ALGORITHMS = {
32
+ 'sha1': 'SHA-1',
33
+ 'sha-1': 'SHA-1',
34
+ 'sha256': 'SHA-256',
35
+ 'sha-256': 'SHA-256',
36
+ 'sha384': 'SHA-384',
37
+ 'sha-384': 'SHA-384',
38
+ 'sha512': 'SHA-512',
39
+ 'sha-512': 'SHA-512'
40
+ };
41
+ const SUPPORTED_NAMED_CURVES = {
42
+ 'p-256': 'P-256',
43
+ 'p256': 'P-256',
44
+ 'p-384': 'P-384',
45
+ 'p384': 'P-384',
46
+ 'p-521': 'P-521',
47
+ 'p521': 'P-521'
48
+ };
49
+ const SUPPORTED_KEY_FORMATS = {
50
+ 'jwk': 'jwk',
51
+ 'raw': 'raw',
52
+ 'pkcs8': 'pkcs8',
53
+ 'spki': 'spki'
54
+ };
55
+ function printUsage(process, terminal) {
56
+ const usage = `Usage: crypto <subcommand> [options]
57
+
58
+ Subcommands:
59
+ generate Generate cryptographic keys
60
+ encrypt Encrypt data
61
+ decrypt Decrypt data
62
+ sign Sign data
63
+ verify Verify signatures
64
+ import Import keys from various formats
65
+ export Export keys to various formats
66
+ derive Derive keys from passwords or other keys
67
+ random Generate random bytes
68
+
69
+ --help display this help and exit
70
+
71
+ Run 'crypto <subcommand> --help' for subcommand-specific help.
72
+
73
+ Examples:
74
+
75
+ # Symmetric encryption
76
+ crypto generate --algorithm aes-gcm --length 256 --output key.json
77
+ crypto encrypt --algorithm aes-gcm --key-file key.json --input plaintext.txt --output encrypted.bin
78
+ crypto decrypt --algorithm aes-gcm --key-file key.json --input encrypted.bin --output decrypted.txt
79
+
80
+ # ECDSA signing and verification
81
+ crypto generate --algorithm ecdsa --named-curve P-256 --output ecdsa-key.json
82
+ crypto sign --algorithm ecdsa --key-file ecdsa-key.json --input message.txt --output signature.sig
83
+ crypto verify --algorithm ecdsa --key-file ecdsa-key.json --input message.txt --signature signature.sig
84
+
85
+ # Key format conversion
86
+ crypto import --format jwk --input key.json --output key.pem
87
+ crypto export --format pkcs8 --input key.pem --output key.json`;
88
+ void writelnStderr(process, terminal, usage);
89
+ }
90
+ function toArrayBuffer(buffer) {
91
+ if (buffer instanceof ArrayBuffer) {
92
+ return buffer;
93
+ }
94
+ if (buffer instanceof SharedArrayBuffer) {
95
+ const view = new Uint8Array(buffer);
96
+ const newBuffer = new ArrayBuffer(view.length);
97
+ new Uint8Array(newBuffer).set(view);
98
+ return newBuffer;
99
+ }
100
+ const view = new Uint8Array(buffer);
101
+ const newBuffer = new ArrayBuffer(view.length);
102
+ new Uint8Array(newBuffer).set(view);
103
+ return newBuffer;
104
+ }
105
+ async function readStreamToUint8Array(reader) {
106
+ const chunks = [];
107
+ try {
108
+ while (true) {
109
+ const { done, value } = await reader.read();
110
+ if (done)
111
+ break;
112
+ if (value) {
113
+ chunks.push(value);
114
+ }
115
+ }
116
+ }
117
+ finally {
118
+ reader.releaseLock();
119
+ }
120
+ const totalLength = chunks.reduce((sum, chunk) => sum + chunk.length, 0);
121
+ const result = new Uint8Array(totalLength);
122
+ let offset = 0;
123
+ for (const chunk of chunks) {
124
+ result.set(chunk, offset);
125
+ offset += chunk.length;
126
+ }
127
+ return result;
128
+ }
129
+ async function readFileToUint8Array(fs, filePath) {
130
+ const handle = await fs.open(filePath, 'r');
131
+ const stat = await fs.stat(filePath);
132
+ const chunks = [];
133
+ let bytesRead = 0;
134
+ const chunkSize = 64 * 1024;
135
+ while (bytesRead < stat.size) {
136
+ const data = new Uint8Array(chunkSize);
137
+ const readSize = Math.min(chunkSize, stat.size - bytesRead);
138
+ await handle.read(data, 0, readSize, bytesRead);
139
+ chunks.push(data.subarray(0, readSize));
140
+ bytesRead += readSize;
141
+ }
142
+ await handle.close();
143
+ const totalLength = chunks.reduce((sum, chunk) => sum + chunk.length, 0);
144
+ const fileData = new Uint8Array(totalLength);
145
+ let offset = 0;
146
+ for (const chunk of chunks) {
147
+ fileData.set(chunk, offset);
148
+ offset += chunk.length;
149
+ }
150
+ return fileData;
151
+ }
152
+ async function writeUint8ArrayToFile(fs, filePath, data) {
153
+ const handle = await fs.open(filePath, 'w');
154
+ await handle.write(data);
155
+ await handle.close();
156
+ }
157
+ function parseArgs(args) {
158
+ const parsed = {};
159
+ let i = 0;
160
+ while (i < args.length) {
161
+ const arg = args[i];
162
+ if (!arg) {
163
+ i++;
164
+ continue;
165
+ }
166
+ if (arg === '--help' || arg === '-h') {
167
+ parsed.help = true;
168
+ i++;
169
+ }
170
+ else if (arg.startsWith('--')) {
171
+ const key = arg.slice(2);
172
+ if (arg.includes('=')) {
173
+ const parts = arg.split('=', 2);
174
+ if (parts.length === 2 && parts[0] && parts[1]) {
175
+ parsed[parts[0].slice(2)] = parts[1];
176
+ }
177
+ i++;
178
+ }
179
+ else if (i + 1 < args.length && !args[i + 1]?.startsWith('--')) {
180
+ const nextArg = args[i + 1];
181
+ if (nextArg) {
182
+ parsed[key] = nextArg;
183
+ }
184
+ i += 2;
185
+ }
186
+ else {
187
+ parsed[key] = true;
188
+ i++;
189
+ }
190
+ }
191
+ else if (arg.startsWith('-') && arg.length === 2) {
192
+ const key = arg.slice(1);
193
+ if (i + 1 < args.length && !args[i + 1]?.startsWith('-')) {
194
+ const nextArg = args[i + 1];
195
+ if (nextArg) {
196
+ parsed[key] = nextArg;
197
+ }
198
+ i += 2;
199
+ }
200
+ else {
201
+ parsed[key] = true;
202
+ i++;
203
+ }
204
+ }
205
+ else {
206
+ if (!parsed._)
207
+ parsed._ = [];
208
+ if (typeof parsed._ === 'string')
209
+ parsed._ = [parsed._];
210
+ if (Array.isArray(parsed._)) {
211
+ parsed._.push(arg);
212
+ }
213
+ i++;
214
+ }
215
+ }
216
+ return parsed;
217
+ }
218
+ async function handleGenerate(shell, terminal, process, args) {
219
+ const usage = `Usage: crypto generate [OPTIONS]
220
+
221
+ Generate cryptographic keys.
222
+
223
+ Options:
224
+ --algorithm, -a ALGORITHM Algorithm (AES-GCM, AES-CBC, AES-CTR, AES-KW, RSA-OAEP, RSA-PSS, RSASSA-PKCS1-v1_5, ECDSA, ECDH, HMAC)
225
+ --length, -l LENGTH Key length in bits (for AES: 128, 192, 256; for RSA: 1024, 2048, 4096)
226
+ --named-curve, -c CURVE Named curve for ECDSA/ECDH (P-256, P-384, P-521)
227
+ --hash, -h ALGORITHM Hash algorithm for HMAC/RSA (SHA-1, SHA-256, SHA-384, SHA-512)
228
+ --output, -o FILE Output file (default: stdout, JWK format)
229
+ --format, -f FORMAT Output format (jwk, raw, pkcs8, spki) (default: jwk)
230
+ --help Display this help
231
+
232
+ Examples:
233
+ crypto generate --algorithm aes-gcm --length 256 --output key.json
234
+ crypto generate --algorithm ecdsa --named-curve P-256 --output ecdsa-key.json
235
+ crypto generate --algorithm rsa-oaep --length 2048 --output rsa-key.json
236
+ crypto generate --algorithm hmac --hash SHA-256 --length 256 --output hmac-key.json`;
237
+ const parsed = parseArgs(args);
238
+ if (parsed.help) {
239
+ await writelnStderr(process, terminal, usage);
240
+ return 0;
241
+ }
242
+ const algorithm = parsed.algorithm || parsed.a;
243
+ const length = parsed.length || parsed.l;
244
+ const namedCurve = parsed['named-curve'] || parsed.c;
245
+ const hash = parsed.hash || parsed.h;
246
+ const output = parsed.output || parsed.o;
247
+ const formatValue = parsed.format || parsed.f || 'jwk';
248
+ const format = (typeof formatValue === 'string' ? formatValue : 'jwk').toLowerCase();
249
+ if (!algorithm || typeof algorithm !== 'string') {
250
+ await writelnStderr(process, terminal, 'crypto generate: --algorithm is required');
251
+ await writelnStderr(process, terminal, 'Try "crypto generate --help" for more information.');
252
+ return 1;
253
+ }
254
+ const algoLower = algorithm.toLowerCase();
255
+ const keyFormat = SUPPORTED_KEY_FORMATS[format];
256
+ if (!keyFormat) {
257
+ await writelnStderr(process, terminal, `crypto generate: unsupported format '${format}'`);
258
+ return 1;
259
+ }
260
+ try {
261
+ let key;
262
+ let exportFormat = keyFormat;
263
+ if (SUPPORTED_SYMMETRIC_ALGORITHMS[algoLower]) {
264
+ const symAlgo = SUPPORTED_SYMMETRIC_ALGORITHMS[algoLower];
265
+ const keyLength = length ? parseInt(length, 10) : 256;
266
+ if (![128, 192, 256].includes(keyLength)) {
267
+ await writelnStderr(process, terminal, 'crypto generate: AES key length must be 128, 192, or 256');
268
+ return 1;
269
+ }
270
+ key = await crypto.subtle.generateKey({ name: symAlgo, length: keyLength }, true, ['encrypt', 'decrypt']);
271
+ if (keyFormat === 'pkcs8' || keyFormat === 'spki') {
272
+ await writelnStderr(process, terminal, 'crypto generate: symmetric keys cannot be exported in PKCS8 or SPKI format');
273
+ return 1;
274
+ }
275
+ exportFormat = keyFormat === 'raw' ? 'raw' : 'jwk';
276
+ }
277
+ else if (SUPPORTED_ASYMMETRIC_ALGORITHMS[algoLower] === 'ECDSA' || SUPPORTED_ASYMMETRIC_ALGORITHMS[algoLower] === 'ECDH') {
278
+ const curve = namedCurve ? (SUPPORTED_NAMED_CURVES[namedCurve.toLowerCase()] || 'P-256') : 'P-256';
279
+ const keyUsages = SUPPORTED_ASYMMETRIC_ALGORITHMS[algoLower] === 'ECDSA'
280
+ ? ['sign', 'verify']
281
+ : ['deriveKey', 'deriveBits'];
282
+ key = await crypto.subtle.generateKey({
283
+ name: SUPPORTED_ASYMMETRIC_ALGORITHMS[algoLower],
284
+ namedCurve: curve
285
+ }, true, keyUsages);
286
+ if (keyFormat === 'raw') {
287
+ await writelnStderr(process, terminal, 'crypto generate: ECDSA/ECDH keys cannot be exported in raw format');
288
+ return 1;
289
+ }
290
+ }
291
+ else if (SUPPORTED_ASYMMETRIC_ALGORITHMS[algoLower]?.startsWith('RSA')) {
292
+ const rsaAlgo = SUPPORTED_ASYMMETRIC_ALGORITHMS[algoLower];
293
+ const keyLength = length ? parseInt(length, 10) : 2048;
294
+ const hashAlgo = hash ? (SUPPORTED_HASH_ALGORITHMS[hash.toLowerCase()] || 'SHA-256') : 'SHA-256';
295
+ if (![1024, 2048, 4096].includes(keyLength)) {
296
+ await writelnStderr(process, terminal, 'crypto generate: RSA key length must be 1024, 2048, or 4096');
297
+ return 1;
298
+ }
299
+ const keyUsages = rsaAlgo === 'RSA-OAEP'
300
+ ? ['encrypt', 'decrypt']
301
+ : ['sign', 'verify'];
302
+ key = await crypto.subtle.generateKey({
303
+ name: rsaAlgo,
304
+ modulusLength: keyLength,
305
+ publicExponent: new Uint8Array([1, 0, 1]),
306
+ hash: hashAlgo
307
+ }, true, keyUsages);
308
+ if (keyFormat === 'raw') {
309
+ await writelnStderr(process, terminal, 'crypto generate: RSA keys cannot be exported in raw format');
310
+ return 1;
311
+ }
312
+ }
313
+ else if (algoLower === 'hmac') {
314
+ const keyLength = length ? parseInt(length, 10) : 256;
315
+ const hashAlgo = hash ? (SUPPORTED_HASH_ALGORITHMS[hash.toLowerCase()] || 'SHA-256') : 'SHA-256';
316
+ key = await crypto.subtle.generateKey({
317
+ name: 'HMAC',
318
+ hash: hashAlgo,
319
+ length: keyLength
320
+ }, true, ['sign', 'verify']);
321
+ if (keyFormat === 'pkcs8' || keyFormat === 'spki') {
322
+ await writelnStderr(process, terminal, 'crypto generate: HMAC keys cannot be exported in PKCS8 or SPKI format');
323
+ return 1;
324
+ }
325
+ exportFormat = keyFormat === 'raw' ? 'raw' : 'jwk';
326
+ }
327
+ else {
328
+ await writelnStderr(process, terminal, `crypto generate: unsupported algorithm '${algorithm}'`);
329
+ return 1;
330
+ }
331
+ let exported;
332
+ if ('publicKey' in key && 'privateKey' in key) {
333
+ const keyPair = key;
334
+ if (keyFormat === 'spki') {
335
+ exported = await crypto.subtle.exportKey('spki', keyPair.publicKey);
336
+ }
337
+ else if (keyFormat === 'pkcs8') {
338
+ exported = await crypto.subtle.exportKey('pkcs8', keyPair.privateKey);
339
+ }
340
+ else {
341
+ exported = await crypto.subtle.exportKey('jwk', keyPair.privateKey);
342
+ }
343
+ }
344
+ else {
345
+ exported = await crypto.subtle.exportKey(exportFormat, key);
346
+ }
347
+ let outputData;
348
+ if (exportFormat === 'jwk') {
349
+ const json = JSON.stringify(exported, null, 2);
350
+ outputData = new TextEncoder().encode(json);
351
+ }
352
+ else {
353
+ outputData = new Uint8Array(exported);
354
+ }
355
+ if (output) {
356
+ const outputPath = path.resolve(shell.cwd, output);
357
+ await writeUint8ArrayToFile(shell.context.fs.promises, outputPath, outputData);
358
+ await writelnStdout(process, terminal, chalk.green(`Key generated and saved to ${output}`));
359
+ }
360
+ else {
361
+ if (process?.stdout) {
362
+ const writer = process.stdout.getWriter();
363
+ try {
364
+ await writer.write(outputData);
365
+ if (exportFormat === 'jwk') {
366
+ await writer.write(new TextEncoder().encode('\n'));
367
+ }
368
+ }
369
+ finally {
370
+ writer.releaseLock();
371
+ }
372
+ }
373
+ else {
374
+ terminal.write(new TextDecoder().decode(outputData));
375
+ }
376
+ }
377
+ return 0;
378
+ }
379
+ catch (error) {
380
+ await writelnStderr(process, terminal, `crypto generate: ${error instanceof Error ? error.message : 'Unknown error'}`);
381
+ return 1;
382
+ }
383
+ }
384
+ async function handleEncrypt(shell, terminal, process, args) {
385
+ const usage = `Usage: crypto encrypt [OPTIONS]
386
+
387
+ Encrypt data using various algorithms.
388
+
389
+ Options:
390
+ --algorithm, -a ALGORITHM Algorithm (AES-GCM, AES-CBC, AES-CTR, RSA-OAEP)
391
+ --key-file, -k FILE Key file (JWK format)
392
+ --input, -i FILE Input file (default: stdin)
393
+ --output, -o FILE Output file (default: stdout)
394
+ --iv-file FILE IV/nonce file (for AES, auto-generated if not provided)
395
+ --help Display this help`;
396
+ const parsed = parseArgs(args);
397
+ if (parsed.help) {
398
+ await writelnStderr(process, terminal, usage);
399
+ return 0;
400
+ }
401
+ const algorithm = parsed.algorithm || parsed.a;
402
+ const keyFile = parsed['key-file'] || parsed.k;
403
+ const input = parsed.input || parsed.i;
404
+ const output = parsed.output || parsed.o;
405
+ const ivFile = parsed['iv-file'];
406
+ if (!algorithm || typeof algorithm !== 'string') {
407
+ await writelnStderr(process, terminal, 'crypto encrypt: --algorithm is required');
408
+ return 1;
409
+ }
410
+ if (!keyFile || typeof keyFile !== 'string') {
411
+ await writelnStderr(process, terminal, 'crypto encrypt: --key-file is required');
412
+ return 1;
413
+ }
414
+ try {
415
+ const algoLower = algorithm.toLowerCase();
416
+ if (!SUPPORTED_SYMMETRIC_ALGORITHMS[algoLower] && algoLower !== 'rsa-oaep') {
417
+ await writelnStderr(process, terminal, `crypto encrypt: unsupported algorithm '${algorithm}'`);
418
+ return 1;
419
+ }
420
+ const keyPath = path.resolve(shell.cwd, keyFile);
421
+ const keyData = await readFileToUint8Array(shell.context.fs.promises, keyPath);
422
+ const keyJson = JSON.parse(new TextDecoder().decode(keyData));
423
+ let key;
424
+ let encryptParams;
425
+ if (SUPPORTED_SYMMETRIC_ALGORITHMS[algoLower]) {
426
+ const symAlgo = SUPPORTED_SYMMETRIC_ALGORITHMS[algoLower];
427
+ key = await crypto.subtle.importKey('jwk', keyJson, { name: symAlgo }, false, ['encrypt']);
428
+ let iv;
429
+ if (ivFile && typeof ivFile === 'string') {
430
+ iv = await readFileToUint8Array(shell.context.fs.promises, path.resolve(shell.cwd, ivFile));
431
+ }
432
+ else {
433
+ if (symAlgo === 'AES-GCM') {
434
+ iv = crypto.getRandomValues(new Uint8Array(12));
435
+ }
436
+ else {
437
+ iv = crypto.getRandomValues(new Uint8Array(16));
438
+ }
439
+ }
440
+ const ivBuffer = toArrayBuffer(iv.buffer);
441
+ if (symAlgo === 'AES-GCM') {
442
+ encryptParams = { name: 'AES-GCM', iv: new Uint8Array(ivBuffer, iv.byteOffset, iv.length) };
443
+ }
444
+ else if (symAlgo === 'AES-CBC') {
445
+ encryptParams = { name: 'AES-CBC', iv: new Uint8Array(ivBuffer, iv.byteOffset, iv.length) };
446
+ }
447
+ else {
448
+ encryptParams = { name: 'AES-CTR', counter: new Uint8Array(ivBuffer, iv.byteOffset, iv.length), length: 128 };
449
+ }
450
+ }
451
+ else {
452
+ key = await crypto.subtle.importKey('jwk', keyJson, { name: 'RSA-OAEP', hash: 'SHA-256' }, false, ['encrypt']);
453
+ encryptParams = { name: 'RSA-OAEP' };
454
+ }
455
+ let inputData;
456
+ if (input) {
457
+ inputData = await readFileToUint8Array(shell.context.fs.promises, path.resolve(shell.cwd, input));
458
+ }
459
+ else {
460
+ if (!process?.stdin) {
461
+ await writelnStderr(process, terminal, 'crypto encrypt: no input specified');
462
+ return 1;
463
+ }
464
+ const reader = process.stdin.getReader();
465
+ inputData = await readStreamToUint8Array(reader);
466
+ }
467
+ const inputBuffer = toArrayBuffer(inputData.buffer);
468
+ const encrypted = await crypto.subtle.encrypt(encryptParams, key, new Uint8Array(inputBuffer, inputData.byteOffset, inputData.length));
469
+ let outputData;
470
+ const encryptedArray = new Uint8Array(encrypted);
471
+ if (SUPPORTED_SYMMETRIC_ALGORITHMS[algoLower] && !ivFile) {
472
+ const ivParam = encryptParams.iv;
473
+ const ivArray = ivParam instanceof Uint8Array
474
+ ? ivParam
475
+ : ivParam instanceof ArrayBuffer
476
+ ? new Uint8Array(ivParam)
477
+ : new Uint8Array(ivParam.buffer, ivParam.byteOffset, ivParam.byteLength);
478
+ const ivLength = ivArray.length;
479
+ outputData = new Uint8Array(ivLength + encrypted.byteLength);
480
+ outputData.set(ivArray, 0);
481
+ outputData.set(encryptedArray, ivLength);
482
+ }
483
+ else {
484
+ outputData = encryptedArray;
485
+ }
486
+ if (output) {
487
+ await writeUint8ArrayToFile(shell.context.fs.promises, path.resolve(shell.cwd, output), outputData);
488
+ if (!ivFile && SUPPORTED_SYMMETRIC_ALGORITHMS[algoLower]) {
489
+ const ivParam = encryptParams.iv;
490
+ const ivArray = ivParam instanceof Uint8Array
491
+ ? ivParam
492
+ : ivParam instanceof ArrayBuffer
493
+ ? new Uint8Array(ivParam)
494
+ : new Uint8Array(ivParam.buffer, ivParam.byteOffset, ivParam.byteLength);
495
+ const ivPath = path.resolve(shell.cwd, output + '.iv');
496
+ await writeUint8ArrayToFile(shell.context.fs.promises, ivPath, ivArray);
497
+ await writelnStdout(process, terminal, chalk.yellow(`IV saved to ${output}.iv`));
498
+ }
499
+ }
500
+ else {
501
+ if (process?.stdout) {
502
+ const writer = process.stdout.getWriter();
503
+ try {
504
+ await writer.write(outputData);
505
+ }
506
+ finally {
507
+ writer.releaseLock();
508
+ }
509
+ }
510
+ else {
511
+ terminal.write(new TextDecoder().decode(outputData));
512
+ }
513
+ }
514
+ return 0;
515
+ }
516
+ catch (error) {
517
+ await writelnStderr(process, terminal, `crypto encrypt: ${error instanceof Error ? error.message : 'Unknown error'}`);
518
+ return 1;
519
+ }
520
+ }
521
+ async function handleDecrypt(shell, terminal, process, args) {
522
+ const usage = `Usage: crypto decrypt [OPTIONS]
523
+
524
+ Decrypt data using various algorithms.
525
+
526
+ Options:
527
+ --algorithm, -a ALGORITHM Algorithm (AES-GCM, AES-CBC, AES-CTR, RSA-OAEP)
528
+ --key-file, -k FILE Key file (JWK format)
529
+ --input, -i FILE Input file (default: stdin)
530
+ --output, -o FILE Output file (default: stdout)
531
+ --iv-file FILE IV/nonce file (for AES, required if not embedded)
532
+ --help Display this help`;
533
+ const parsed = parseArgs(args);
534
+ if (parsed.help) {
535
+ await writelnStderr(process, terminal, usage);
536
+ return 0;
537
+ }
538
+ const algorithm = parsed.algorithm || parsed.a;
539
+ const keyFile = parsed['key-file'] || parsed.k;
540
+ const input = parsed.input || parsed.i;
541
+ const output = parsed.output || parsed.o;
542
+ const ivFile = parsed['iv-file'];
543
+ if (!algorithm || typeof algorithm !== 'string') {
544
+ await writelnStderr(process, terminal, 'crypto decrypt: --algorithm is required');
545
+ return 1;
546
+ }
547
+ if (!keyFile || typeof keyFile !== 'string') {
548
+ await writelnStderr(process, terminal, 'crypto decrypt: --key-file is required');
549
+ return 1;
550
+ }
551
+ try {
552
+ const algoLower = algorithm.toLowerCase();
553
+ if (!SUPPORTED_SYMMETRIC_ALGORITHMS[algoLower] && algoLower !== 'rsa-oaep') {
554
+ await writelnStderr(process, terminal, `crypto decrypt: unsupported algorithm '${algorithm}'`);
555
+ return 1;
556
+ }
557
+ const keyPath = path.resolve(shell.cwd, keyFile);
558
+ const keyData = await readFileToUint8Array(shell.context.fs.promises, keyPath);
559
+ const keyJson = JSON.parse(new TextDecoder().decode(keyData));
560
+ let inputData;
561
+ if (input) {
562
+ inputData = await readFileToUint8Array(shell.context.fs.promises, path.resolve(shell.cwd, input));
563
+ }
564
+ else {
565
+ if (!process?.stdin) {
566
+ await writelnStderr(process, terminal, 'crypto decrypt: no input specified');
567
+ return 1;
568
+ }
569
+ const reader = process.stdin.getReader();
570
+ inputData = await readStreamToUint8Array(reader);
571
+ }
572
+ let key;
573
+ let decryptParams;
574
+ let encryptedData;
575
+ if (SUPPORTED_SYMMETRIC_ALGORITHMS[algoLower]) {
576
+ const symAlgo = SUPPORTED_SYMMETRIC_ALGORITHMS[algoLower];
577
+ key = await crypto.subtle.importKey('jwk', keyJson, { name: symAlgo }, false, ['decrypt']);
578
+ let iv;
579
+ if (ivFile && typeof ivFile === 'string') {
580
+ iv = await readFileToUint8Array(shell.context.fs.promises, path.resolve(shell.cwd, ivFile));
581
+ encryptedData = inputData;
582
+ }
583
+ else {
584
+ if (symAlgo === 'AES-GCM') {
585
+ iv = inputData.slice(0, 12);
586
+ encryptedData = inputData.slice(12);
587
+ }
588
+ else {
589
+ iv = inputData.slice(0, 16);
590
+ encryptedData = inputData.slice(16);
591
+ }
592
+ }
593
+ const ivBuffer = toArrayBuffer(iv.buffer);
594
+ if (symAlgo === 'AES-GCM') {
595
+ decryptParams = { name: 'AES-GCM', iv: new Uint8Array(ivBuffer, iv.byteOffset, iv.length) };
596
+ }
597
+ else if (symAlgo === 'AES-CBC') {
598
+ decryptParams = { name: 'AES-CBC', iv: new Uint8Array(ivBuffer, iv.byteOffset, iv.length) };
599
+ }
600
+ else {
601
+ decryptParams = { name: 'AES-CTR', counter: new Uint8Array(ivBuffer, iv.byteOffset, iv.length), length: 128 };
602
+ }
603
+ }
604
+ else {
605
+ key = await crypto.subtle.importKey('jwk', keyJson, { name: 'RSA-OAEP', hash: 'SHA-256' }, false, ['decrypt']);
606
+ decryptParams = { name: 'RSA-OAEP' };
607
+ encryptedData = inputData;
608
+ }
609
+ const encryptedBuffer = toArrayBuffer(encryptedData.buffer);
610
+ const decrypted = await crypto.subtle.decrypt(decryptParams, key, new Uint8Array(encryptedBuffer, encryptedData.byteOffset, encryptedData.length));
611
+ const outputData = new Uint8Array(decrypted);
612
+ if (output) {
613
+ await writeUint8ArrayToFile(shell.context.fs.promises, path.resolve(shell.cwd, output), outputData);
614
+ }
615
+ else {
616
+ if (process?.stdout) {
617
+ const writer = process.stdout.getWriter();
618
+ try {
619
+ await writer.write(outputData);
620
+ }
621
+ finally {
622
+ writer.releaseLock();
623
+ }
624
+ }
625
+ else {
626
+ terminal.write(new TextDecoder().decode(outputData));
627
+ }
628
+ }
629
+ return 0;
630
+ }
631
+ catch (error) {
632
+ await writelnStderr(process, terminal, `crypto decrypt: ${error instanceof Error ? error.message : 'Unknown error'}`);
633
+ return 1;
634
+ }
635
+ }
636
+ async function handleSign(shell, terminal, process, args) {
637
+ const usage = `Usage: crypto sign [OPTIONS]
638
+
639
+ Sign data using various algorithms.
640
+
641
+ Options:
642
+ --algorithm, -a ALGORITHM Algorithm (ECDSA, RSA-PSS, RSASSA-PKCS1-v1_5, HMAC)
643
+ --key-file, -k FILE Private key file (JWK format)
644
+ --input, -i FILE Input file (default: stdin)
645
+ --output, -o FILE Output file (default: stdout)
646
+ --help Display this help
647
+
648
+ Examples:
649
+ crypto sign --algorithm ecdsa --key-file ecdsa-key.json --input message.txt --output signature.sig
650
+ crypto sign --algorithm hmac --key-file hmac-key.json --input data.bin --output signature.sig`;
651
+ const parsed = parseArgs(args);
652
+ if (parsed.help) {
653
+ await writelnStderr(process, terminal, usage);
654
+ return 0;
655
+ }
656
+ const algorithm = parsed.algorithm || parsed.a;
657
+ const keyFile = parsed['key-file'] || parsed.k;
658
+ const input = parsed.input || parsed.i;
659
+ const output = parsed.output || parsed.o;
660
+ if (!algorithm || typeof algorithm !== 'string') {
661
+ await writelnStderr(process, terminal, 'crypto sign: --algorithm is required');
662
+ return 1;
663
+ }
664
+ if (!keyFile || typeof keyFile !== 'string') {
665
+ await writelnStderr(process, terminal, 'crypto sign: --key-file is required');
666
+ return 1;
667
+ }
668
+ try {
669
+ const algoLower = algorithm.toLowerCase();
670
+ if (!SUPPORTED_SIGN_ALGORITHMS[algoLower]) {
671
+ await writelnStderr(process, terminal, `crypto sign: unsupported algorithm '${algorithm}'`);
672
+ return 1;
673
+ }
674
+ const keyPath = path.resolve(shell.cwd, keyFile);
675
+ const keyData = await readFileToUint8Array(shell.context.fs.promises, keyPath);
676
+ const keyJson = JSON.parse(new TextDecoder().decode(keyData));
677
+ let key;
678
+ let signParams;
679
+ if (algoLower === 'ecdsa') {
680
+ key = await crypto.subtle.importKey('jwk', keyJson, { name: 'ECDSA', namedCurve: 'P-256' }, false, ['sign']);
681
+ signParams = { name: 'ECDSA', hash: 'SHA-256' };
682
+ }
683
+ else if (algoLower === 'rsa-pss') {
684
+ key = await crypto.subtle.importKey('jwk', keyJson, { name: 'RSA-PSS', hash: 'SHA-256' }, false, ['sign']);
685
+ signParams = { name: 'RSA-PSS', saltLength: 32 };
686
+ }
687
+ else if (algoLower === 'rsassa-pkcs1-v1_5' || algoLower === 'rsassa-pkcs1-v1-5') {
688
+ key = await crypto.subtle.importKey('jwk', keyJson, { name: 'RSASSA-PKCS1-v1_5', hash: 'SHA-256' }, false, ['sign']);
689
+ signParams = { name: 'RSASSA-PKCS1-v1_5' };
690
+ }
691
+ else {
692
+ key = await crypto.subtle.importKey('jwk', keyJson, { name: 'HMAC', hash: 'SHA-256' }, false, ['sign']);
693
+ signParams = { name: 'HMAC' };
694
+ }
695
+ let inputData;
696
+ if (input) {
697
+ inputData = await readFileToUint8Array(shell.context.fs.promises, path.resolve(shell.cwd, input));
698
+ }
699
+ else {
700
+ if (!process?.stdin) {
701
+ await writelnStderr(process, terminal, 'crypto sign: no input specified');
702
+ return 1;
703
+ }
704
+ const reader = process.stdin.getReader();
705
+ inputData = await readStreamToUint8Array(reader);
706
+ }
707
+ const inputBuffer = toArrayBuffer(inputData.buffer);
708
+ const signature = await crypto.subtle.sign(signParams, key, new Uint8Array(inputBuffer, inputData.byteOffset, inputData.length));
709
+ const outputData = new Uint8Array(signature);
710
+ if (output) {
711
+ await writeUint8ArrayToFile(shell.context.fs.promises, path.resolve(shell.cwd, output), outputData);
712
+ }
713
+ else {
714
+ if (process?.stdout) {
715
+ const writer = process.stdout.getWriter();
716
+ try {
717
+ await writer.write(outputData);
718
+ }
719
+ finally {
720
+ writer.releaseLock();
721
+ }
722
+ }
723
+ else {
724
+ terminal.write(new TextDecoder().decode(outputData));
725
+ }
726
+ }
727
+ return 0;
728
+ }
729
+ catch (error) {
730
+ await writelnStderr(process, terminal, `crypto sign: ${error instanceof Error ? error.message : 'Unknown error'}`);
731
+ return 1;
732
+ }
733
+ }
734
+ async function handleVerify(shell, terminal, process, args) {
735
+ const usage = `Usage: crypto verify [OPTIONS]
736
+
737
+ Verify signatures using various algorithms.
738
+
739
+ Options:
740
+ --algorithm, -a ALGORITHM Algorithm (ECDSA, RSA-PSS, RSASSA-PKCS1-v1_5, HMAC)
741
+ --key-file, -k FILE Public key file (JWK format, can use key pair file)
742
+ --input, -i FILE Input file (default: stdin)
743
+ --signature, -s FILE Signature file
744
+ --help Display this help
745
+
746
+ Examples:
747
+ crypto verify --algorithm ecdsa --key-file ecdsa-key.json --input message.txt --signature signature.sig
748
+ crypto verify --algorithm hmac --key-file hmac-key.json --input data.bin --signature signature.sig`;
749
+ const parsed = parseArgs(args);
750
+ if (parsed.help) {
751
+ await writelnStderr(process, terminal, usage);
752
+ return 0;
753
+ }
754
+ const algorithm = parsed.algorithm || parsed.a;
755
+ const keyFile = parsed['key-file'] || parsed.k;
756
+ const input = parsed.input || parsed.i;
757
+ const signatureFile = parsed.signature || parsed.s;
758
+ if (!algorithm || typeof algorithm !== 'string') {
759
+ await writelnStderr(process, terminal, 'crypto verify: --algorithm is required');
760
+ return 1;
761
+ }
762
+ if (!keyFile || typeof keyFile !== 'string') {
763
+ await writelnStderr(process, terminal, 'crypto verify: --key-file is required');
764
+ return 1;
765
+ }
766
+ if (!signatureFile || typeof signatureFile !== 'string') {
767
+ await writelnStderr(process, terminal, 'crypto verify: --signature is required');
768
+ return 1;
769
+ }
770
+ try {
771
+ const algoLower = algorithm.toLowerCase();
772
+ if (!SUPPORTED_SIGN_ALGORITHMS[algoLower]) {
773
+ await writelnStderr(process, terminal, `crypto verify: unsupported algorithm '${algorithm}'`);
774
+ return 1;
775
+ }
776
+ const keyPath = path.resolve(shell.cwd, keyFile);
777
+ const keyData = await readFileToUint8Array(shell.context.fs.promises, keyPath);
778
+ let keyJson = JSON.parse(new TextDecoder().decode(keyData));
779
+ // Extract public key from private key JWK if needed
780
+ if (keyJson.d) {
781
+ const publicKeyJson = {
782
+ kty: keyJson.kty,
783
+ key_ops: ['verify'],
784
+ ext: keyJson.ext
785
+ };
786
+ if (keyJson.kty === 'EC') {
787
+ publicKeyJson.crv = keyJson.crv;
788
+ publicKeyJson.x = keyJson.x;
789
+ publicKeyJson.y = keyJson.y;
790
+ }
791
+ else if (keyJson.kty === 'RSA') {
792
+ ;
793
+ publicKeyJson.n = keyJson.n;
794
+ publicKeyJson.e = keyJson.e;
795
+ }
796
+ keyJson = publicKeyJson;
797
+ }
798
+ let key;
799
+ let verifyParams;
800
+ if (algoLower === 'ecdsa') {
801
+ const namedCurve = (keyJson.crv || 'P-256');
802
+ key = await crypto.subtle.importKey('jwk', keyJson, { name: 'ECDSA', namedCurve }, false, ['verify']);
803
+ verifyParams = { name: 'ECDSA', hash: 'SHA-256' };
804
+ }
805
+ else if (algoLower === 'rsa-pss') {
806
+ key = await crypto.subtle.importKey('jwk', keyJson, { name: 'RSA-PSS', hash: 'SHA-256' }, false, ['verify']);
807
+ verifyParams = { name: 'RSA-PSS', saltLength: 32 };
808
+ }
809
+ else if (algoLower === 'rsassa-pkcs1-v1_5' || algoLower === 'rsassa-pkcs1-v1-5') {
810
+ key = await crypto.subtle.importKey('jwk', keyJson, { name: 'RSASSA-PKCS1-v1_5', hash: 'SHA-256' }, false, ['verify']);
811
+ verifyParams = { name: 'RSASSA-PKCS1-v1_5' };
812
+ }
813
+ else {
814
+ key = await crypto.subtle.importKey('jwk', keyJson, { name: 'HMAC', hash: 'SHA-256' }, false, ['verify']);
815
+ verifyParams = { name: 'HMAC' };
816
+ }
817
+ let inputData;
818
+ if (input) {
819
+ inputData = await readFileToUint8Array(shell.context.fs.promises, path.resolve(shell.cwd, input));
820
+ }
821
+ else {
822
+ if (!process?.stdin) {
823
+ await writelnStderr(process, terminal, 'crypto verify: no input specified');
824
+ return 1;
825
+ }
826
+ const reader = process.stdin.getReader();
827
+ inputData = await readStreamToUint8Array(reader);
828
+ }
829
+ const signature = await readFileToUint8Array(shell.context.fs.promises, path.resolve(shell.cwd, signatureFile));
830
+ const signatureBuffer = toArrayBuffer(signature.buffer);
831
+ const inputBuffer = toArrayBuffer(inputData.buffer);
832
+ const isValid = await crypto.subtle.verify(verifyParams, key, new Uint8Array(signatureBuffer, signature.byteOffset, signature.length), new Uint8Array(inputBuffer, inputData.byteOffset, inputData.length));
833
+ if (isValid) {
834
+ await writelnStdout(process, terminal, chalk.green('Signature is valid'));
835
+ return 0;
836
+ }
837
+ else {
838
+ await writelnStderr(process, terminal, chalk.red('Signature is invalid'));
839
+ return 1;
840
+ }
841
+ }
842
+ catch (error) {
843
+ await writelnStderr(process, terminal, `crypto verify: ${error instanceof Error ? error.message : 'Unknown error'}`);
844
+ return 1;
845
+ }
846
+ }
847
+ async function handleImport(shell, terminal, process, args) {
848
+ const usage = `Usage: crypto import [OPTIONS]
849
+
850
+ Import keys from various formats.
851
+
852
+ Options:
853
+ --format, -f FORMAT Input format (jwk, raw, pkcs8, spki)
854
+ --algorithm, -a ALGORITHM Algorithm (required for raw format)
855
+ --input, -i FILE Input file (default: stdin)
856
+ --output, -o FILE Output file (default: stdout)
857
+ --output-format FORMAT Output format (jwk, raw, pkcs8, spki) (default: jwk)
858
+ --help Display this help
859
+
860
+ Examples:
861
+ crypto import --format jwk --input key.json --output-format raw --output key.bin
862
+ crypto import --format pkcs8 --algorithm ecdsa --input key.pem --output-format jwk --output key.json
863
+ crypto import --format jwk --input key.json --output-format pkcs8 --output private.pem`;
864
+ const parsed = parseArgs(args);
865
+ if (parsed.help) {
866
+ await writelnStderr(process, terminal, usage);
867
+ return 0;
868
+ }
869
+ const formatValue = parsed.format || parsed.f || 'jwk';
870
+ const format = (typeof formatValue === 'string' ? formatValue : 'jwk').toLowerCase();
871
+ const algorithm = parsed.algorithm || parsed.a;
872
+ const input = parsed.input || parsed.i;
873
+ const output = parsed.output || parsed.o;
874
+ const outputFormatValue = parsed['output-format'] || 'jwk';
875
+ const outputFormat = (typeof outputFormatValue === 'string' ? outputFormatValue : 'jwk').toLowerCase();
876
+ const keyFormat = SUPPORTED_KEY_FORMATS[format];
877
+ if (!keyFormat) {
878
+ await writelnStderr(process, terminal, `crypto import: unsupported input format '${format}'`);
879
+ return 1;
880
+ }
881
+ const outputKeyFormat = SUPPORTED_KEY_FORMATS[outputFormat];
882
+ if (!outputKeyFormat) {
883
+ await writelnStderr(process, terminal, `crypto import: unsupported output format '${outputFormat}'`);
884
+ return 1;
885
+ }
886
+ if (keyFormat !== 'jwk' && (!algorithm || typeof algorithm !== 'string')) {
887
+ await writelnStderr(process, terminal, 'crypto import: --algorithm is required for non-JWK formats');
888
+ return 1;
889
+ }
890
+ try {
891
+ let inputData;
892
+ if (input) {
893
+ inputData = await readFileToUint8Array(shell.context.fs.promises, path.resolve(shell.cwd, input));
894
+ }
895
+ else {
896
+ if (!process?.stdin) {
897
+ await writelnStderr(process, terminal, 'crypto import: no input specified');
898
+ return 1;
899
+ }
900
+ const reader = process.stdin.getReader();
901
+ inputData = await readStreamToUint8Array(reader);
902
+ }
903
+ let keyMaterial;
904
+ let importAlgorithm;
905
+ let keyUsages;
906
+ if (keyFormat === 'jwk') {
907
+ const json = new TextDecoder().decode(inputData);
908
+ const jwk = JSON.parse(json);
909
+ keyMaterial = jwk;
910
+ if (jwk.kty === 'RSA') {
911
+ importAlgorithm = { name: 'RSA-OAEP', hash: 'SHA-256' };
912
+ keyUsages = jwk.d ? ['decrypt', 'encrypt'] : ['encrypt'];
913
+ }
914
+ else if (jwk.kty === 'EC') {
915
+ importAlgorithm = { name: 'ECDSA', namedCurve: jwk.crv || 'P-256' };
916
+ keyUsages = jwk.d ? ['sign', 'verify'] : ['verify'];
917
+ }
918
+ else if (jwk.kty === 'oct') {
919
+ importAlgorithm = { name: 'AES-GCM' };
920
+ keyUsages = ['encrypt', 'decrypt'];
921
+ }
922
+ else {
923
+ await writelnStderr(process, terminal, 'crypto import: unsupported key type in JWK');
924
+ return 1;
925
+ }
926
+ }
927
+ else {
928
+ keyMaterial = toArrayBuffer(inputData.buffer);
929
+ const algoLower = algorithm.toLowerCase();
930
+ if (SUPPORTED_SYMMETRIC_ALGORITHMS[algoLower]) {
931
+ importAlgorithm = { name: SUPPORTED_SYMMETRIC_ALGORITHMS[algoLower] };
932
+ keyUsages = ['encrypt', 'decrypt'];
933
+ }
934
+ else if (SUPPORTED_ASYMMETRIC_ALGORITHMS[algoLower] === 'ECDSA' || SUPPORTED_ASYMMETRIC_ALGORITHMS[algoLower] === 'ECDH') {
935
+ importAlgorithm = { name: SUPPORTED_ASYMMETRIC_ALGORITHMS[algoLower], namedCurve: 'P-256' };
936
+ keyUsages = SUPPORTED_ASYMMETRIC_ALGORITHMS[algoLower] === 'ECDSA' ? ['sign', 'verify'] : ['deriveKey', 'deriveBits'];
937
+ }
938
+ else if (SUPPORTED_ASYMMETRIC_ALGORITHMS[algoLower]?.startsWith('RSA')) {
939
+ importAlgorithm = { name: SUPPORTED_ASYMMETRIC_ALGORITHMS[algoLower], hash: 'SHA-256' };
940
+ keyUsages = SUPPORTED_ASYMMETRIC_ALGORITHMS[algoLower] === 'RSA-OAEP' ? ['encrypt', 'decrypt'] : ['sign', 'verify'];
941
+ }
942
+ else if (algoLower === 'hmac') {
943
+ importAlgorithm = { name: 'HMAC', hash: 'SHA-256' };
944
+ keyUsages = ['sign', 'verify'];
945
+ }
946
+ else {
947
+ await writelnStderr(process, terminal, `crypto import: unsupported algorithm '${algorithm}'`);
948
+ return 1;
949
+ }
950
+ }
951
+ const key = keyFormat === 'jwk'
952
+ ? await crypto.subtle.importKey('jwk', keyMaterial, importAlgorithm, true, keyUsages)
953
+ : await crypto.subtle.importKey(keyFormat, keyMaterial, importAlgorithm, true, keyUsages);
954
+ let exported;
955
+ if ('publicKey' in key && 'privateKey' in key) {
956
+ const keyPair = key;
957
+ if (outputKeyFormat === 'spki') {
958
+ exported = await crypto.subtle.exportKey('spki', keyPair.publicKey);
959
+ }
960
+ else if (outputKeyFormat === 'pkcs8') {
961
+ exported = await crypto.subtle.exportKey('pkcs8', keyPair.privateKey);
962
+ }
963
+ else if (outputKeyFormat === 'jwk') {
964
+ exported = await crypto.subtle.exportKey('jwk', keyPair.privateKey);
965
+ }
966
+ else {
967
+ await writelnStderr(process, terminal, 'crypto import: asymmetric keys cannot be exported in raw format');
968
+ return 1;
969
+ }
970
+ }
971
+ else {
972
+ if (outputKeyFormat === 'pkcs8' || outputKeyFormat === 'spki') {
973
+ await writelnStderr(process, terminal, `crypto import: symmetric keys cannot be exported in ${outputKeyFormat} format`);
974
+ return 1;
975
+ }
976
+ exported = await crypto.subtle.exportKey(outputKeyFormat, key);
977
+ }
978
+ let outputData;
979
+ if (outputKeyFormat === 'jwk') {
980
+ outputData = new TextEncoder().encode(JSON.stringify(exported, null, 2));
981
+ }
982
+ else {
983
+ outputData = new Uint8Array(exported);
984
+ }
985
+ if (output) {
986
+ await writeUint8ArrayToFile(shell.context.fs.promises, path.resolve(shell.cwd, output), outputData);
987
+ }
988
+ else {
989
+ if (process?.stdout) {
990
+ const writer = process.stdout.getWriter();
991
+ try {
992
+ await writer.write(outputData);
993
+ if (outputKeyFormat === 'jwk') {
994
+ await writer.write(new TextEncoder().encode('\n'));
995
+ }
996
+ }
997
+ finally {
998
+ writer.releaseLock();
999
+ }
1000
+ }
1001
+ else {
1002
+ terminal.write(new TextDecoder().decode(outputData));
1003
+ }
1004
+ }
1005
+ return 0;
1006
+ }
1007
+ catch (error) {
1008
+ await writelnStderr(process, terminal, `crypto import: ${error instanceof Error ? error.message : 'Unknown error'}`);
1009
+ return 1;
1010
+ }
1011
+ }
1012
+ async function handleExport(shell, terminal, process, args) {
1013
+ const usage = `Usage: crypto export [OPTIONS]
1014
+
1015
+ Export keys to various formats.
1016
+
1017
+ Options:
1018
+ --key-file, -k FILE Key file (JWK format)
1019
+ --format, -f FORMAT Output format (jwk, raw, pkcs8, spki) (default: jwk)
1020
+ --output, -o FILE Output file (default: stdout)
1021
+ --help Display this help`;
1022
+ const parsed = parseArgs(args);
1023
+ if (parsed.help) {
1024
+ await writelnStderr(process, terminal, usage);
1025
+ return 0;
1026
+ }
1027
+ const keyFile = parsed['key-file'] || parsed.k;
1028
+ const formatValue = parsed.format || parsed.f || 'jwk';
1029
+ const format = (typeof formatValue === 'string' ? formatValue : 'jwk').toLowerCase();
1030
+ const output = parsed.output || parsed.o;
1031
+ if (!keyFile || typeof keyFile !== 'string') {
1032
+ await writelnStderr(process, terminal, 'crypto export: --key-file is required');
1033
+ return 1;
1034
+ }
1035
+ const keyFormat = SUPPORTED_KEY_FORMATS[format];
1036
+ if (!keyFormat) {
1037
+ await writelnStderr(process, terminal, `crypto export: unsupported format '${format}'`);
1038
+ return 1;
1039
+ }
1040
+ try {
1041
+ const keyPath = path.resolve(shell.cwd, keyFile);
1042
+ const keyData = await readFileToUint8Array(shell.context.fs.promises, keyPath);
1043
+ const keyJson = JSON.parse(new TextDecoder().decode(keyData));
1044
+ let importAlgorithm;
1045
+ let keyUsages;
1046
+ if (keyJson.kty === 'RSA') {
1047
+ importAlgorithm = { name: 'RSA-OAEP', hash: 'SHA-256' };
1048
+ keyUsages = keyJson.d ? ['decrypt', 'encrypt'] : ['encrypt'];
1049
+ }
1050
+ else if (keyJson.kty === 'EC') {
1051
+ importAlgorithm = { name: 'ECDSA', namedCurve: keyJson.crv || 'P-256' };
1052
+ keyUsages = keyJson.d ? ['sign', 'verify'] : ['verify'];
1053
+ }
1054
+ else if (keyJson.kty === 'oct') {
1055
+ importAlgorithm = { name: 'AES-GCM' };
1056
+ keyUsages = ['encrypt', 'decrypt'];
1057
+ }
1058
+ else {
1059
+ await writelnStderr(process, terminal, 'crypto export: unsupported key type in JWK');
1060
+ return 1;
1061
+ }
1062
+ const key = await crypto.subtle.importKey('jwk', keyJson, importAlgorithm, true, keyUsages);
1063
+ const exported = await crypto.subtle.exportKey(keyFormat, key);
1064
+ let outputData;
1065
+ if (keyFormat === 'jwk') {
1066
+ outputData = new TextEncoder().encode(JSON.stringify(exported, null, 2));
1067
+ }
1068
+ else {
1069
+ outputData = new Uint8Array(exported);
1070
+ }
1071
+ if (output) {
1072
+ await writeUint8ArrayToFile(shell.context.fs.promises, path.resolve(shell.cwd, output), outputData);
1073
+ }
1074
+ else {
1075
+ if (process?.stdout) {
1076
+ const writer = process.stdout.getWriter();
1077
+ try {
1078
+ await writer.write(outputData);
1079
+ if (keyFormat === 'jwk') {
1080
+ await writer.write(new TextEncoder().encode('\n'));
1081
+ }
1082
+ }
1083
+ finally {
1084
+ writer.releaseLock();
1085
+ }
1086
+ }
1087
+ else {
1088
+ terminal.write(new TextDecoder().decode(outputData));
1089
+ }
1090
+ }
1091
+ return 0;
1092
+ }
1093
+ catch (error) {
1094
+ await writelnStderr(process, terminal, `crypto export: ${error instanceof Error ? error.message : 'Unknown error'}`);
1095
+ return 1;
1096
+ }
1097
+ }
1098
+ async function handleDerive(shell, terminal, process, args) {
1099
+ const usage = `Usage: crypto derive [OPTIONS]
1100
+
1101
+ Derive keys from passwords or other keys.
1102
+
1103
+ Options:
1104
+ --algorithm, -a ALGORITHM Algorithm (PBKDF2, HKDF, ECDH)
1105
+ --password, -p PASSWORD Password (for PBKDF2/HKDF)
1106
+ --salt-file FILE Salt file (for PBKDF2/HKDF)
1107
+ --iterations, -i COUNT Iterations (for PBKDF2, default: 100000)
1108
+ --key-file FILE Private key file (for ECDH)
1109
+ --public-key-file FILE Public key file (for ECDH)
1110
+ --output, -o FILE Output file (default: stdout, JWK format)
1111
+ --help Display this help`;
1112
+ const parsed = parseArgs(args);
1113
+ if (parsed.help) {
1114
+ await writelnStderr(process, terminal, usage);
1115
+ return 0;
1116
+ }
1117
+ const algorithm = parsed.algorithm || parsed.a;
1118
+ const password = parsed.password || parsed.p;
1119
+ const saltFile = parsed['salt-file'];
1120
+ const iterations = parsed.iterations || parsed.i;
1121
+ const keyFile = parsed['key-file'];
1122
+ const publicKeyFile = parsed['public-key-file'];
1123
+ const output = parsed.output || parsed.o;
1124
+ if (!algorithm || typeof algorithm !== 'string') {
1125
+ await writelnStderr(process, terminal, 'crypto derive: --algorithm is required');
1126
+ return 1;
1127
+ }
1128
+ try {
1129
+ const algoLower = algorithm.toLowerCase();
1130
+ if (!SUPPORTED_DERIVE_ALGORITHMS[algoLower]) {
1131
+ await writelnStderr(process, terminal, `crypto derive: unsupported algorithm '${algorithm}'`);
1132
+ return 1;
1133
+ }
1134
+ let derivedKey;
1135
+ if (algoLower === 'pbkdf2') {
1136
+ if (!password || typeof password !== 'string') {
1137
+ await writelnStderr(process, terminal, 'crypto derive: --password is required for PBKDF2');
1138
+ return 1;
1139
+ }
1140
+ let salt;
1141
+ if (saltFile && typeof saltFile === 'string') {
1142
+ salt = await readFileToUint8Array(shell.context.fs.promises, path.resolve(shell.cwd, saltFile));
1143
+ }
1144
+ else {
1145
+ salt = crypto.getRandomValues(new Uint8Array(16));
1146
+ }
1147
+ const passwordKey = await crypto.subtle.importKey('raw', new TextEncoder().encode(password), 'PBKDF2', false, ['deriveBits', 'deriveKey']);
1148
+ const saltBuffer = toArrayBuffer(salt.buffer);
1149
+ derivedKey = await crypto.subtle.deriveKey({
1150
+ name: 'PBKDF2',
1151
+ salt: new Uint8Array(saltBuffer, salt.byteOffset, salt.length),
1152
+ iterations: iterations ? parseInt(iterations, 10) : 100000,
1153
+ hash: 'SHA-256'
1154
+ }, passwordKey, { name: 'AES-GCM', length: 256 }, true, ['encrypt', 'decrypt']);
1155
+ }
1156
+ else if (algoLower === 'hkdf') {
1157
+ if (!password || typeof password !== 'string') {
1158
+ await writelnStderr(process, terminal, 'crypto derive: --password is required for HKDF');
1159
+ return 1;
1160
+ }
1161
+ let salt;
1162
+ if (saltFile && typeof saltFile === 'string') {
1163
+ salt = await readFileToUint8Array(shell.context.fs.promises, path.resolve(shell.cwd, saltFile));
1164
+ }
1165
+ else {
1166
+ salt = crypto.getRandomValues(new Uint8Array(16));
1167
+ }
1168
+ const passwordKey = await crypto.subtle.importKey('raw', new TextEncoder().encode(password), 'HKDF', false, ['deriveBits', 'deriveKey']);
1169
+ const saltBuffer = toArrayBuffer(salt.buffer);
1170
+ derivedKey = await crypto.subtle.deriveKey({
1171
+ name: 'HKDF',
1172
+ salt: new Uint8Array(saltBuffer, salt.byteOffset, salt.length),
1173
+ hash: 'SHA-256',
1174
+ info: new Uint8Array(0)
1175
+ }, passwordKey, { name: 'AES-GCM', length: 256 }, true, ['encrypt', 'decrypt']);
1176
+ }
1177
+ else {
1178
+ if (!keyFile || typeof keyFile !== 'string') {
1179
+ await writelnStderr(process, terminal, 'crypto derive: --key-file is required for ECDH');
1180
+ return 1;
1181
+ }
1182
+ if (!publicKeyFile || typeof publicKeyFile !== 'string') {
1183
+ await writelnStderr(process, terminal, 'crypto derive: --public-key-file is required for ECDH');
1184
+ return 1;
1185
+ }
1186
+ const privateKeyData = await readFileToUint8Array(shell.context.fs.promises, path.resolve(shell.cwd, keyFile));
1187
+ const privateKeyJson = JSON.parse(new TextDecoder().decode(privateKeyData));
1188
+ const publicKeyData = await readFileToUint8Array(shell.context.fs.promises, path.resolve(shell.cwd, publicKeyFile));
1189
+ const publicKeyJson = JSON.parse(new TextDecoder().decode(publicKeyData));
1190
+ const privateKey = await crypto.subtle.importKey('jwk', privateKeyJson, { name: 'ECDH', namedCurve: 'P-256' }, false, ['deriveBits', 'deriveKey']);
1191
+ const publicKey = await crypto.subtle.importKey('jwk', publicKeyJson, { name: 'ECDH', namedCurve: 'P-256' }, false, []);
1192
+ derivedKey = await crypto.subtle.deriveKey({ name: 'ECDH', public: publicKey }, privateKey, { name: 'AES-GCM', length: 256 }, true, ['encrypt', 'decrypt']);
1193
+ }
1194
+ const exported = await crypto.subtle.exportKey('jwk', derivedKey);
1195
+ const outputData = new TextEncoder().encode(JSON.stringify(exported, null, 2));
1196
+ if (output) {
1197
+ await writeUint8ArrayToFile(shell.context.fs.promises, path.resolve(shell.cwd, output), outputData);
1198
+ }
1199
+ else {
1200
+ if (process?.stdout) {
1201
+ const writer = process.stdout.getWriter();
1202
+ try {
1203
+ await writer.write(outputData);
1204
+ await writer.write(new TextEncoder().encode('\n'));
1205
+ }
1206
+ finally {
1207
+ writer.releaseLock();
1208
+ }
1209
+ }
1210
+ else {
1211
+ terminal.write(new TextDecoder().decode(outputData));
1212
+ }
1213
+ }
1214
+ return 0;
1215
+ }
1216
+ catch (error) {
1217
+ await writelnStderr(process, terminal, `crypto derive: ${error instanceof Error ? error.message : 'Unknown error'}`);
1218
+ return 1;
1219
+ }
1220
+ }
1221
+ async function handleRandom(shell, terminal, process, args) {
1222
+ const usage = `Usage: crypto random [OPTIONS]
1223
+
1224
+ Generate random bytes.
1225
+
1226
+ Options:
1227
+ --length, -l LENGTH Number of bytes to generate (default: 32)
1228
+ --output, -o FILE Output file (default: stdout)
1229
+ --help Display this help`;
1230
+ const parsed = parseArgs(args);
1231
+ if (parsed.help) {
1232
+ await writelnStderr(process, terminal, usage);
1233
+ return 0;
1234
+ }
1235
+ const length = parsed.length || parsed.l;
1236
+ const output = parsed.output || parsed.o;
1237
+ const byteLength = length ? parseInt(length, 10) : 32;
1238
+ if (isNaN(byteLength) || byteLength <= 0) {
1239
+ await writelnStderr(process, terminal, 'crypto random: --length must be a positive number');
1240
+ return 1;
1241
+ }
1242
+ try {
1243
+ const randomBytes = crypto.getRandomValues(new Uint8Array(byteLength));
1244
+ if (output) {
1245
+ await writeUint8ArrayToFile(shell.context.fs.promises, path.resolve(shell.cwd, output), randomBytes);
1246
+ }
1247
+ else {
1248
+ if (process?.stdout) {
1249
+ const writer = process.stdout.getWriter();
1250
+ try {
1251
+ await writer.write(randomBytes);
1252
+ }
1253
+ finally {
1254
+ writer.releaseLock();
1255
+ }
1256
+ }
1257
+ else {
1258
+ terminal.write(new TextDecoder().decode(randomBytes));
1259
+ }
1260
+ }
1261
+ return 0;
1262
+ }
1263
+ catch (error) {
1264
+ await writelnStderr(process, terminal, `crypto random: ${error instanceof Error ? error.message : 'Unknown error'}`);
1265
+ return 1;
1266
+ }
1267
+ }
1268
+ export function createCommand(kernel, shell, terminal) {
1269
+ return new TerminalCommand({
1270
+ command: 'crypto',
1271
+ description: 'Cryptographic utilities using the Web Crypto API',
1272
+ kernel,
1273
+ shell,
1274
+ terminal,
1275
+ run: async (pid, argv) => {
1276
+ const process = kernel.processes.get(pid);
1277
+ if (!process)
1278
+ return 1;
1279
+ if (argv.length > 0 && (argv[0] === '--help' || argv[0] === '-h')) {
1280
+ printUsage(process, terminal);
1281
+ return 0;
1282
+ }
1283
+ if (argv.length === 0) {
1284
+ printUsage(process, terminal);
1285
+ return 0;
1286
+ }
1287
+ const subcommand = argv[0]?.toLowerCase();
1288
+ const subArgs = argv.slice(1);
1289
+ try {
1290
+ switch (subcommand) {
1291
+ case 'generate':
1292
+ return await handleGenerate(shell, terminal, process, subArgs);
1293
+ case 'encrypt':
1294
+ return await handleEncrypt(shell, terminal, process, subArgs);
1295
+ case 'decrypt':
1296
+ return await handleDecrypt(shell, terminal, process, subArgs);
1297
+ case 'sign':
1298
+ return await handleSign(shell, terminal, process, subArgs);
1299
+ case 'verify':
1300
+ return await handleVerify(shell, terminal, process, subArgs);
1301
+ case 'import':
1302
+ return await handleImport(shell, terminal, process, subArgs);
1303
+ case 'export':
1304
+ return await handleExport(shell, terminal, process, subArgs);
1305
+ case 'derive':
1306
+ return await handleDerive(shell, terminal, process, subArgs);
1307
+ case 'random':
1308
+ return await handleRandom(shell, terminal, process, subArgs);
1309
+ default:
1310
+ await writelnStderr(process, terminal, chalk.red(`Error: Unknown subcommand: ${subcommand}`));
1311
+ await writelnStderr(process, terminal, 'Run "crypto --help" for usage information');
1312
+ return 1;
1313
+ }
1314
+ }
1315
+ catch (error) {
1316
+ await writelnStderr(process, terminal, chalk.red(`Error: ${error instanceof Error ? error.message : String(error)}`));
1317
+ return 1;
1318
+ }
1319
+ }
1320
+ });
1321
+ }
1322
+ //# sourceMappingURL=crypto.js.map