Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
8d518c4
src: decouple KeyObject and CryptoKey and move CryptoKey to src
panva Apr 24, 2026
a11f2ea
src,crypto: add NativeCryptoKey
panva Apr 24, 2026
1007575
lib,crypto: rewire CryptoKey on the native class
panva Apr 24, 2026
24d37bd
lib,crypto: migrate algorithm modules to native CryptoKey
panva Apr 24, 2026
367d184
src,crypto: relax RSA/EC keygen arg checks
panva Apr 24, 2026
8bff7ff
lib,crypto: validate HkdfParams info length early
panva Apr 24, 2026
f306137
lib,crypto: add early structural JWK validation
panva Apr 24, 2026
fdef77d
test: add CryptoKey class regression tests
panva Apr 24, 2026
d3272c5
benchmark: add Web Crypto sign/verify benchmarks
panva Apr 24, 2026
a9942b7
fixup! src,crypto: add NativeCryptoKey
panva Apr 24, 2026
665347f
fixup! lib,crypto: rewire CryptoKey on the native class
panva Apr 24, 2026
65e5612
fixup! src,crypto: add NativeCryptoKey
panva Apr 24, 2026
6843254
doc: update src/crypto/README.md
panva Apr 25, 2026
c4f74ad
fixup! src,crypto: add NativeCryptoKey
panva Apr 25, 2026
bc362b2
fixup! lib,crypto: rewire CryptoKey on the native class
panva Apr 25, 2026
db2a2fb
fixup! lib,crypto: migrate algorithm modules to native CryptoKey
panva Apr 25, 2026
1a8a0f0
fixup! lib,crypto: migrate algorithm modules to native CryptoKey
panva Apr 25, 2026
7bcee80
src,lib: switch usages internal slot to a bitmask
panva Apr 25, 2026
1af292c
lib: document more internal helpers
panva Apr 25, 2026
f402ca5
fixup! test: add CryptoKey class regression tests
panva Apr 25, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
97 changes: 97 additions & 0 deletions benchmark/crypto/webcrypto-sign.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
'use strict';

const common = require('../common.js');
const { hasOpenSSL } = require('../../test/common/crypto.js');
const { subtle } = globalThis.crypto;

const kAlgorithms = {
'ec': { name: 'ECDSA', namedCurve: 'P-256' },
'rsassa-pkcs1-v1_5': {
name: 'RSASSA-PKCS1-v1_5',
modulusLength: 2048,
publicExponent: new Uint8Array([1, 0, 1]),
hash: 'SHA-256',
},
'rsa-pss': {
name: 'RSA-PSS',
modulusLength: 2048,
publicExponent: new Uint8Array([1, 0, 1]),
hash: 'SHA-256',
},
'ed25519': { name: 'Ed25519' },
};

if (hasOpenSSL(3, 5)) {
kAlgorithms['ml-dsa-44'] = { name: 'ML-DSA-44' };
}

const kSignParams = {
'ec': { name: 'ECDSA', hash: 'SHA-256' },
'rsassa-pkcs1-v1_5': { name: 'RSASSA-PKCS1-v1_5' },
'rsa-pss': { name: 'RSA-PSS', saltLength: 32 },
'ed25519': { name: 'Ed25519' },
'ml-dsa-44': { name: 'ML-DSA-44' },
};

const data = globalThis.crypto.getRandomValues(new Uint8Array(256));

let keys;

const bench = common.createBenchmark(main, {
keyType: Object.keys(kAlgorithms),
mode: ['serial', 'parallel'],
keyReuse: ['shared', 'unique'],
n: [1e3],
}, {
combinationFilter(p) {
// Unique only differs from shared when operations overlap (parallel);
// sequential calls have no contention so unique+serial adds no value.
if (p.keyReuse === 'unique') return p.mode === 'parallel';
return true;
},
});

async function measureSerial(n, signParams, sharedKey) {
bench.start();
for (let i = 0; i < n; ++i) {
await subtle.sign(signParams, sharedKey || keys[i], data);
}
bench.end(n);
}

async function measureParallel(n, signParams, sharedKey) {
const promises = new Array(n);
bench.start();
for (let i = 0; i < n; ++i) {
promises[i] = subtle.sign(signParams, sharedKey || keys[i], data);
}
await Promise.all(promises);
bench.end(n);
}

