@sebastienrousseau/crypto-cli 0.0.8 → 0.0.24
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.
- package/README.md +685 -3
- package/dist/cli.js +37 -196
- package/dist/commands/decrypt.command.js +2 -2
- package/dist/commands/encrypt.command.js +2 -2
- package/dist/commands/generate.command.js +2 -2
- package/dist/commands/modern/cbom.command.d.ts +2 -1
- package/dist/commands/modern/cbom.command.d.ts.map +1 -1
- package/dist/commands/modern/cbom.command.js +99 -146
- package/dist/commands/modern/cbom.core.d.ts +5 -0
- package/dist/commands/modern/cbom.core.d.ts.map +1 -0
- package/dist/commands/modern/cbom.core.js +57 -0
- package/dist/commands/modern/encrypt.command.d.ts.map +1 -1
- package/dist/commands/modern/encrypt.command.js +42 -63
- package/dist/commands/modern/hash.command.d.ts.map +1 -1
- package/dist/commands/modern/hash.command.js +1 -1
- package/dist/commands/modern/keygen.command.js +1 -1
- package/dist/commands/modern/password-hash.command.d.ts.map +1 -1
- package/dist/commands/modern/password-hash.command.js +28 -27
- package/dist/commands/modern/sign.command.d.ts.map +1 -1
- package/dist/commands/modern/sign.command.js +83 -73
- package/dist/commands/reformat.command.js +2 -2
- package/dist/commands/revoke.command.js +2 -2
- package/dist/commands/session.command.js +2 -2
- package/dist/commands/sign.command.js +2 -2
- package/dist/commands/verify.command.js +2 -2
- package/dist/menu.d.ts +12 -0
- package/dist/menu.d.ts.map +1 -0
- package/dist/menu.js +107 -0
- package/dist/program/cbom.d.ts +4 -0
- package/dist/program/cbom.d.ts.map +1 -0
- package/dist/program/cbom.js +49 -0
- package/dist/program/encrypt.d.ts +6 -0
- package/dist/program/encrypt.d.ts.map +1 -0
- package/dist/program/encrypt.js +73 -0
- package/dist/program/hash.d.ts +4 -0
- package/dist/program/hash.d.ts.map +1 -0
- package/dist/program/hash.js +21 -0
- package/dist/program/hpke.d.ts +4 -0
- package/dist/program/hpke.d.ts.map +1 -0
- package/dist/program/hpke.js +111 -0
- package/dist/program/index.d.ts +7 -0
- package/dist/program/index.d.ts.map +1 -0
- package/dist/program/index.js +77 -0
- package/dist/program/io.d.ts +35 -0
- package/dist/program/io.d.ts.map +1 -0
- package/dist/program/io.js +65 -0
- package/dist/program/keygen.d.ts +4 -0
- package/dist/program/keygen.d.ts.map +1 -0
- package/dist/program/keygen.js +32 -0
- package/dist/program/kms.d.ts +8 -0
- package/dist/program/kms.d.ts.map +1 -0
- package/dist/program/kms.js +247 -0
- package/dist/program/opaque.d.ts +4 -0
- package/dist/program/opaque.d.ts.map +1 -0
- package/dist/program/opaque.js +209 -0
- package/dist/program/password.d.ts +4 -0
- package/dist/program/password.d.ts.map +1 -0
- package/dist/program/password.js +73 -0
- package/dist/program/secrets.d.ts +30 -0
- package/dist/program/secrets.d.ts.map +1 -0
- package/dist/program/secrets.js +70 -0
- package/dist/program/sign.d.ts +18 -0
- package/dist/program/sign.d.ts.map +1 -0
- package/dist/program/sign.js +130 -0
- package/dist/program/stream-multi.d.ts +7 -0
- package/dist/program/stream-multi.d.ts.map +1 -0
- package/dist/program/stream-multi.js +163 -0
- package/dist/program/stream.d.ts +6 -0
- package/dist/program/stream.d.ts.map +1 -0
- package/dist/program/stream.js +102 -0
- package/dist/program/usage.d.ts +7 -0
- package/dist/program/usage.d.ts.map +1 -0
- package/dist/program/usage.js +36 -0
- package/package.json +21 -34
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.registerVerify = exports.registerSign = exports.parseKeyFile = exports.SIGNING_ALGORITHMS = void 0;
|
|
4
|
+
const promises_1 = require("node:fs/promises");
|
|
5
|
+
const commander_1 = require("commander");
|
|
6
|
+
const modern_1 = require("@sebastienrousseau/crypto-lib/modern");
|
|
7
|
+
const io_1 = require("./io");
|
|
8
|
+
const secrets_1 = require("./secrets");
|
|
9
|
+
const mlDsa = (level) => ({
|
|
10
|
+
sign: (key, msg) => (0, modern_1.mlDsaSign)(level, key, msg).signature,
|
|
11
|
+
verify: (key, msg, sig) => (0, modern_1.mlDsaVerify)(level, key, msg, sig).valid,
|
|
12
|
+
});
|
|
13
|
+
const SIGNERS = {
|
|
14
|
+
ed25519: {
|
|
15
|
+
sign: (key, msg) => (0, modern_1.ed25519Sign)(key, msg).signature,
|
|
16
|
+
verify: (key, msg, sig) => (0, modern_1.ed25519Verify)(key, msg, sig).valid,
|
|
17
|
+
},
|
|
18
|
+
ed448: {
|
|
19
|
+
sign: (key, msg) => (0, modern_1.ed448Sign)(key, msg).signature,
|
|
20
|
+
verify: (key, msg, sig) => (0, modern_1.ed448Verify)(key, msg, sig).valid,
|
|
21
|
+
},
|
|
22
|
+
p256: {
|
|
23
|
+
sign: (key, msg) => (0, modern_1.p256Sign)(key, msg).signature,
|
|
24
|
+
verify: (key, msg, sig) => (0, modern_1.p256Verify)(key, msg, sig).valid,
|
|
25
|
+
},
|
|
26
|
+
p384: {
|
|
27
|
+
sign: (key, msg) => (0, modern_1.p384Sign)(key, msg).signature,
|
|
28
|
+
verify: (key, msg, sig) => (0, modern_1.p384Verify)(key, msg, sig).valid,
|
|
29
|
+
},
|
|
30
|
+
"ml-dsa-44": mlDsa(44),
|
|
31
|
+
"ml-dsa-65": mlDsa(65),
|
|
32
|
+
"ml-dsa-87": mlDsa(87),
|
|
33
|
+
};
|
|
34
|
+
exports.SIGNING_ALGORITHMS = Object.keys(SIGNERS);
|
|
35
|
+
const HEX = /^(?:[0-9a-fA-F]{2})+$/;
|
|
36
|
+
const asRecord = (value) => typeof value === "object" && value !== null
|
|
37
|
+
? value
|
|
38
|
+
: {};
|
|
39
|
+
const parseJson = (raw) => {
|
|
40
|
+
try {
|
|
41
|
+
return JSON.parse(raw.toString("utf8"));
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
throw new Error("the key file is not JSON from crypto-cli keygen --json");
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
const parseKeyFile = (raw, field) => {
|
|
48
|
+
const key = asRecord(parseJson(raw));
|
|
49
|
+
const { algorithm, kid } = key;
|
|
50
|
+
if (typeof algorithm !== "string" || !Object.hasOwn(SIGNERS, algorithm)) {
|
|
51
|
+
throw new Error(`the key must be a signing key from crypto-cli keygen --json ` +
|
|
52
|
+
`(${exports.SIGNING_ALGORITHMS.join(", ")})`);
|
|
53
|
+
}
|
|
54
|
+
const material = key[field];
|
|
55
|
+
if (typeof material !== "string") {
|
|
56
|
+
throw new Error(`the key file has no ${field}`);
|
|
57
|
+
}
|
|
58
|
+
return {
|
|
59
|
+
algorithm,
|
|
60
|
+
kid: typeof kid === "string" ? kid : undefined,
|
|
61
|
+
material,
|
|
62
|
+
signer: SIGNERS[algorithm],
|
|
63
|
+
};
|
|
64
|
+
};
|
|
65
|
+
exports.parseKeyFile = parseKeyFile;
|
|
66
|
+
const readKey = async (ctx, opts, file, field) => {
|
|
67
|
+
const source = { file: opts.keyFile, stdin: opts.keyStdin };
|
|
68
|
+
const raw = await (0, secrets_1.requireSecret)({
|
|
69
|
+
io: ctx.io,
|
|
70
|
+
source,
|
|
71
|
+
flags: secrets_1.KEY_FLAGS,
|
|
72
|
+
dataOnStdin: (0, io_1.isStdin)(file),
|
|
73
|
+
checkMode: field === "privateKey",
|
|
74
|
+
}, "key");
|
|
75
|
+
return (0, exports.parseKeyFile)(raw, field);
|
|
76
|
+
};
|
|
77
|
+
const JSON_OPTION = ["--json", "print the result as one line of JSON"];
|
|
78
|
+
const signatureCommand = (program, name, description, key) => (0, secrets_1.withSecretOptions)(program
|
|
79
|
+
.command(name)
|
|
80
|
+
.description(description)
|
|
81
|
+
.argument("[file]", `file to ${name}; '-' or omitted reads standard input`), secrets_1.KEY_FLAGS, { ...key, takesData: true });
|
|
82
|
+
const registerSign = (program, ctx) => signatureCommand(program, "sign", "Sign a file with a key pair from keygen --json and print the " +
|
|
83
|
+
"signature in hex", {
|
|
84
|
+
what: "key pair",
|
|
85
|
+
format: `keygen --json output (${exports.SIGNING_ALGORITHMS.join(", ")})`,
|
|
86
|
+
})
|
|
87
|
+
.option(...JSON_OPTION)
|
|
88
|
+
.action(async (file, opts) => {
|
|
89
|
+
const key = await readKey(ctx, opts, file, "privateKey");
|
|
90
|
+
const signature = key.signer.sign(key.material, await (0, io_1.readInput)(file, ctx.io));
|
|
91
|
+
const result = { signature, algorithm: key.algorithm, kid: key.kid };
|
|
92
|
+
(0, io_1.emit)(ctx.io, Boolean(opts.json), result, signature);
|
|
93
|
+
});
|
|
94
|
+
exports.registerSign = registerSign;
|
|
95
|
+
const readSignature = async (opts) => {
|
|
96
|
+
if (opts.signature !== undefined)
|
|
97
|
+
return opts.signature.trim();
|
|
98
|
+
if (opts.signatureFile === undefined) {
|
|
99
|
+
throw new io_1.UsageError("missing signature: pass --signature <hex> or --signature-file <path>");
|
|
100
|
+
}
|
|
101
|
+
return (0, io_1.valueOrJsonField)(await (0, promises_1.readFile)(opts.signatureFile), "signature");
|
|
102
|
+
};
|
|
103
|
+
const checkSignature = (key, data, signature) => {
|
|
104
|
+
if (!HEX.test(signature))
|
|
105
|
+
return false;
|
|
106
|
+
try {
|
|
107
|
+
return key.signer.verify(key.material, data, signature);
|
|
108
|
+
}
|
|
109
|
+
catch {
|
|
110
|
+
return false;
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
const registerVerify = (program, ctx) => signatureCommand(program, "verify", "Verify a signature from sign; exits 1 when it is not valid", {
|
|
114
|
+
what: "public key",
|
|
115
|
+
format: "keygen --json output, or JSON with algorithm and publicKey",
|
|
116
|
+
})
|
|
117
|
+
.addOption(new commander_1.Option("--signature <hex>", "the signature, in hex").conflicts("signatureFile"))
|
|
118
|
+
.option("--signature-file <path>", "read the signature from a file (sign output or its --json line)")
|
|
119
|
+
.option(...JSON_OPTION)
|
|
120
|
+
.action(async (file, opts) => {
|
|
121
|
+
const signature = await readSignature(opts);
|
|
122
|
+
const key = await readKey(ctx, opts, file, "publicKey");
|
|
123
|
+
const valid = checkSignature(key, await (0, io_1.readInput)(file, ctx.io), signature);
|
|
124
|
+
const result = { valid, algorithm: key.algorithm, kid: key.kid };
|
|
125
|
+
(0, io_1.emit)(ctx.io, Boolean(opts.json), result, valid ? "valid" : "invalid");
|
|
126
|
+
if (!valid)
|
|
127
|
+
ctx.exitCode = io_1.EXIT.FAILURE;
|
|
128
|
+
});
|
|
129
|
+
exports.registerVerify = registerVerify;
|
|
130
|
+
//# sourceMappingURL=sign.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"}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.registerStream = exports.parseCiphertext = exports.parseChunkSize = void 0;
|
|
4
|
+
const crypto_lib_1 = require("@sebastienrousseau/crypto-lib");
|
|
5
|
+
const streaming_1 = require("@sebastienrousseau/crypto-lib/streaming");
|
|
6
|
+
const io_1 = require("./io");
|
|
7
|
+
const stream_multi_1 = require("./stream-multi");
|
|
8
|
+
const BASE64 = /^[A-Za-z0-9+/]+={0,2}$/;
|
|
9
|
+
const parseChunkSize = (value) => {
|
|
10
|
+
if (value === undefined)
|
|
11
|
+
return undefined;
|
|
12
|
+
const size = Number(value);
|
|
13
|
+
if (!Number.isSafeInteger(size) || size < 64) {
|
|
14
|
+
throw new io_1.UsageError("--chunk-size must be an integer >= 64");
|
|
15
|
+
}
|
|
16
|
+
return size;
|
|
17
|
+
};
|
|
18
|
+
exports.parseChunkSize = parseChunkSize;
|
|
19
|
+
const parseCiphertext = (input) => {
|
|
20
|
+
const text = (0, io_1.valueOrJsonField)(input, "ciphertext");
|
|
21
|
+
if (!BASE64.test(text)) {
|
|
22
|
+
throw new Error("the input is not base64 from crypto-cli stream encrypt");
|
|
23
|
+
}
|
|
24
|
+
return Buffer.from(text, "base64");
|
|
25
|
+
};
|
|
26
|
+
exports.parseCiphertext = parseCiphertext;
|
|
27
|
+
const registerStreamEncrypt = (stream, ctx) => {
|
|
28
|
+
stream
|
|
29
|
+
.command("encrypt")
|
|
30
|
+
.description("Encrypt with post-quantum hybrid STREAM AEAD (X25519 + ML-KEM-768)")
|
|
31
|
+
.argument("[file]", "file to encrypt; '-' or omitted reads standard input")
|
|
32
|
+
.option("-x, --x25519-public <hex>", "recipient X25519 public key (hex)")
|
|
33
|
+
.option("-m, --ml-kem-public <hex>", "recipient ML-KEM-768 public key (hex)")
|
|
34
|
+
.option("-c, --chunk-size <bytes>", "chunk size in bytes (minimum 64, default 65536)")
|
|
35
|
+
.option("--json", "print the result as one line of JSON")
|
|
36
|
+
.action(async (file, opts) => {
|
|
37
|
+
if (!opts.x25519Public || !opts.mlKemPublic) {
|
|
38
|
+
throw new io_1.UsageError("missing recipient public key: both --x25519-public and --ml-kem-public are required");
|
|
39
|
+
}
|
|
40
|
+
const chunkSize = (0, exports.parseChunkSize)(opts.chunkSize);
|
|
41
|
+
const input = await (0, io_1.readInput)(file, ctx.io);
|
|
42
|
+
const result = (0, streaming_1.streamPqEncrypt)({
|
|
43
|
+
recipientX25519Public: opts.x25519Public,
|
|
44
|
+
recipientMlKemPublic: opts.mlKemPublic,
|
|
45
|
+
plaintext: input,
|
|
46
|
+
...(chunkSize !== undefined ? { chunkSize } : {}),
|
|
47
|
+
});
|
|
48
|
+
const b64 = Buffer.from(result.ciphertext).toString("base64");
|
|
49
|
+
(0, io_1.emit)(ctx.io, Boolean(opts.json), { ciphertext: b64, algorithm: result.algorithm }, b64);
|
|
50
|
+
});
|
|
51
|
+
};
|
|
52
|
+
const registerStreamDecrypt = (stream, ctx) => {
|
|
53
|
+
stream
|
|
54
|
+
.command("decrypt")
|
|
55
|
+
.description("Decrypt post-quantum hybrid STREAM ciphertext; exits 1 on error")
|
|
56
|
+
.argument("[file]", "output of stream encrypt (base64 or its --json line)")
|
|
57
|
+
.option("-x, --x25519-secret <hex>", "recipient X25519 secret key (hex)")
|
|
58
|
+
.option("-m, --ml-kem-secret <hex>", "recipient ML-KEM-768 secret key (hex)")
|
|
59
|
+
.option("-c, --chunk-size <bytes>", "chunk size in bytes")
|
|
60
|
+
.option("--json", "print the result as one line of JSON")
|
|
61
|
+
.action(async (file, opts) => {
|
|
62
|
+
if (!opts.x25519Secret || !opts.mlKemSecret) {
|
|
63
|
+
throw new io_1.UsageError("missing recipient secret key: both --x25519-secret and --ml-kem-secret are required");
|
|
64
|
+
}
|
|
65
|
+
const chunkSize = (0, exports.parseChunkSize)(opts.chunkSize);
|
|
66
|
+
const ciphertext = (0, exports.parseCiphertext)(await (0, io_1.readInput)(file, ctx.io));
|
|
67
|
+
let plaintext;
|
|
68
|
+
try {
|
|
69
|
+
plaintext = (0, streaming_1.streamPqDecrypt)({
|
|
70
|
+
recipientX25519Secret: opts.x25519Secret,
|
|
71
|
+
recipientMlKemSecret: opts.mlKemSecret,
|
|
72
|
+
ciphertext,
|
|
73
|
+
...(chunkSize !== undefined ? { chunkSize } : {}),
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
catch {
|
|
77
|
+
throw new Error("decryption failed: wrong key, or the input was modified or truncated");
|
|
78
|
+
}
|
|
79
|
+
if (!opts.json) {
|
|
80
|
+
ctx.io.stdout(plaintext);
|
|
81
|
+
(0, crypto_lib_1.wipeMemory)(plaintext);
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
const b64 = Buffer.from(plaintext).toString("base64");
|
|
85
|
+
(0, crypto_lib_1.wipeMemory)(plaintext);
|
|
86
|
+
(0, io_1.emit)(ctx.io, true, {
|
|
87
|
+
plaintext: b64,
|
|
88
|
+
encoding: "base64",
|
|
89
|
+
algorithm: "x25519-ml-kem-768-xchacha20-poly1305",
|
|
90
|
+
}, "");
|
|
91
|
+
});
|
|
92
|
+
};
|
|
93
|
+
const registerStream = (program, ctx) => {
|
|
94
|
+
const stream = program
|
|
95
|
+
.command("stream")
|
|
96
|
+
.description("Post-quantum hybrid streaming AEAD operations");
|
|
97
|
+
registerStreamEncrypt(stream, ctx);
|
|
98
|
+
registerStreamDecrypt(stream, ctx);
|
|
99
|
+
(0, stream_multi_1.registerStreamMulti)(stream, ctx);
|
|
100
|
+
};
|
|
101
|
+
exports.registerStream = registerStream;
|
|
102
|
+
//# sourceMappingURL=stream.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export declare const USAGE_MARKERS: {
|
|
2
|
+
readonly start: "<!-- cli-usage:start (generated: pnpm run readme:usage) -->";
|
|
3
|
+
readonly end: "<!-- cli-usage:end -->";
|
|
4
|
+
};
|
|
5
|
+
export declare const renderUsage: () => string;
|
|
6
|
+
export declare const replaceUsage: (readme: string) => string;
|
|
7
|
+
//# sourceMappingURL=usage.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"usage.d.ts","sourceRoot":"","sources":["../../src/program/usage.ts"],"names":[],"mappings":"AASA,eAAO,MAAM,aAAa;;;CAGhB,CAAC;AAgBX,eAAO,MAAM,WAAW,QAAO,MAQ9B,CAAC;AAQF,eAAO,MAAM,YAAY,GAAI,QAAQ,MAAM,KAAG,MAW7C,CAAC"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.replaceUsage = exports.renderUsage = exports.USAGE_MARKERS = void 0;
|
|
4
|
+
const index_1 = require("./index");
|
|
5
|
+
exports.USAGE_MARKERS = {
|
|
6
|
+
start: "<!-- cli-usage:start (generated: pnpm run readme:usage) -->",
|
|
7
|
+
end: "<!-- cli-usage:end -->",
|
|
8
|
+
};
|
|
9
|
+
const walk = (command, prefix) => {
|
|
10
|
+
const name = prefix ? `${prefix} ${command.name()}` : command.name();
|
|
11
|
+
return [
|
|
12
|
+
[name, command],
|
|
13
|
+
...command.commands.flatMap((sub) => walk(sub, name)),
|
|
14
|
+
];
|
|
15
|
+
};
|
|
16
|
+
const renderUsage = () => {
|
|
17
|
+
const program = (0, index_1.buildProgram)({ io: (0, index_1.processIO)(), exitCode: index_1.EXIT.OK }, "");
|
|
18
|
+
const sections = walk(program, "").map(([name, command]) => {
|
|
19
|
+
command.configureHelp({ helpWidth: 80 });
|
|
20
|
+
return `#### \`${name}\`\n\n\`\`\`text\n${command.helpInformation()}\`\`\`\n`;
|
|
21
|
+
});
|
|
22
|
+
return [exports.USAGE_MARKERS.start, "", ...sections, exports.USAGE_MARKERS.end].join("\n");
|
|
23
|
+
};
|
|
24
|
+
exports.renderUsage = renderUsage;
|
|
25
|
+
const replaceUsage = (readme) => {
|
|
26
|
+
const start = readme.indexOf(exports.USAGE_MARKERS.start);
|
|
27
|
+
const end = readme.indexOf(exports.USAGE_MARKERS.end);
|
|
28
|
+
if (start < 0 || end < start) {
|
|
29
|
+
throw new Error("README.md has no cli-usage block");
|
|
30
|
+
}
|
|
31
|
+
return (readme.slice(0, start) +
|
|
32
|
+
(0, exports.renderUsage)() +
|
|
33
|
+
readme.slice(end + exports.USAGE_MARKERS.end.length));
|
|
34
|
+
};
|
|
35
|
+
exports.replaceUsage = replaceUsage;
|
|
36
|
+
//# sourceMappingURL=usage.js.map
|
package/package.json
CHANGED
|
@@ -12,51 +12,46 @@
|
|
|
12
12
|
"source": "git",
|
|
13
13
|
"target": "git://github.com/sebastienrousseau/crypto-service.git"
|
|
14
14
|
},
|
|
15
|
+
"type": "commonjs",
|
|
15
16
|
"bin": {
|
|
16
17
|
"crypto-cli": "dist/cli.js",
|
|
17
18
|
"cryptocli": "dist/cli.js"
|
|
18
19
|
},
|
|
20
|
+
"exports": {
|
|
21
|
+
"./package.json": "./package.json"
|
|
22
|
+
},
|
|
19
23
|
"bugs": {
|
|
20
24
|
"url": "https://github.com/sebastienrousseau/crypto-service/issues"
|
|
21
25
|
},
|
|
22
26
|
"dependencies": {
|
|
23
|
-
"
|
|
27
|
+
"commander": "^15.0.0",
|
|
28
|
+
"figlet": "^1.12.0",
|
|
24
29
|
"kleur": "^4.1.5",
|
|
25
30
|
"prompts": "^2.4.2",
|
|
26
|
-
"@sebastienrousseau/crypto-
|
|
27
|
-
"@sebastienrousseau/crypto-lib": "0.0.
|
|
31
|
+
"@sebastienrousseau/crypto-kms": "0.0.24",
|
|
32
|
+
"@sebastienrousseau/crypto-lib": "0.0.24",
|
|
33
|
+
"@sebastienrousseau/crypto-cbom": "0.0.24"
|
|
28
34
|
},
|
|
29
35
|
"description": "The Crypto Lib Command Line Interface (CLI) is a unified tool to perform common cryptographic operations such as key generation, data encryption, digital signing, and signature verification which are invoked from the command prompt or terminal.",
|
|
30
36
|
"devDependencies": {
|
|
31
37
|
"@sebastienrousseau/c8-config": "^0.0.7",
|
|
32
38
|
"@sebastienrousseau/eslint-config": "^0.0.7",
|
|
33
39
|
"@sebastienrousseau/jsdoc-config": "^0.0.7",
|
|
34
|
-
"@sebastienrousseau/markdownlint-config": "^0.0.
|
|
35
|
-
"@sebastienrousseau/mocha-config": "^0.0.
|
|
40
|
+
"@sebastienrousseau/markdownlint-config": "^0.0.7",
|
|
41
|
+
"@sebastienrousseau/mocha-config": "^0.0.7",
|
|
36
42
|
"@sebastienrousseau/prettier-config": "^0.0.7",
|
|
37
|
-
"@
|
|
38
|
-
"@types/chai": "^4.3.7",
|
|
43
|
+
"@types/chai": "^5.2.3",
|
|
39
44
|
"@types/chai-as-promised": "^8.0.2",
|
|
40
45
|
"@types/figlet": "^1.5.6",
|
|
41
|
-
"@types/mocha": "^10.0.
|
|
46
|
+
"@types/mocha": "^10.0.10",
|
|
42
47
|
"@types/node": "^26.6.3",
|
|
43
48
|
"@types/prompts": "^2.4.5",
|
|
44
|
-
"@typescript-eslint/eslint-plugin": "^6.21.0",
|
|
45
|
-
"@typescript-eslint/parser": "^6.21.0",
|
|
46
49
|
"c8": "^12.0.0",
|
|
47
|
-
"chai": "^
|
|
48
|
-
"chai-as-promised": "^
|
|
49
|
-
"eslint": "^8.57.1",
|
|
50
|
-
"eslint-import-resolver-typescript": "^3.10.1",
|
|
51
|
-
"eslint-plugin-import": "^2.28.1",
|
|
50
|
+
"chai": "^6.3.0",
|
|
51
|
+
"chai-as-promised": "^8.0.2",
|
|
52
52
|
"filesizes": "^0.1.2",
|
|
53
|
-
"mocha": "^
|
|
54
|
-
"prettier": "^3.9.
|
|
55
|
-
"remark-cli": "^12.0.0",
|
|
56
|
-
"remark-footnotes": "^5.0.0",
|
|
57
|
-
"remark-preset-lint-consistent": "^6.0.1",
|
|
58
|
-
"remark-preset-lint-markdown-style-guide": "^5.1.3",
|
|
59
|
-
"remark-preset-lint-recommended": "^7.0.1",
|
|
53
|
+
"mocha": "^12.0.3",
|
|
54
|
+
"prettier": "^3.9.9",
|
|
60
55
|
"rimraf": "^6.1.3",
|
|
61
56
|
"ts-node": "^10.9.1",
|
|
62
57
|
"typedoc": "^0.28.20",
|
|
@@ -96,29 +91,21 @@
|
|
|
96
91
|
],
|
|
97
92
|
"license": "MIT OR Apache-2.0",
|
|
98
93
|
"license_URI": "http://www.opensource.org/licenses/mit-license.php",
|
|
99
|
-
"main": "./dist/cli.js",
|
|
100
|
-
"exports": {
|
|
101
|
-
".": {
|
|
102
|
-
"types": "./dist/index.d.ts",
|
|
103
|
-
"require": "./dist/cli.js",
|
|
104
|
-
"import": "./dist/cli.js"
|
|
105
|
-
}
|
|
106
|
-
},
|
|
107
94
|
"name": "@sebastienrousseau/crypto-cli",
|
|
108
95
|
"private": false,
|
|
109
96
|
"publishConfig": {
|
|
110
97
|
"access": "public"
|
|
111
98
|
},
|
|
112
|
-
"types": "./dist/index.d.ts",
|
|
113
99
|
"sideEffects": false,
|
|
114
|
-
"version": "0.0.
|
|
100
|
+
"version": "0.0.24",
|
|
115
101
|
"scripts": {
|
|
116
102
|
"build": "tsc --build tsconfig.json",
|
|
117
103
|
"clean": "rimraf ./coverage ./dist ./docs",
|
|
118
|
-
"lint": "eslint
|
|
119
|
-
"lint:fix": "eslint --cache --fix
|
|
104
|
+
"lint": "eslint src __tests__",
|
|
105
|
+
"lint:fix": "eslint --cache --fix src __tests__",
|
|
120
106
|
"format": "prettier --write src/**/*.ts",
|
|
121
107
|
"format:check": "prettier --check \"src/**/*.ts\"",
|
|
108
|
+
"readme:usage": "node scripts/readme-usage.mjs",
|
|
122
109
|
"start": "node dist/cli.js",
|
|
123
110
|
"test": "c8 --check-coverage --lines 100 --branches 100 --functions 100 --reporter=lcov --reporter=text mocha --config=.mocharc.cjs",
|
|
124
111
|
"docs": "typedoc"
|