crypto: support ML-KEM, DHKEM, and RSASVE key encapsulation mechanisms · nodejs/node@1474153
1+'use strict';
2+3+const common = require('../common.js');
4+const { hasOpenSSL } = require('../../test/common/crypto.js');
5+const crypto = require('crypto');
6+const fs = require('fs');
7+const path = require('path');
8+const fixtures_keydir = path.resolve(__dirname, '../../test/fixtures/keys/');
9+10+function readKey(name) {
11+return fs.readFileSync(`${fixtures_keydir}/${name}.pem`, 'utf8');
12+}
13+14+const keyFixtures = {};
15+16+if (hasOpenSSL(3, 5)) {
17+keyFixtures['ml-kem-512'] = readKey('ml_kem_512_private');
18+keyFixtures['ml-kem-768'] = readKey('ml_kem_768_private');
19+keyFixtures['ml-kem-1024'] = readKey('ml_kem_1024_private');
20+}
21+if (hasOpenSSL(3, 2)) {
22+keyFixtures['p-256'] = readKey('ec_p256_private');
23+keyFixtures['p-384'] = readKey('ec_p384_private');
24+keyFixtures['p-521'] = readKey('ec_p521_private');
25+keyFixtures.x25519 = readKey('x25519_private');
26+keyFixtures.x448 = readKey('x448_private');
27+}
28+if (hasOpenSSL(3, 0)) {
29+keyFixtures.rsa = readKey('rsa_private_2048');
30+}
31+32+if (Object.keys(keyFixtures).length === 0) {
33+console.log('no supported key types available for this OpenSSL version');
34+process.exit(0);
35+}
36+37+const bench = common.createBenchmark(main, {
38+keyType: Object.keys(keyFixtures),
39+mode: ['sync', 'async', 'async-parallel'],
40+keyFormat: ['keyObject', 'keyObject.unique'],
41+op: ['encapsulate', 'decapsulate'],
42+n: [1e3],
43+}, {
44+combinationFilter(p) {
45+// "keyObject.unique" allows to compare the result with "keyObject" to
46+// assess whether mutexes over the key material impact the operation
47+return p.keyFormat !== 'keyObject.unique' ||
48+(p.keyFormat === 'keyObject.unique' && p.mode === 'async-parallel');
49+},
50+});
51+52+function measureSync(n, op, privateKey, keys, ciphertexts) {
53+bench.start();
54+for (let i = 0; i < n; ++i) {
55+const key = privateKey || keys[i];
56+if (op === 'encapsulate') {
57+crypto.encapsulate(key);
58+} else {
59+crypto.decapsulate(key, ciphertexts[i]);
60+}
61+}
62+bench.end(n);
63+}
64+65+function measureAsync(n, op, privateKey, keys, ciphertexts) {
66+let remaining = n;
67+function done() {
68+if (--remaining === 0)
69+bench.end(n);
70+else
71+one();
72+}
73+74+function one() {
75+const key = privateKey || keys[n - remaining];
76+if (op === 'encapsulate') {
77+crypto.encapsulate(key, done);
78+} else {
79+crypto.decapsulate(key, ciphertexts[n - remaining], done);
80+}
81+}
82+bench.start();
83+one();
84+}
85+86+function measureAsyncParallel(n, op, privateKey, keys, ciphertexts) {
87+let remaining = n;
88+function done() {
89+if (--remaining === 0)
90+bench.end(n);
91+}
92+bench.start();
93+for (let i = 0; i < n; ++i) {
94+const key = privateKey || keys[i];
95+if (op === 'encapsulate') {
96+crypto.encapsulate(key, done);
97+} else {
98+crypto.decapsulate(key, ciphertexts[i], done);
99+}
100+}
101+}
102+103+function main({ n, mode, keyFormat, keyType, op }) {
104+const pems = [...Buffer.alloc(n)].map(() => keyFixtures[keyType]);
105+const keyObjects = pems.map(crypto.createPrivateKey);
106+107+let privateKey, keys, ciphertexts;
108+109+switch (keyFormat) {
110+case 'keyObject':
111+privateKey = keyObjects[0];
112+break;
113+case 'keyObject.unique':
114+keys = keyObjects;
115+break;
116+default:
117+throw new Error('not implemented');
118+}
119+120+// Pre-generate ciphertexts for decapsulate operations
121+if (op === 'decapsulate') {
122+if (privateKey) {
123+ciphertexts = [...Buffer.alloc(n)].map(() => crypto.encapsulate(privateKey).ciphertext);
124+} else {
125+ciphertexts = keys.map((key) => crypto.encapsulate(key).ciphertext);
126+}
127+}
128+129+switch (mode) {
130+case 'sync':
131+measureSync(n, op, privateKey, keys, ciphertexts);
132+break;
133+case 'async':
134+measureAsync(n, op, privateKey, keys, ciphertexts);
135+break;
136+case 'async-parallel':
137+measureAsyncParallel(n, op, privateKey, keys, ciphertexts);
138+break;
139+}
140+}