async function main({ n, mode, keyReuse, keyType }) {
const algorithm = kAlgorithms[keyType];
const signParams = kSignParams[keyType];

if (!keys || keys.length !== n || keys[0].algorithm.name !== signParams.name) {
keys = new Array(n);
// Generate one key pair, then import its pkcs8 bytes n times to get
// distinct CryptoKey instances.
const kp = await subtle.generateKey(algorithm, true, ['sign', 'verify']);
const pkcs8 = await subtle.exportKey('pkcs8', kp.privateKey);
for (let i = 0; i < n; ++i) {
keys[i] = await subtle.importKey('pkcs8', pkcs8, algorithm, false, ['sign']);
}
}

const sharedKey = keyReuse === 'shared' ? keys[0] : undefined;

switch (mode) {
case 'serial':
await measureSerial(n, signParams, sharedKey);
break;
case 'parallel':
await measureParallel(n, signParams, sharedKey);
break;
}
}
100 changes: 100 additions & 0 deletions benchmark/crypto/webcrypto-verify.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
'use strict';

const common = require('../common.js');
const { hasOpenSSL } = require('../../test/common/crypto.js');
const { subtle } = globalThis.crypto;

const kAlgorithms = {
'ec': { name: 'ECDSA', namedCurve: 'P-256' },
'rsassa-pkcs1-v1_5': {
name: 'RSASSA-PKCS1-v1_5',
modulusLength: 2048,
publicExponent: new Uint8Array([1, 0, 1]),
hash: 'SHA-256',
},
'rsa-pss': {
name: 'RSA-PSS',
modulusLength: 2048,
publicExponent: new Uint8Array([1, 0, 1]),
hash: 'SHA-256',
},
'ed25519': { name: 'Ed25519' },
};

if (hasOpenSSL(3, 5)) {
kAlgorithms['ml-dsa-44'] = { name: 'ML-DSA-44' };
}

const kSignParams = {
'ec': { name: 'ECDSA', hash: 'SHA-256' },
'rsassa-pkcs1-v1_5': { name: 'RSASSA-PKCS1-v1_5' },
'rsa-pss': { name: 'RSA-PSS', saltLength: 32 },
'ed25519': { name: 'Ed25519' },
'ml-dsa-44': { name: 'ML-DSA-44' },
};

const data = globalThis.crypto.getRandomValues(new Uint8Array(256));

let publicKeys;
let signature;

const bench = common.createBenchmark(main, {
keyType: Object.keys(kAlgorithms),
mode: ['serial', 'parallel'],
keyReuse: ['shared', 'unique'],
n: [1e3],
}, {
combinationFilter(p) {
// Unique only differs from shared when operations overlap (parallel);
// sequential calls have no contention so unique+serial adds no value.
if (p.keyReuse === 'unique') return p.mode === 'parallel';
return true;
},
});

async function measureSerial(n, verifyParams, sharedKey) {
bench.start();
for (let i = 0; i < n; ++i) {
await subtle.verify(verifyParams, sharedKey || publicKeys[i], signature, data);
}
bench.end(n);
}

async function measureParallel(n, verifyParams, sharedKey) {
const promises = new Array(n);
bench.start();
for (let i = 0; i < n; ++i) {
promises[i] = subtle.verify(verifyParams, sharedKey || publicKeys[i], signature, data);
}
await Promise.all(promises);
bench.end(n);
}

async function main({ n, mode, keyReuse, keyType }) {
const algorithm = kAlgorithms[keyType];
const verifyParams = kSignParams[keyType];

if (!publicKeys || publicKeys.length !== n ||
publicKeys[0].algorithm.name !== verifyParams.name) {
publicKeys = new Array(n);
// Generate one key pair, then import its spki bytes n times to get
// distinct CryptoKey instances.
const kp = await subtle.generateKey(algorithm, true, ['sign', 'verify']);
const spki = await subtle.exportKey('spki', kp.publicKey);
for (let i = 0; i < n; ++i) {
publicKeys[i] = await subtle.importKey('spki', spki, algorithm, false, ['verify']);
}
signature = await subtle.sign(verifyParams, kp.privateKey, data);
}

const sharedKey = keyReuse === 'shared' ? publicKeys[0] : undefined;

switch (mode) {
case 'serial':
await measureSerial(n, verifyParams, sharedKey);
break;
case 'parallel':
await measureParallel(n, verifyParams, sharedKey);
break;
}
}
4 changes: 4 additions & 0 deletions lib/eslint.config_partial.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,10 @@ export default [
selector: "CallExpression[callee.type='Identifier'][callee.name='ReflectApply'][arguments.2.type='ArrayExpression']",
message: 'Use `FunctionPrototypeCall` to avoid creating an ad-hoc array',
},
{
selector: "VariableDeclarator[init.type='CallExpression'][init.callee.name='internalBinding'][init.arguments.0.value='crypto'] > ObjectPattern > Property[key.name='getCryptoKeySlots']",
message: "Use `const { getCryptoKeySlots } = require('internal/crypto/keys');` instead of destructuring it from `internalBinding('crypto')`.",
},
],
'no-restricted-globals': [
'error',
Expand Down
Loading
Loading