Files
scriptc/scripts/gen-cipher-info.mjs
T
Chris Tate 846552b0e2 Support crypto.getCipherInfo, AES-CCM and the AES key wrap ciphers
getCipherInfo(nameOrNid[, options]) runs in a new runtime unit,
scr_cipher_info.c, over a table that scripts/gen-cipher-info.mjs generates
from Node 24.22's getCipherInfo. The table covers every cipher OpenSSL
names, including the ones without a native implementation. Lookups match
names case-insensitively and accept OpenSSL's nids. The unit applies
cipher.js's argument checks and GetCipherInfo's keyLength and ivLength
rules, and returns the info object in Node's key order. CipherInfo maps
to a checked-dynamic value, so TypeScript and JavaScript code receive
Node's object. Reading the nid Node leaves out for the SIV and SM4 GCM,
CCM and XTS ciphers, where the declared type gives number, throws a
TypeError.

createCipheriv and createDecipheriv support aes-128/192/256-ccm (and the
id-aesN-ccm names) over OpenSSL's ccm128 steps. They follow Node's tag and
IV lengths, the plaintextLength requirement, the message-length caps and
the one-update rule, and report a failed authentication from final().

They also support the RFC 3394 and RFC 5649 key wrap ciphers (aes-N-wrap,
aesN-wrap, id-aesN-wrap, the -pad forms and the -inv ciphers) over
wrap128.c's routines, with Node's lengths, integrity failures and
one-update rule.

The new names follow the existing ones in the cipher table, so the lookup
cost of the existing ciphers is unchanged.
2026-10-22 17:18:54 -05:00

126 lines
4.7 KiB
JavaScript

// 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");