@pqc-sdk/cli 0.2.0 → 0.3.0
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/dist/index.js +35 -4
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -168,10 +168,26 @@ import { existsSync as existsSync3 } from "fs";
|
|
|
168
168
|
import { defineCommand as defineCommand2 } from "citty";
|
|
169
169
|
|
|
170
170
|
// src/keyfiles.ts
|
|
171
|
-
import { chmod, mkdir, writeFile } from "fs/promises";
|
|
171
|
+
import { chmod, mkdir, readFile as readFile2, writeFile } from "fs/promises";
|
|
172
172
|
import { existsSync as existsSync2 } from "fs";
|
|
173
173
|
import { join as join2 } from "path";
|
|
174
174
|
import { SUPPORTED_ALGORITHMS, pqc } from "@pqc-sdk/core";
|
|
175
|
+
var KEY_IGNORE_PATTERNS = ["keys/", "*.secret.pqc"];
|
|
176
|
+
async function ensureKeysIgnored(cwd) {
|
|
177
|
+
const gitignorePath = join2(cwd, ".gitignore");
|
|
178
|
+
const current = existsSync2(gitignorePath) ? await readFile2(gitignorePath, "utf8") : "";
|
|
179
|
+
const present = new Set(current.split(/\r?\n/).map((line) => line.trim()));
|
|
180
|
+
const missing = KEY_IGNORE_PATTERNS.filter((pattern) => !present.has(pattern));
|
|
181
|
+
if (missing.length === 0) {
|
|
182
|
+
return [];
|
|
183
|
+
}
|
|
184
|
+
const prefix = current === "" ? "" : current.endsWith("\n") ? "\n" : "\n\n";
|
|
185
|
+
const block = `${prefix}# PQC key material \u2014 never commit secret keys
|
|
186
|
+
${missing.join("\n")}
|
|
187
|
+
`;
|
|
188
|
+
await writeFile(gitignorePath, current + block);
|
|
189
|
+
return [...missing];
|
|
190
|
+
}
|
|
175
191
|
function assertSupportedAlgorithm(value) {
|
|
176
192
|
if (!SUPPORTED_ALGORITHMS.includes(value)) {
|
|
177
193
|
throw new Error(
|
|
@@ -253,11 +269,22 @@ var init = defineCommand2({
|
|
|
253
269
|
await writeFile2(CONFIG_FILE, `${JSON.stringify(CONFIG, null, 2)}
|
|
254
270
|
`);
|
|
255
271
|
const keys = await writeKeyPair(CONFIG.keysDir, "dev", CONFIG.defaultAlgorithm, false);
|
|
256
|
-
await
|
|
272
|
+
const ignored = await ensureKeysIgnored(process.cwd());
|
|
273
|
+
const exampleWritten = !existsSync3("example.ts");
|
|
274
|
+
if (exampleWritten) {
|
|
275
|
+
await writeFile2("example.ts", EXAMPLE);
|
|
276
|
+
}
|
|
257
277
|
heading("PQC project initialized:");
|
|
258
278
|
item(`${CONFIG_FILE} \u2014 configuration with safe defaults`);
|
|
259
279
|
item(`${keys.publicPath} / ${keys.secretPath} \u2014 development ${keys.algorithm} pair`);
|
|
260
|
-
|
|
280
|
+
if (ignored.length > 0) {
|
|
281
|
+
item(`.gitignore \u2014 added ${ignored.join(", ")} so secret keys are never committed`);
|
|
282
|
+
}
|
|
283
|
+
if (exampleWritten) {
|
|
284
|
+
item("example.ts \u2014 full roundtrip ready to run");
|
|
285
|
+
} else {
|
|
286
|
+
warn("example.ts already exists \u2014 left untouched.");
|
|
287
|
+
}
|
|
261
288
|
console.log();
|
|
262
289
|
warn("The keys/dev.* keys are for development ONLY \u2014 do NOT use them in production.");
|
|
263
290
|
ok(`Next step: run example.ts (node --experimental-strip-types example.ts or tsx)`);
|
|
@@ -296,9 +323,13 @@ var keygen = defineCommand3({
|
|
|
296
323
|
const algorithm = assertSupportedAlgorithm(args.algorithm);
|
|
297
324
|
const baseName = args.name === void 0 ? algorithm : assertSafeName(args.name);
|
|
298
325
|
const keys = await writeKeyPair(args.out, baseName, algorithm, args.force);
|
|
326
|
+
const ignored = await ensureKeysIgnored(process.cwd());
|
|
299
327
|
ok(`${algorithm} pair generated:`);
|
|
300
328
|
item(`public: ${keys.publicPath}`);
|
|
301
329
|
item(`secret: ${keys.secretPath} (mode 0600)`);
|
|
330
|
+
if (ignored.length > 0) {
|
|
331
|
+
item(`.gitignore: added ${ignored.join(", ")} so secret keys are never committed`);
|
|
332
|
+
}
|
|
302
333
|
warn("The secret key must not be committed or leave this environment.");
|
|
303
334
|
}
|
|
304
335
|
});
|
|
@@ -307,7 +338,7 @@ var keygen = defineCommand3({
|
|
|
307
338
|
var main = defineCommand4({
|
|
308
339
|
meta: {
|
|
309
340
|
name: "pqc",
|
|
310
|
-
version: "0.
|
|
341
|
+
version: "0.3.0",
|
|
311
342
|
description: "CLI for the post-quantum cryptography SDK (@pqc-sdk/core)"
|
|
312
343
|
},
|
|
313
344
|
subCommands: {
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/commands/audit.ts","../src/ui.ts","../src/commands/init.ts","../src/keyfiles.ts","../src/commands/keygen.ts"],"sourcesContent":["import { defineCommand, runMain } from 'citty';\n\nimport { audit } from './commands/audit.js';\nimport { init } from './commands/init.js';\nimport { keygen } from './commands/keygen.js';\n\n// Injected by tsup (`define` in tsup.config.ts) from package.json at build time.\ndeclare const __PQC_CLI_VERSION__: string;\n\nconst main = defineCommand({\n meta: {\n name: 'pqc',\n version: __PQC_CLI_VERSION__,\n description: 'CLI for the post-quantum cryptography SDK (@pqc-sdk/core)',\n },\n subCommands: {\n init,\n keygen,\n audit,\n },\n});\n\nawait runMain(main);\n","import { readFile, readdir, stat } from 'node:fs/promises';\nimport { existsSync } from 'node:fs';\nimport { join, relative } from 'node:path';\n\nimport { defineCommand } from 'citty';\nimport pc from 'picocolors';\n\nimport { finding, heading, note, ok } from '../ui.js';\n\ninterface Finding {\n location: string;\n what: string;\n migrateTo: string;\n}\n\n/**\n * Upper bound on the size of a single source file the scanner will read. Files\n * larger than this are skipped: they are almost never hand-written source, and\n * reading them would slow the scan with no useful signal.\n */\nconst MAX_FILE_BYTES = 1024 * 1024; // 1 MiB\n\nconst ML_DSA = 'ML-DSA-65 (pqc.sign / pqc.verify)';\nconst ML_KEM = 'ML-KEM-768 + AES-256-GCM (pqc.encrypt / pqc.decrypt)';\n\nconst RISKY_PACKAGES: Record<string, { what: string; migrateTo: string }> = {\n jsonwebtoken: { what: 'JWTs signed with RSA/ECDSA (RS256/ES256)', migrateTo: ML_DSA },\n jose: { what: 'JOSE/JWT with RSA/ECDSA algorithms', migrateTo: ML_DSA },\n elliptic: { what: 'Classic elliptic curves (ECDSA/ECDH)', migrateTo: `${ML_DSA} and ${ML_KEM}` },\n secp256k1: { what: 'ECDSA signatures over secp256k1', migrateTo: ML_DSA },\n 'node-rsa': { what: 'RSA encryption and signatures', migrateTo: `${ML_KEM} and ${ML_DSA}` },\n 'node-forge': { what: 'Classic RSA/X.509', migrateTo: `${ML_KEM} and ${ML_DSA}` },\n tweetnacl: { what: 'Ed25519/X25519 (pre-quantum)', migrateTo: `${ML_DSA} and ${ML_KEM}` },\n};\n\nconst CODE_PATTERNS: ReadonlyArray<{ re: RegExp; what: string; migrateTo: string }> = [\n {\n re: /create(?:Sign|Verify)\\s*\\(/,\n what: 'RSA/ECDSA signing via node:crypto (createSign/createVerify)',\n migrateTo: ML_DSA,\n },\n {\n re: /createECDH\\s*\\(|\\.diffieHellman\\s*\\(/,\n what: 'ECDH/DH key exchange',\n migrateTo: ML_KEM,\n },\n {\n re: /publicEncrypt\\s*\\(|privateDecrypt\\s*\\(/,\n what: 'RSA encryption (publicEncrypt/privateDecrypt)',\n migrateTo: ML_KEM,\n },\n {\n re: /generateKeyPair(?:Sync)?\\s*\\(\\s*['\"](?:rsa|rsa-pss|dsa|ec|ed25519|ed448|x25519|x448)['\"]/,\n what: 'pre-quantum keypair generation',\n migrateTo: 'pqc.keys.generate (ML-KEM-768 for encryption, ML-DSA-65 for signatures)',\n },\n {\n re: /['\"`](?:RS|ES|PS)(?:256|384|512)['\"`]/,\n what: 'JWT with an RSA/ECDSA signing algorithm',\n migrateTo: ML_DSA,\n },\n {\n re: /['\"`](?:RSA-OAEP|ECDH|ECDSA)['\"`]/,\n what: 'WebCrypto with a pre-quantum algorithm',\n migrateTo: `${ML_KEM} or ${ML_DSA}`,\n },\n];\n\nconst SOURCE_EXTENSIONS = new Set(['.js', '.mjs', '.cjs', '.ts', '.mts', '.cts', '.jsx', '.tsx']);\nconst IGNORED_DIRS = new Set(['node_modules', 'dist', 'build', 'coverage', '.git', '.next']);\n\nasync function collectSourceFiles(root: string): Promise<string[]> {\n const files: string[] = [];\n const pending = [root];\n while (pending.length > 0) {\n const dir = pending.pop()!;\n for (const entry of await readdir(dir, { withFileTypes: true })) {\n if (entry.isDirectory()) {\n if (!IGNORED_DIRS.has(entry.name)) pending.push(join(dir, entry.name));\n continue;\n }\n const dot = entry.name.lastIndexOf('.');\n if (dot !== -1 && SOURCE_EXTENSIONS.has(entry.name.slice(dot))) {\n files.push(join(dir, entry.name));\n }\n }\n }\n return files.sort();\n}\n\nasync function auditPackageJson(cwd: string): Promise<Finding[]> {\n const path = join(cwd, 'package.json');\n if (!existsSync(path)) return [];\n const manifest = JSON.parse(await readFile(path, 'utf8')) as Record<\n string,\n Record<string, string> | undefined\n >;\n const declared = {\n ...manifest.dependencies,\n ...manifest.devDependencies,\n ...manifest.peerDependencies,\n };\n return Object.keys(declared)\n .filter((name) => name in RISKY_PACKAGES)\n .map((name) => ({ location: `package.json (${name})`, ...RISKY_PACKAGES[name]! }));\n}\n\ninterface SourceScan {\n findings: Finding[];\n /** Files skipped because they exceed {@link MAX_FILE_BYTES}, relative to cwd. */\n skipped: string[];\n}\n\nasync function auditSources(cwd: string): Promise<SourceScan> {\n const findings: Finding[] = [];\n const skipped: string[] = [];\n for (const file of await collectSourceFiles(cwd)) {\n if ((await stat(file)).size > MAX_FILE_BYTES) {\n skipped.push(relative(cwd, file));\n continue;\n }\n const lines = (await readFile(file, 'utf8')).split('\\n');\n lines.forEach((line, index) => {\n for (const { re, what, migrateTo } of CODE_PATTERNS) {\n if (re.test(line)) {\n findings.push({ location: `${relative(cwd, file)}:${index + 1}`, what, migrateTo });\n }\n }\n });\n }\n return { findings, skipped };\n}\n\nexport const audit = defineCommand({\n meta: {\n name: 'audit',\n description:\n 'Heuristically detect pre-quantum crypto (best-effort regex scan) and suggest the PQC equivalent',\n },\n async run() {\n const cwd = process.cwd();\n const { findings: sourceFindings, skipped } = await auditSources(cwd);\n const findings = [...(await auditPackageJson(cwd)), ...sourceFindings];\n\n note(\n 'Heuristic, best-effort regex scan — expect occasional false positives and false negatives.',\n );\n if (skipped.length > 0) {\n note(\n `Skipped ${skipped.length} file(s) larger than ${MAX_FILE_BYTES / 1024 / 1024} MiB: ${skipped.join(', ')}`,\n );\n }\n\n if (findings.length === 0) {\n ok('No pre-quantum crypto detected.');\n return;\n }\n\n heading(`Pre-quantum crypto detected (${findings.length} findings):`);\n for (const f of findings) {\n finding(f.location, f.what, f.migrateTo);\n }\n console.log();\n console.log(\n pc.yellow(\n `${findings.length} usages to migrate. Algorithm guide: FIPS 203 (ML-KEM) encryption, FIPS 204 (ML-DSA) signatures.`,\n ),\n );\n process.exitCode = 1;\n },\n});\n","import pc from 'picocolors';\n\n/**\n * Output helpers. picocolors disables colors automatically when there is no\n * TTY (and NO_COLOR/FORCE_COLOR are honored), so output stays readable in\n * pipes and CI logs without ANSI codes.\n */\nexport const ok = (message: string): void => {\n console.log(`${pc.green('✓')} ${message}`);\n};\n\nexport const warn = (message: string): void => {\n console.log(`${pc.yellow('⚠')} ${pc.yellow(message)}`);\n};\n\nexport const item = (message: string): void => {\n console.log(` ${message}`);\n};\n\nexport const finding = (location: string, what: string, migrateTo: string): void => {\n console.log(` ${pc.red('●')} ${pc.bold(location)} — ${what}`);\n console.log(` ${pc.dim('migrate to:')} ${pc.cyan(migrateTo)}`);\n};\n\nexport const heading = (message: string): void => {\n console.log(pc.bold(message));\n};\n\nexport const note = (message: string): void => {\n console.log(pc.dim(message));\n};\n","import { writeFile } from 'node:fs/promises';\nimport { existsSync } from 'node:fs';\n\nimport { defineCommand } from 'citty';\n\nimport { writeKeyPair } from '../keyfiles.js';\nimport { heading, item, ok, warn } from '../ui.js';\n\nconst CONFIG_FILE = 'pqc.config.json';\n\nconst CONFIG = {\n defaultAlgorithm: 'ml-kem-768',\n keysDir: 'keys',\n} as const;\n\nconst EXAMPLE = `import { pqc } from '@pqc-sdk/core';\n\n// Full roundtrip: generate keys, encrypt and decrypt.\nconst pair = await pqc.keys.generate(); // ML-KEM-768 by default\nconst ciphertext = await pqc.encrypt('hello post-quantum', pair.publicKey);\nconst plaintext = await pqc.decrypt(ciphertext, pair.secretKey);\nconsole.log(new TextDecoder().decode(plaintext)); // \"hello post-quantum\"\n\n// Digital signatures (ML-DSA-65):\nconst signer = await pqc.keys.generate({ algorithm: 'ml-dsa-65' });\nconst signature = await pqc.sign('document', signer.secretKey);\nconsole.log(await pqc.verify('document', signature, signer.publicKey)); // true\n\n// Keys serialize to base64url with metadata, ready to persist:\nconst token = pqc.keys.serialize(pair.publicKey);\nconsole.log(token.slice(0, 48) + '…');\n\n// To load the development keys generated by \\`pqc init\\`:\n// import { readFile } from 'node:fs/promises';\n// const publicKey = pqc.keys.deserialize(\n// (await readFile('keys/dev.public.pqc', 'utf8')).trim(),\n// );\n`;\n\nexport const init = defineCommand({\n meta: {\n name: 'init',\n description: 'Initialize a project: config, development keys and example',\n },\n async run() {\n if (existsSync(CONFIG_FILE)) {\n throw new Error(`${CONFIG_FILE} already exists: the project is already initialized.`);\n }\n\n await writeFile(CONFIG_FILE, `${JSON.stringify(CONFIG, null, 2)}\\n`);\n const keys = await writeKeyPair(CONFIG.keysDir, 'dev', CONFIG.defaultAlgorithm, false);\n await writeFile('example.ts', EXAMPLE);\n\n heading('PQC project initialized:');\n item(`${CONFIG_FILE} — configuration with safe defaults`);\n item(`${keys.publicPath} / ${keys.secretPath} — development ${keys.algorithm} pair`);\n item('example.ts — full roundtrip ready to run');\n console.log();\n warn('The keys/dev.* keys are for development ONLY — do NOT use them in production.');\n ok(`Next step: run example.ts (node --experimental-strip-types example.ts or tsx)`);\n },\n});\n","import { chmod, mkdir, writeFile } from 'node:fs/promises';\nimport { existsSync } from 'node:fs';\nimport { join } from 'node:path';\n\nimport { SUPPORTED_ALGORITHMS, pqc, type SupportedAlgorithm } from '@pqc-sdk/core';\n\nexport interface WrittenKeyPair {\n algorithm: SupportedAlgorithm;\n publicPath: string;\n secretPath: string;\n}\n\nexport function assertSupportedAlgorithm(value: string): SupportedAlgorithm {\n if (!(SUPPORTED_ALGORITHMS as readonly string[]).includes(value)) {\n throw new Error(\n `Unsupported algorithm: ${value} (supported: ${SUPPORTED_ALGORITHMS.join(', ')})`,\n );\n }\n return value as SupportedAlgorithm;\n}\n\n/**\n * Validates a base file name for a key pair. The name becomes part of the path\n * passed to {@link writeKeyPair}, so it must not be empty, contain path\n * separators (`/` or `\\`), or contain `..` — otherwise a caller could write\n * keys outside the intended output directory.\n */\nexport function assertSafeName(value: string): string {\n if (value === '') {\n throw new Error('Invalid --name: must not be empty.');\n }\n if (value.includes('/') || value.includes('\\\\')) {\n throw new Error('Invalid --name: must not contain path separators (\"/\" or \"\\\\\").');\n }\n if (value.includes('..')) {\n throw new Error('Invalid --name: must not contain \"..\".');\n }\n return value;\n}\n\n/** Generates a pair and writes it serialized as base64url, one file per key. */\nexport async function writeKeyPair(\n directory: string,\n baseName: string,\n algorithm: SupportedAlgorithm,\n force: boolean,\n): Promise<WrittenKeyPair> {\n const publicPath = join(directory, `${baseName}.public.pqc`);\n const secretPath = join(directory, `${baseName}.secret.pqc`);\n\n if (!force) {\n for (const path of [publicPath, secretPath]) {\n if (existsSync(path)) {\n throw new Error(`${path} already exists. Use --force to overwrite it.`);\n }\n }\n }\n\n const pair = await pqc.keys.generate({ algorithm });\n await mkdir(directory, { recursive: true });\n await writeFile(publicPath, `${pqc.keys.serialize(pair.publicKey)}\\n`);\n await writeFile(secretPath, `${pqc.keys.serialize(pair.secretKey)}\\n`, { mode: 0o600 });\n await chmod(secretPath, 0o600);\n\n return { algorithm, publicPath, secretPath };\n}\n","import { defineCommand } from 'citty';\n\nimport { assertSafeName, assertSupportedAlgorithm, writeKeyPair } from '../keyfiles.js';\nimport { item, ok, warn } from '../ui.js';\n\nexport const keygen = defineCommand({\n meta: {\n name: 'keygen',\n description: 'Generate a PQC key pair serialized as base64url',\n },\n args: {\n algorithm: {\n type: 'string',\n description: 'Algorithm of the pair (ml-kem-768 or ml-dsa-65)',\n default: 'ml-kem-768',\n },\n name: {\n type: 'string',\n description: 'Base file name for the key pair (default: the algorithm, e.g. ml-kem-768)',\n },\n out: {\n type: 'string',\n description: 'Output directory',\n default: 'keys',\n },\n force: {\n type: 'boolean',\n description: 'Overwrite existing keys',\n default: false,\n },\n },\n async run({ args }) {\n const algorithm = assertSupportedAlgorithm(args.algorithm);\n const baseName = args.name === undefined ? algorithm : assertSafeName(args.name);\n const keys = await writeKeyPair(args.out, baseName, algorithm, args.force);\n\n ok(`${algorithm} pair generated:`);\n item(`public: ${keys.publicPath}`);\n item(`secret: ${keys.secretPath} (mode 0600)`);\n warn('The secret key must not be committed or leave this environment.');\n },\n});\n"],"mappings":"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","names":["defineCommand","pc","pc","writeFile","existsSync","defineCommand","existsSync","join","defineCommand","existsSync","writeFile","defineCommand","defineCommand","defineCommand"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/commands/audit.ts","../src/ui.ts","../src/commands/init.ts","../src/keyfiles.ts","../src/commands/keygen.ts"],"sourcesContent":["import { defineCommand, runMain } from 'citty';\n\nimport { audit } from './commands/audit.js';\nimport { init } from './commands/init.js';\nimport { keygen } from './commands/keygen.js';\n\n// Injected by tsup (`define` in tsup.config.ts) from package.json at build time.\ndeclare const __PQC_CLI_VERSION__: string;\n\nconst main = defineCommand({\n meta: {\n name: 'pqc',\n version: __PQC_CLI_VERSION__,\n description: 'CLI for the post-quantum cryptography SDK (@pqc-sdk/core)',\n },\n subCommands: {\n init,\n keygen,\n audit,\n },\n});\n\nawait runMain(main);\n","import { readFile, readdir, stat } from 'node:fs/promises';\nimport { existsSync } from 'node:fs';\nimport { join, relative } from 'node:path';\n\nimport { defineCommand } from 'citty';\nimport pc from 'picocolors';\n\nimport { finding, heading, note, ok } from '../ui.js';\n\ninterface Finding {\n location: string;\n what: string;\n migrateTo: string;\n}\n\n/**\n * Upper bound on the size of a single source file the scanner will read. Files\n * larger than this are skipped: they are almost never hand-written source, and\n * reading them would slow the scan with no useful signal.\n */\nconst MAX_FILE_BYTES = 1024 * 1024; // 1 MiB\n\nconst ML_DSA = 'ML-DSA-65 (pqc.sign / pqc.verify)';\nconst ML_KEM = 'ML-KEM-768 + AES-256-GCM (pqc.encrypt / pqc.decrypt)';\n\nconst RISKY_PACKAGES: Record<string, { what: string; migrateTo: string }> = {\n jsonwebtoken: { what: 'JWTs signed with RSA/ECDSA (RS256/ES256)', migrateTo: ML_DSA },\n jose: { what: 'JOSE/JWT with RSA/ECDSA algorithms', migrateTo: ML_DSA },\n elliptic: { what: 'Classic elliptic curves (ECDSA/ECDH)', migrateTo: `${ML_DSA} and ${ML_KEM}` },\n secp256k1: { what: 'ECDSA signatures over secp256k1', migrateTo: ML_DSA },\n 'node-rsa': { what: 'RSA encryption and signatures', migrateTo: `${ML_KEM} and ${ML_DSA}` },\n 'node-forge': { what: 'Classic RSA/X.509', migrateTo: `${ML_KEM} and ${ML_DSA}` },\n tweetnacl: { what: 'Ed25519/X25519 (pre-quantum)', migrateTo: `${ML_DSA} and ${ML_KEM}` },\n};\n\nconst CODE_PATTERNS: ReadonlyArray<{ re: RegExp; what: string; migrateTo: string }> = [\n {\n re: /create(?:Sign|Verify)\\s*\\(/,\n what: 'RSA/ECDSA signing via node:crypto (createSign/createVerify)',\n migrateTo: ML_DSA,\n },\n {\n re: /createECDH\\s*\\(|\\.diffieHellman\\s*\\(/,\n what: 'ECDH/DH key exchange',\n migrateTo: ML_KEM,\n },\n {\n re: /publicEncrypt\\s*\\(|privateDecrypt\\s*\\(/,\n what: 'RSA encryption (publicEncrypt/privateDecrypt)',\n migrateTo: ML_KEM,\n },\n {\n re: /generateKeyPair(?:Sync)?\\s*\\(\\s*['\"](?:rsa|rsa-pss|dsa|ec|ed25519|ed448|x25519|x448)['\"]/,\n what: 'pre-quantum keypair generation',\n migrateTo: 'pqc.keys.generate (ML-KEM-768 for encryption, ML-DSA-65 for signatures)',\n },\n {\n re: /['\"`](?:RS|ES|PS)(?:256|384|512)['\"`]/,\n what: 'JWT with an RSA/ECDSA signing algorithm',\n migrateTo: ML_DSA,\n },\n {\n re: /['\"`](?:RSA-OAEP|ECDH|ECDSA)['\"`]/,\n what: 'WebCrypto with a pre-quantum algorithm',\n migrateTo: `${ML_KEM} or ${ML_DSA}`,\n },\n];\n\nconst SOURCE_EXTENSIONS = new Set(['.js', '.mjs', '.cjs', '.ts', '.mts', '.cts', '.jsx', '.tsx']);\nconst IGNORED_DIRS = new Set(['node_modules', 'dist', 'build', 'coverage', '.git', '.next']);\n\nasync function collectSourceFiles(root: string): Promise<string[]> {\n const files: string[] = [];\n const pending = [root];\n while (pending.length > 0) {\n const dir = pending.pop()!;\n for (const entry of await readdir(dir, { withFileTypes: true })) {\n if (entry.isDirectory()) {\n if (!IGNORED_DIRS.has(entry.name)) pending.push(join(dir, entry.name));\n continue;\n }\n const dot = entry.name.lastIndexOf('.');\n if (dot !== -1 && SOURCE_EXTENSIONS.has(entry.name.slice(dot))) {\n files.push(join(dir, entry.name));\n }\n }\n }\n return files.sort();\n}\n\nasync function auditPackageJson(cwd: string): Promise<Finding[]> {\n const path = join(cwd, 'package.json');\n if (!existsSync(path)) return [];\n const manifest = JSON.parse(await readFile(path, 'utf8')) as Record<\n string,\n Record<string, string> | undefined\n >;\n const declared = {\n ...manifest.dependencies,\n ...manifest.devDependencies,\n ...manifest.peerDependencies,\n };\n return Object.keys(declared)\n .filter((name) => name in RISKY_PACKAGES)\n .map((name) => ({ location: `package.json (${name})`, ...RISKY_PACKAGES[name]! }));\n}\n\ninterface SourceScan {\n findings: Finding[];\n /** Files skipped because they exceed {@link MAX_FILE_BYTES}, relative to cwd. */\n skipped: string[];\n}\n\nasync function auditSources(cwd: string): Promise<SourceScan> {\n const findings: Finding[] = [];\n const skipped: string[] = [];\n for (const file of await collectSourceFiles(cwd)) {\n if ((await stat(file)).size > MAX_FILE_BYTES) {\n skipped.push(relative(cwd, file));\n continue;\n }\n const lines = (await readFile(file, 'utf8')).split('\\n');\n lines.forEach((line, index) => {\n for (const { re, what, migrateTo } of CODE_PATTERNS) {\n if (re.test(line)) {\n findings.push({ location: `${relative(cwd, file)}:${index + 1}`, what, migrateTo });\n }\n }\n });\n }\n return { findings, skipped };\n}\n\nexport const audit = defineCommand({\n meta: {\n name: 'audit',\n description:\n 'Heuristically detect pre-quantum crypto (best-effort regex scan) and suggest the PQC equivalent',\n },\n async run() {\n const cwd = process.cwd();\n const { findings: sourceFindings, skipped } = await auditSources(cwd);\n const findings = [...(await auditPackageJson(cwd)), ...sourceFindings];\n\n note(\n 'Heuristic, best-effort regex scan — expect occasional false positives and false negatives.',\n );\n if (skipped.length > 0) {\n note(\n `Skipped ${skipped.length} file(s) larger than ${MAX_FILE_BYTES / 1024 / 1024} MiB: ${skipped.join(', ')}`,\n );\n }\n\n if (findings.length === 0) {\n ok('No pre-quantum crypto detected.');\n return;\n }\n\n heading(`Pre-quantum crypto detected (${findings.length} findings):`);\n for (const f of findings) {\n finding(f.location, f.what, f.migrateTo);\n }\n console.log();\n console.log(\n pc.yellow(\n `${findings.length} usages to migrate. Algorithm guide: FIPS 203 (ML-KEM) encryption, FIPS 204 (ML-DSA) signatures.`,\n ),\n );\n process.exitCode = 1;\n },\n});\n","import pc from 'picocolors';\n\n/**\n * Output helpers. picocolors disables colors automatically when there is no\n * TTY (and NO_COLOR/FORCE_COLOR are honored), so output stays readable in\n * pipes and CI logs without ANSI codes.\n */\nexport const ok = (message: string): void => {\n console.log(`${pc.green('✓')} ${message}`);\n};\n\nexport const warn = (message: string): void => {\n console.log(`${pc.yellow('⚠')} ${pc.yellow(message)}`);\n};\n\nexport const item = (message: string): void => {\n console.log(` ${message}`);\n};\n\nexport const finding = (location: string, what: string, migrateTo: string): void => {\n console.log(` ${pc.red('●')} ${pc.bold(location)} — ${what}`);\n console.log(` ${pc.dim('migrate to:')} ${pc.cyan(migrateTo)}`);\n};\n\nexport const heading = (message: string): void => {\n console.log(pc.bold(message));\n};\n\nexport const note = (message: string): void => {\n console.log(pc.dim(message));\n};\n","import { writeFile } from 'node:fs/promises';\nimport { existsSync } from 'node:fs';\n\nimport { defineCommand } from 'citty';\n\nimport { ensureKeysIgnored, writeKeyPair } from '../keyfiles.js';\nimport { heading, item, ok, warn } from '../ui.js';\n\nconst CONFIG_FILE = 'pqc.config.json';\n\nconst CONFIG = {\n defaultAlgorithm: 'ml-kem-768',\n keysDir: 'keys',\n} as const;\n\nconst EXAMPLE = `import { pqc } from '@pqc-sdk/core';\n\n// Full roundtrip: generate keys, encrypt and decrypt.\nconst pair = await pqc.keys.generate(); // ML-KEM-768 by default\nconst ciphertext = await pqc.encrypt('hello post-quantum', pair.publicKey);\nconst plaintext = await pqc.decrypt(ciphertext, pair.secretKey);\nconsole.log(new TextDecoder().decode(plaintext)); // \"hello post-quantum\"\n\n// Digital signatures (ML-DSA-65):\nconst signer = await pqc.keys.generate({ algorithm: 'ml-dsa-65' });\nconst signature = await pqc.sign('document', signer.secretKey);\nconsole.log(await pqc.verify('document', signature, signer.publicKey)); // true\n\n// Keys serialize to base64url with metadata, ready to persist:\nconst token = pqc.keys.serialize(pair.publicKey);\nconsole.log(token.slice(0, 48) + '…');\n\n// To load the development keys generated by \\`pqc init\\`:\n// import { readFile } from 'node:fs/promises';\n// const publicKey = pqc.keys.deserialize(\n// (await readFile('keys/dev.public.pqc', 'utf8')).trim(),\n// );\n`;\n\nexport const init = defineCommand({\n meta: {\n name: 'init',\n description: 'Initialize a project: config, development keys and example',\n },\n async run() {\n if (existsSync(CONFIG_FILE)) {\n throw new Error(`${CONFIG_FILE} already exists: the project is already initialized.`);\n }\n\n await writeFile(CONFIG_FILE, `${JSON.stringify(CONFIG, null, 2)}\\n`);\n const keys = await writeKeyPair(CONFIG.keysDir, 'dev', CONFIG.defaultAlgorithm, false);\n const ignored = await ensureKeysIgnored(process.cwd());\n const exampleWritten = !existsSync('example.ts');\n if (exampleWritten) {\n await writeFile('example.ts', EXAMPLE);\n }\n\n heading('PQC project initialized:');\n item(`${CONFIG_FILE} — configuration with safe defaults`);\n item(`${keys.publicPath} / ${keys.secretPath} — development ${keys.algorithm} pair`);\n if (ignored.length > 0) {\n item(`.gitignore — added ${ignored.join(', ')} so secret keys are never committed`);\n }\n if (exampleWritten) {\n item('example.ts — full roundtrip ready to run');\n } else {\n warn('example.ts already exists — left untouched.');\n }\n console.log();\n warn('The keys/dev.* keys are for development ONLY — do NOT use them in production.');\n ok(`Next step: run example.ts (node --experimental-strip-types example.ts or tsx)`);\n },\n});\n","import { chmod, mkdir, readFile, writeFile } from 'node:fs/promises';\nimport { existsSync } from 'node:fs';\nimport { join } from 'node:path';\n\nimport { SUPPORTED_ALGORITHMS, pqc, type SupportedAlgorithm } from '@pqc-sdk/core';\n\n/** Patterns that keep generated secret keys out of version control. */\nexport const KEY_IGNORE_PATTERNS = ['keys/', '*.secret.pqc'] as const;\n\n/**\n * Ensures the project `.gitignore` ignores generated key material so a secret\n * key is never committed by an accidental `git add .`. Creates `.gitignore`\n * when it is missing, or appends only the patterns not already present.\n * Idempotent. Returns the patterns it added (empty when nothing changed).\n */\nexport async function ensureKeysIgnored(cwd: string): Promise<string[]> {\n const gitignorePath = join(cwd, '.gitignore');\n const current = existsSync(gitignorePath) ? await readFile(gitignorePath, 'utf8') : '';\n const present = new Set(current.split(/\\r?\\n/).map((line) => line.trim()));\n const missing = KEY_IGNORE_PATTERNS.filter((pattern) => !present.has(pattern));\n if (missing.length === 0) {\n return [];\n }\n const prefix = current === '' ? '' : current.endsWith('\\n') ? '\\n' : '\\n\\n';\n const block = `${prefix}# PQC key material — never commit secret keys\\n${missing.join('\\n')}\\n`;\n await writeFile(gitignorePath, current + block);\n return [...missing];\n}\n\nexport interface WrittenKeyPair {\n algorithm: SupportedAlgorithm;\n publicPath: string;\n secretPath: string;\n}\n\nexport function assertSupportedAlgorithm(value: string): SupportedAlgorithm {\n if (!(SUPPORTED_ALGORITHMS as readonly string[]).includes(value)) {\n throw new Error(\n `Unsupported algorithm: ${value} (supported: ${SUPPORTED_ALGORITHMS.join(', ')})`,\n );\n }\n return value as SupportedAlgorithm;\n}\n\n/**\n * Validates a base file name for a key pair. The name becomes part of the path\n * passed to {@link writeKeyPair}, so it must not be empty, contain path\n * separators (`/` or `\\`), or contain `..` — otherwise a caller could write\n * keys outside the intended output directory.\n */\nexport function assertSafeName(value: string): string {\n if (value === '') {\n throw new Error('Invalid --name: must not be empty.');\n }\n if (value.includes('/') || value.includes('\\\\')) {\n throw new Error('Invalid --name: must not contain path separators (\"/\" or \"\\\\\").');\n }\n if (value.includes('..')) {\n throw new Error('Invalid --name: must not contain \"..\".');\n }\n return value;\n}\n\n/** Generates a pair and writes it serialized as base64url, one file per key. */\nexport async function writeKeyPair(\n directory: string,\n baseName: string,\n algorithm: SupportedAlgorithm,\n force: boolean,\n): Promise<WrittenKeyPair> {\n const publicPath = join(directory, `${baseName}.public.pqc`);\n const secretPath = join(directory, `${baseName}.secret.pqc`);\n\n if (!force) {\n for (const path of [publicPath, secretPath]) {\n if (existsSync(path)) {\n throw new Error(`${path} already exists. Use --force to overwrite it.`);\n }\n }\n }\n\n const pair = await pqc.keys.generate({ algorithm });\n await mkdir(directory, { recursive: true });\n await writeFile(publicPath, `${pqc.keys.serialize(pair.publicKey)}\\n`);\n await writeFile(secretPath, `${pqc.keys.serialize(pair.secretKey)}\\n`, { mode: 0o600 });\n await chmod(secretPath, 0o600);\n\n return { algorithm, publicPath, secretPath };\n}\n","import { defineCommand } from 'citty';\n\nimport {\n assertSafeName,\n assertSupportedAlgorithm,\n ensureKeysIgnored,\n writeKeyPair,\n} from '../keyfiles.js';\nimport { item, ok, warn } from '../ui.js';\n\nexport const keygen = defineCommand({\n meta: {\n name: 'keygen',\n description: 'Generate a PQC key pair serialized as base64url',\n },\n args: {\n algorithm: {\n type: 'string',\n description: 'Algorithm of the pair (ml-kem-768 or ml-dsa-65)',\n default: 'ml-kem-768',\n },\n name: {\n type: 'string',\n description: 'Base file name for the key pair (default: the algorithm, e.g. ml-kem-768)',\n },\n out: {\n type: 'string',\n description: 'Output directory',\n default: 'keys',\n },\n force: {\n type: 'boolean',\n description: 'Overwrite existing keys',\n default: false,\n },\n },\n async run({ args }) {\n const algorithm = assertSupportedAlgorithm(args.algorithm);\n const baseName = args.name === undefined ? algorithm : assertSafeName(args.name);\n const keys = await writeKeyPair(args.out, baseName, algorithm, args.force);\n const ignored = await ensureKeysIgnored(process.cwd());\n\n ok(`${algorithm} pair generated:`);\n item(`public: ${keys.publicPath}`);\n item(`secret: ${keys.secretPath} (mode 0600)`);\n if (ignored.length > 0) {\n item(`.gitignore: added ${ignored.join(', ')} so secret keys are never committed`);\n }\n warn('The secret key must not be committed or leave this environment.');\n },\n});\n"],"mappings":"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","names":["defineCommand","pc","pc","writeFile","existsSync","defineCommand","readFile","existsSync","join","defineCommand","existsSync","writeFile","defineCommand","defineCommand","defineCommand"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pqc-sdk/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "CLI for the post-quantum cryptography SDK",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
"dependencies": {
|
|
16
16
|
"citty": "^0.2.2",
|
|
17
17
|
"picocolors": "^1.1.1",
|
|
18
|
-
"@pqc-sdk/core": "0.
|
|
18
|
+
"@pqc-sdk/core": "0.3.0"
|
|
19
19
|
},
|
|
20
20
|
"devDependencies": {
|
|
21
21
|
"@types/node": "^22.15.29",
|