Skip to content

Commit 8f3f9db

Browse files
committed
feat: US-005 - Implement crypto.subtle sign/verify and key derivation
1 parent 5db39c4 commit 8f3f9db

10 files changed

Lines changed: 1020 additions & 113 deletions

File tree

docs/nodejs-conformance-report.mdx

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -12,24 +12,24 @@ description: Node.js v22 test/parallel/ conformance results for the secure-exec
1212
| Node.js version | 22.14.0 |
1313
| Source | v22.14.0 (test/parallel/) |
1414
| Total tests | 3532 |
15-
| Passing (genuine) | 741 (21.0%) |
16-
| Passing (vacuous self-skip) | 34 |
17-
| Passing (total) | 775 (21.9%) |
18-
| Expected fail | 2686 |
15+
| Passing (genuine) | 740 (21.0%) |
16+
| Passing (vacuous self-skip) | 33 |
17+
| Passing (total) | 773 (21.9%) |
18+
| Expected fail | 2688 |
1919
| Skip | 71 |
20-
| Last updated | 2026-03-25 |
20+
| Last updated | 2026-03-26 |
2121

2222
## Failure Categories
2323

2424
| Category | Tests |
2525
| --- | --- |
26-
| implementation-gap | 1385 |
27-
| unsupported-module | 737 |
26+
| implementation-gap | 1386 |
27+
| unsupported-module | 738 |
2828
| requires-v8-flags | 239 |
2929
| requires-exec-path | 200 |
3030
| unsupported-api | 124 |
3131
| test-infra | 68 |
32-
| vacuous-skip | 34 |
32+
| vacuous-skip | 33 |
3333
| native-addon | 3 |
3434
| security-constraint | 1 |
3535

@@ -70,7 +70,7 @@ description: Node.js v22 test/parallel/ conformance results for the secure-exec
7070
| constants | 1 | 0 | 1 | 0 | 0.0% |
7171
| corepack | 1 | 0 | 1 | 0 | 0.0% |
7272
| coverage | 1 | 0 | 1 | 0 | 0.0% |
73-
| crypto | 99 | 50 (13 vacuous) | 49 | 0 | 50.5% |
73+
| crypto | 99 | 48 (12 vacuous) | 51 | 0 | 48.5% |
7474
| cwd | 3 | 0 | 3 | 0 | 0.0% |
7575
| data | 1 | 0 | 1 | 0 | 0.0% |
7676
| datetime | 1 | 0 | 1 | 0 | 0.0% |
@@ -245,11 +245,11 @@ description: Node.js v22 test/parallel/ conformance results for the secure-exec
245245
| wrap | 4 | 0 | 4 | 0 | 0.0% |
246246
| x509 | 1 | 0 | 1 | 0 | 0.0% |
247247
| zlib | 53 | 17 | 33 | 3 | 34.0% |
248-
| **Total** | **3532** | **775** | **2686** | **71** | **22.4%** |
248+
| **Total** | **3532** | **773** | **2688** | **71** | **22.3%** |
249249

250250
## Expectations Detail
251251

252-
### implementation-gap (704 entries)
252+
### implementation-gap (705 entries)
253253

254254
**Glob patterns:**
255255

@@ -260,9 +260,9 @@ description: Node.js v22 test/parallel/ conformance results for the secure-exec
260260
- `test-https-*.js` — https depends on tls — most tests fail on missing TLS fixture files or crypto API gaps
261261
- `test-http2-*.js` — http2 module bridged via kernel — most tests fail on API gaps, missing fixtures, or protocol handling
262262

263-
*698 individual tests — see expectations.json for full list.*
263+
*699 individual tests — see expectations.json for full list.*
264264

265-
### unsupported-module (190 entries)
265+
### unsupported-module (191 entries)
266266

267267
**Glob patterns:**
268268

@@ -278,7 +278,7 @@ description: Node.js v22 test/parallel/ conformance results for the secure-exec
278278
- `test-debugger-*.js` — debugger protocol requires inspector which is Tier 5 (Unsupported)
279279
- `test-quic-*.js` — QUIC protocol depends on tls which is Tier 4 (Deferred)
280280

281-
<Accordion title="179 individual tests">
281+
<Accordion title="180 individual tests">
282282

283283
| Test | Reason |
284284
| --- | --- |
@@ -458,6 +458,7 @@ description: Node.js v22 test/parallel/ conformance results for the secure-exec
458458
| `test-util-text-decoder.js` | requires node:test module which is not available in sandbox |
459459
| `test-warn-stream-wrap.js` | require('_stream_wrap') module not registered in sandbox — _stream_wrap is an internal Node.js alias not exposed through readable-stream polyfill |
460460
| `test-vm-timeout.js` | hangs — vm.runInNewContext with timeout blocks waiting for vm module (not available) |
461+
| `test-crypto-worker-thread.js` | requires worker_threads module which is Tier 4 (Deferred) |
461462
| `test-assert-fail-deprecation.js` | requires 'test' module (node:test) which is not available in sandbox |
462463
| `test-buffer-resizable.js` | requires 'test' module (node:test) which is not available in sandbox |
463464
| `test-stream-consumers.js` | stream/consumers submodule not available in stream polyfill |
@@ -800,9 +801,9 @@ description: Node.js v22 test/parallel/ conformance results for the secure-exec
800801

801802
</Accordion>
802803

803-
### vacuous-skip (34 entries)
804+
### vacuous-skip (33 entries)
804805

