mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-10-27 06:43:19 +08:00
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.
126 lines
4.7 KiB
JavaScript
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");
|