Commit d881931af47 for nodejs
commit d881931af47dedb48f283f6ceb8551e9e19df19c
Author: Filip Skokan <panva.ip@gmail.com>
Date: Tue Sep 22 22:44:50 2026 +0200
crypto: map JWK export failures to OperationError
Map inaccessible key material to the Web Crypto error type, including
seedless ML-DSA and ML-KEM keys converted from KeyObjects.
Assisted-by: Codex
Signed-off-by: Filip Skokan <panva.ip@gmail.com>
PR-URL: https://github.com/nodejs/node/pull/66237
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Aviv Keller <me@aviv.sh>
diff --git a/lib/internal/crypto/webcrypto.js b/lib/internal/crypto/webcrypto.js
index 2f44d726026..6585baa6fc5 100644
--- a/lib/internal/crypto/webcrypto.js
+++ b/lib/internal/crypto/webcrypto.js
@@ -806,7 +806,13 @@ function exportKeyJWK(key) {
};
if (alg === undefined) delete parameters.alg;
- return getCryptoKeyHandle(key).exportJwk(parameters, true);
+ try {
+ return getCryptoKeyHandle(key).exportJwk(parameters, true);
+ } catch (err) {
+ throw lazyDOMException(
+ 'The operation failed for an operation-specific reason',
+ { name: 'OperationError', cause: err });
+ }
}
function exportKeySync(format, key) {
diff --git a/test/parallel/test-webcrypto-seedless-jwk-export.js b/test/parallel/test-webcrypto-seedless-jwk-export.js
new file mode 100644
index 00000000000..1dcad8c64da
--- /dev/null
+++ b/test/parallel/test-webcrypto-seedless-jwk-export.js
@@ -0,0 +1,36 @@
+'use strict';
+
+const common = require('../common');
+if (!common.hasCrypto)
+ common.skip('missing crypto');
+const { hasOpenSSL, isBoringSSL } = require('../common/crypto');
+if (isBoringSSL || !hasOpenSSL(3, 5))
+ common.skip('requires OpenSSL 3.5 or later');
+
+const assert = require('assert');
+const { createPrivateKey } = require('crypto');
+const fixtures = require('../common/fixtures');
+const { subtle } = globalThis.crypto;
+
+(async () => {
+ const algorithm = { name: 'AES-GCM', iv: new Uint8Array(12) };
+ const wrappingKey = await subtle.generateKey({ name: 'AES-GCM', length: 128 },
+ false, ['wrapKey']);
+ for (const [name, usage] of [
+ ['ML-KEM-512', 'decapsulateBits'],
+ ['ML-KEM-768', 'decapsulateBits'],
+ ['ML-KEM-1024', 'decapsulateBits'],
+ ['ML-DSA-44', 'sign'],
+ ['ML-DSA-65', 'sign'],
+ ['ML-DSA-87', 'sign'],
+ ]) {
+ const file = `${name.toLowerCase().replaceAll('-', '_')}_private_priv_only.pem`;
+ const key = createPrivateKey(fixtures.readKey(file)).toCryptoKey(name, true, [usage]);
+ await assert.rejects(subtle.exportKey('jwk', key), {
+ name: 'OperationError', constructor: DOMException,
+ });
+ await assert.rejects(subtle.wrapKey('jwk', key, wrappingKey, algorithm), {
+ name: 'OperationError', constructor: DOMException,
+ });
+ }
+})().then(common.mustCall());