// Writes packages/runtime/src/scr_cipher_info.h from the running Node's // crypto.getCipherInfo: one record per cipher, the names (lowercase) and nids // that find it, and the keyLength and ivLength options each accepts. The // candidate names are getCiphers() and `openssl list -cipher-algorithms` // (OpenSSL's aliases and its legacy-provider ciphers, which getCiphers() // leaves out). Run with the pinned Node: node scripts/gen-cipher-info.mjs import crypto from "node:crypto"; import { execFileSync } from "node:child_process"; import fs from "node:fs"; const listed = execFileSync("openssl", ["list", "-cipher-algorithms"], { encoding: "utf8" }) .match(/[A-Za-z0-9][A-Za-z0-9._-]*/g) .filter((word) => !["Legacy", "Provided", "default", "legacy", "base", "fips"].includes(word)); const candidates = new Set([...crypto.getCiphers(), ...listed]); for (let nid = 1; nid < 1400; nid++) { const info = crypto.getCipherInfo(nid); if (info) candidates.add(info.name); } function ivRule(name, info) { const accepted = []; for (let v = 0; v <= 70; v++) if (crypto.getCipherInfo(name, { ivLength: v })) accepted.push(v); const large = !!crypto.getCipherInfo(name, { ivLength: 100000 }); const own = info.ivLength ?? 0; if (accepted.length === 0) return "SC_INFO_NONE"; if (accepted.length === 1 && accepted[0] === own && !large) return "SC_INFO_EXACT"; if (accepted.join() === "7,8,9,10,11,12,13" && !large) return "SC_INFO_CCM"; if (accepted.join() === [...Array(15)].map((_, i) => i + 1).join() && !large) return "SC_INFO_OCB"; if (accepted.length === 71 && large) return "SC_INFO_ANY"; throw new Error(`unclassified ivLength rule for ${name}: ${accepted}`); } function keyRule(name, info) { const accepted = []; for (let k = 0; k <= 80; k++) if (crypto.getCipherInfo(name, { keyLength: k })) accepted.push(k); if (accepted.length === 0) return "SC_INFO_NONE"; if (accepted.length === 1 && accepted[0] === info.keyLength) return "SC_INFO_EXACT"; throw new Error(`unclassified keyLength rule for ${name}: ${accepted}`); } const records = []; const recordIndex = new Map(); function recordOf(name, info) { const key = JSON.stringify(info); let index = recordIndex.get(key); const rules = [keyRule(name, info), ivRule(name, info)]; if (index === undefined) { index = records.length; records.push({ info, rules }); recordIndex.set(key, index); } else if (records[index].rules.join() !== rules.join()) { throw new Error(`rules differ for ${name}`); } return index; } const names = []; for (const name of [...candidates].map((n) => n.toLowerCase()).sort()) { if (names.length && names.at(-1)[0] === name) continue; const info = crypto.getCipherInfo(name); if (info) names.push([name, recordOf(name, info)]); } const nids = []; for (let nid = 1; nid < 1400; nid++) { const info = crypto.getCipherInfo(nid); if (info) nids.push([nid, recordOf(info.name, info)]); } const str = (s) => (s === undefined ? "NULL" : JSON.stringify(s)); const num = (n) => (n === undefined ? -1 : n); let out = `/* Generated by scripts/gen-cipher-info.mjs from Node ${process.version}'s * crypto.getCipherInfo: every cipher OpenSSL names, with the fields Node * reports (-1 or NULL for a field it leaves out) and the keyLength and * ivLength options each accepts. Do not edit. */ #ifndef SCR_CIPHER_INFO_H #define SCR_CIPHER_INFO_H enum { SC_INFO_NONE, SC_INFO_EXACT, SC_INFO_CCM, SC_INFO_OCB, SC_INFO_ANY }; typedef struct { const char *mode; const char *name; int nid; int block_size; int iv_length; int key_length; unsigned char key_rule; unsigned char iv_rule; } ScCipherInfo; static const ScCipherInfo sc_cipher_infos[] = { `; for (const { info, rules } of records) out += ` {${str(info.mode)}, ${str(info.name)}, ${num(info.nid)}, ${num(info.blockSize)}, ${num(info.ivLength)}, ${info.keyLength}, ${rules[0]}, ${rules[1]}},\n`; out += `}; /* Lowercase names, sorted, with their record. */ static const struct { const char *name; unsigned short info; } sc_cipher_info_names[] = { `; for (const [name, index] of names) out += ` {${JSON.stringify(name)}, ${index}},\n`; out += `}; /* Nids, ascending, with their record. */ static const struct { unsigned short nid; unsigned short info; } sc_cipher_info_nids[] = { `; for (const [nid, index] of nids) out += ` {${nid}, ${index}},\n`; out += `}; #endif `; for (const { info } of records) { const keys = Object.keys(info).join(); const expected = ["mode", "name", "nid", "blockSize", "ivLength", "keyLength"] .filter((k) => k in info) .join(); if (keys !== expected) throw new Error(`unexpected key order ${keys}`); } fs.writeFileSync(new URL("../packages/runtime/src/scr_cipher_info.h", import.meta.url), out); console.log(records.length, "records,", names.length, "names,", nids.length, "nids");