805-
<Accordion title="34 individual tests">
806+
<Accordion title="33 individual tests">
806807

807808
| Test | Reason |
808809
| --- | --- |
@@ -813,7 +814,6 @@ description: Node.js v22 test/parallel/ conformance results for the secure-exec
813814
| `test-crypto-keygen-empty-passphrase-no-error.js` | vacuous pass — test self-skips via common.skip() because common.hasCrypto is false |
814815
| `test-crypto-keygen-missing-oid.js` | vacuous pass — test self-skips via common.skip() because common.hasCrypto is false |
815816
| `test-crypto-keygen-promisify.js` | vacuous pass — test self-skips via common.skip() because common.hasCrypto is false |
816-
| `test-crypto-no-algorithm.js` | vacuous pass — test self-skips via common.skip() because common.hasCrypto is false |
817817
| `test-crypto-op-during-process-exit.js` | vacuous pass — test self-skips via common.skip() because common.hasCrypto is false |
818818
| `test-crypto-padding-aes256.js` | vacuous pass — test self-skips via common.skip() because common.hasCrypto is false |
819819
| `test-crypto-publicDecrypt-fails-first-time.js` | vacuous pass — test self-skips via common.skip() because common.hasCrypto is false |

packages/core/isolate-runtime/src/inject/require-setup.ts

Lines changed: 50 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1333,6 +1333,28 @@
13331333
return Object.assign({}, algorithm);
13341334
}
13351335

1336+
function createCompatibleCryptoKey(keyData) {
1337+
var key;
1338+
if (
1339+
globalThis.CryptoKey &&
1340+
globalThis.CryptoKey.prototype &&
1341+
globalThis.CryptoKey.prototype !== SandboxCryptoKey.prototype
1342+
) {
1343+
key = Object.create(globalThis.CryptoKey.prototype);
1344+
key.type = keyData.type;
1345+
key.extractable = keyData.extractable;
1346+
key.algorithm = keyData.algorithm;
1347+
key.usages = keyData.usages;
1348+
key._keyData = keyData;
1349+
key._pem = keyData._pem;
1350+
key._jwk = keyData._jwk;
1351+
key._raw = keyData._raw;
1352+
key._sourceKeyObjectData = keyData._sourceKeyObjectData;
1353+
return key;
1354+
}
1355+
return new SandboxCryptoKey(keyData);
1356+
}
1357+
13361358
function buildCryptoKeyFromKeyObject(keyObject, algorithm, extractable, usages) {
13371359
var algo = normalizeAlgorithmInput(algorithm);
13381360
var name = algo.name;
@@ -1346,7 +1368,7 @@
13461368
if (usages.some(function(usage) { return usage !== 'deriveBits' && usage !== 'deriveKey'; })) {
13471369
throw new SyntaxError('Unsupported key usage for a PBKDF2 key');
13481370
}
1349-
return new SandboxCryptoKey({
1371+
return createCompatibleCryptoKey({
13501372
type: 'secret',
13511373
extractable: extractable,
13521374
algorithm: { name: name },
@@ -1365,7 +1387,7 @@
13651387
if (!usages.length) {
13661388
throw new SyntaxError('Usages cannot be empty when importing a secret key.');
13671389
}
1368-
return new SandboxCryptoKey({
1390+
return createCompatibleCryptoKey({
13691391
type: 'secret',
13701392
extractable: extractable,
13711393
algorithm: {
@@ -1381,7 +1403,7 @@
13811403
},
13821404
});
13831405
}
1384-
return new SandboxCryptoKey({
1406+
return createCompatibleCryptoKey({
13851407
type: 'secret',
13861408
extractable: extractable,
13871409
algorithm: {
@@ -1428,7 +1450,7 @@
14281450
normalizedAlgo.hash = { name: normalizedAlgo.hash };
14291451
}
14301452

1431-
return new SandboxCryptoKey({
1453+
return createCompatibleCryptoKey({
14321454
type: keyObject.type,
14331455
extractable: extractable,
14341456
algorithm: normalizedAlgo,
@@ -1668,8 +1690,32 @@
16681690
configurable: true,
16691691
});
16701692

1693+
Object.defineProperty(SandboxCryptoKey, Symbol.hasInstance, {
1694+
value: function(candidate) {
1695+
return !!(
1696+
candidate &&
1697+
typeof candidate === 'object' &&
1698+
(
1699+
candidate._keyData ||
1700+
candidate[Symbol.toStringTag] === 'CryptoKey'
1701+
)
1702+
);
1703+
},
1704+
configurable: true,
1705+
});
1706+
1707+
if (
1708+
globalThis.CryptoKey &&
1709+
globalThis.CryptoKey.prototype &&
1710+
globalThis.CryptoKey.prototype !== SandboxCryptoKey.prototype
1711+
) {
1712+
Object.setPrototypeOf(SandboxCryptoKey.prototype, globalThis.CryptoKey.prototype);
1713+
}
1714+
16711715
if (typeof globalThis.CryptoKey === 'undefined') {
16721716
__requireExposeCustomGlobal('CryptoKey', SandboxCryptoKey);
1717+
} else if (globalThis.CryptoKey !== SandboxCryptoKey) {
1718+
globalThis.CryptoKey = SandboxCryptoKey;
16731719
}
16741720

16751721
function toBase64(data) {

packages/core/src/generated/isolate-runtime.ts

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.

0 commit comments

Comments
 (0)