From f0b441230a936a79a5557762a18a1c4d1613eba2 Mon Sep 17 00:00:00 2001 From: Filip Skokan <panva.ip@gmail.com> Date: Sun, 6 Oct 2024 20:09:02 +0200 Subject: [PATCH] crypto: add KeyObject.prototype.toCryptoKey MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PR-URL: https://github.com/nodejs/node/pull/55262 Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Yagiz Nizipli <yagiz@nizipli.com> Reviewed-By: Tobias Nießen <tniessen@tnie.de> --- doc/api/crypto.md | 19 ++ lib/internal/crypto/aes.js | 7 +- lib/internal/crypto/cfrg.js | 7 +- lib/internal/crypto/ec.js | 7 +- lib/internal/crypto/keys.js | 159 +++++++++++++++ lib/internal/crypto/mac.js | 20 +- lib/internal/crypto/rsa.js | 7 +- lib/internal/crypto/webcrypto.js | 61 +----- .../test-crypto-key-objects-to-crypto-key.js | 182 ++++++++++++++++++ test/parallel/test-webcrypto-export-import.js | 5 + 10 files changed, 415 insertions(+), 59 deletions(-) create mode 100644 test/parallel/test-crypto-key-objects-to-crypto-key.js diff --git a/doc/api/crypto.md b/doc/api/crypto.md index a774507171d431..e836e6ced8c226 100644 --- a/doc/api/crypto.md +++ b/doc/api/crypto.md @@ -2134,6 +2134,24 @@ added: v11.6.0 For secret keys, this property represents the size of the key in bytes. This property is `undefined` for asymmetric keys. +### `keyObject.toCryptoKey(algorithm, extractable, keyUsages)` + +<!-- YAML +added: REPLACEME +--> + +<!--lint disable maximum-line-length remark-lint--> + +* `algorithm`: {AlgorithmIdentifier|RsaHashedImportParams|EcKeyImportParams|HmacImportParams} + +<!--lint enable maximum-line-length remark-lint--> + +* `extractable`: {boolean} +* `keyUsages`: {string\[]} See [Key usages][]. +* Returns: {CryptoKey} + +Converts a `KeyObject` instance to a `CryptoKey`. + ### `keyObject.type` <!-- YAML @@ -6084,6 +6102,7 @@ See the [list of SSL OP Flags][] for details. [FIPS provider from OpenSSL 3]: https://www.openssl.org/docs/man3.0/man7/crypto.html#FIPS-provider [HTML 5.2]: https://www.w3.org/TR/html52/changes.html#features-removed [JWK]: https://tools.ietf.org/html/rfc7517 +[Key usages]: webcrypto.md#cryptokeyusages [NIST SP 800-131A]: https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-131Ar2.pdf [NIST SP 800-132]: https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-132.pdf [NIST SP 800-38D]: https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38d.pdf diff --git a/lib/internal/crypto/aes.js b/lib/internal/crypto/aes.js index 762e8ead4ac46f..1d5d0bdb2380d8 100644 --- a/lib/internal/crypto/aes.js +++ b/lib/internal/crypto/aes.js @@ -245,7 +245,7 @@ async function aesGenerateKey(algorithm, extractable, keyUsages) { extractable); } -async function aesImportKey( +function aesImportKey( algorithm, format, keyData, @@ -266,6 +266,11 @@ async function aesImportKey( let keyObject; let length; switch (format) { + case 'KeyObject': { + validateKeyLength(keyData.symmetricKeySize * 8); + keyObject = keyData; + break; + } case 'raw': { validateKeyLength(keyData.byteLength * 8); keyObject = createSecretKey(keyData); diff --git a/lib/internal/crypto/cfrg.js b/lib/internal/crypto/cfrg.js index 9c5e59ebb3bf86..7bea4ed3544f87 100644 --- a/lib/internal/crypto/cfrg.js +++ b/lib/internal/crypto/cfrg.js @@ -197,7 +197,7 @@ function cfrgExportKey(key, format) { key[kKeyObject][kHandle])); } -async function cfrgImportKey( +function cfrgImportKey( format, keyData, algorithm, @@ -208,6 +208,11 @@ async function cfrgImportKey( let keyObject; const usagesSet = new SafeSet(keyUsages); switch (format) { + case 'KeyObject': { + verifyAcceptableCfrgKeyUse(name, keyData.type === 'public', usagesSet); + keyObject = keyData; + break; + } case 'spki': { verifyAcceptableCfrgKeyUse(name, true, usagesSet); try { diff --git a/lib/internal/crypto/ec.js b/lib/internal/crypto/ec.js index e155a5b6610044..3cd5d12cdb2bc2 100644 --- a/lib/internal/crypto/ec.js +++ b/lib/internal/crypto/ec.js @@ -149,7 +149,7 @@ function ecExportKey(key, format) { key[kKeyObject][kHandle])); } -async function ecImportKey( +function ecImportKey( format, keyData, algorithm, @@ -167,6 +167,11 @@ async function ecImportKey( let keyObject; const usagesSet = new SafeSet(keyUsages); switch (format) { + case 'KeyObject': { + verifyAcceptableEcKeyUse(name, keyData.type === 'public', usagesSet); + keyObject = keyData; + break; + } case 'spki': { verifyAcceptableEcKeyUse(name, true, usagesSet); try { diff --git a/lib/internal/crypto/keys.js b/lib/internal/crypto/keys.js index e7d2275ee1a9fa..4738fbbbf208d1 100644 --- a/lib/internal/crypto/keys.js +++ b/lib/internal/crypto/keys.js @@ -6,6 +6,7 @@ const { ObjectDefineProperties, ObjectDefineProperty, ObjectSetPrototypeOf, + SafeSet, Symbol, SymbolToStringTag, Uint8Array, @@ -49,6 +50,8 @@ const { kKeyObject, getArrayBufferOrView, bigIntArrayToUnsignedBigInt, + normalizeAlgorithm, + hasAnyNotIn, } = require('internal/crypto/util'); const { @@ -65,6 +68,7 @@ const { const { customInspectSymbol: kInspect, kEnumerableProperty, + lazyDOMException, } = require('internal/util'); const { inspect } = require('internal/util/inspect'); @@ -148,6 +152,8 @@ const { }, }); + let webidl; + class SecretKeyObject extends KeyObject { constructor(handle) { super('secret', handle); @@ -168,6 +174,51 @@ const { } return this[kHandle].export(); } + + toCryptoKey(algorithm, extractable, keyUsages) { + webidl ??= require('internal/crypto/webidl'); + algorithm = normalizeAlgorithm(webidl.converters.AlgorithmIdentifier(algorithm), 'importKey'); + extractable = webidl.converters.boolean(extractable); + keyUsages = webidl.converters['sequence<KeyUsage>'](keyUsages); + + let result; + switch (algorithm.name) { + case 'HMAC': + result = require('internal/crypto/mac') + .hmacImportKey('KeyObject', this, algorithm, extractable, keyUsages); + break; + case 'AES-CTR': + // Fall through + case 'AES-CBC': + // Fall through + case 'AES-GCM': + // Fall through + case 'AES-KW': + result = require('internal/crypto/aes') + .aesImportKey(algorithm, 'KeyObject', this, extractable, keyUsages); + break; + case 'HKDF': + // Fall through + case 'PBKDF2': + result = importGenericSecretKey( + algorithm, + 'KeyObject', + this, + extractable, + keyUsages); + break; + default: + throw lazyDOMException('Unrecognized algorithm name', 'NotSupportedError'); + } + + if (result.usages.length === 0) { + throw lazyDOMException( + `Usages cannot be empty when importing a ${result.type} key.`, + 'SyntaxError'); + } + + return result; + } } const kAsymmetricKeyType = Symbol('kAsymmetricKeyType'); @@ -209,6 +260,51 @@ const { return {}; } } + + toCryptoKey(algorithm, extractable, keyUsages) { + webidl ??= require('internal/crypto/webidl'); + algorithm = normalizeAlgorithm(webidl.converters.AlgorithmIdentifier(algorithm), 'importKey'); + extractable = webidl.converters.boolean(extractable); + keyUsages = webidl.converters['sequence<KeyUsage>'](keyUsages); + + let result; + switch (algorithm.name) { + case 'RSASSA-PKCS1-v1_5': + // Fall through + case 'RSA-PSS': + // Fall through + case 'RSA-OAEP': + result = require('internal/crypto/rsa') + .rsaImportKey('KeyObject', this, algorithm, extractable, keyUsages); + break; + case 'ECDSA': + // Fall through + case 'ECDH': + result = require('internal/crypto/ec') + .ecImportKey('KeyObject', this, algorithm, extractable, keyUsages); + break; + case 'Ed25519': + // Fall through + case 'Ed448': + // Fall through + case 'X25519': + // Fall through + case 'X448': + result = require('internal/crypto/cfrg') + .cfrgImportKey('KeyObject', this, algorithm, extractable, keyUsages); + break; + default: + throw lazyDOMException('Unrecognized algorithm name', 'NotSupportedError'); + } + + if (result.type === 'private' && result.usages.length === 0) { + throw lazyDOMException( + `Usages cannot be empty when importing a ${result.type} key.`, + 'SyntaxError'); + } + + return result; + } } class PublicKeyObject extends AsymmetricKeyObject { @@ -801,6 +897,68 @@ function isCryptoKey(obj) { return obj != null && obj[kKeyObject] !== undefined; } +function importGenericSecretKey( + { name, length }, + format, + keyData, + extractable, + keyUsages) { + const usagesSet = new SafeSet(keyUsages); + if (extractable) + throw lazyDOMException(`${name} keys are not extractable`, 'SyntaxError'); + + if (hasAnyNotIn(usagesSet, ['deriveKey', 'deriveBits'])) { + throw lazyDOMException( + `Unsupported key usage for a ${name} key`, + 'SyntaxError'); + } + + switch (format) { + case 'KeyObject': { + if (hasAnyNotIn(usagesSet, ['deriveKey', 'deriveBits'])) { + throw lazyDOMException( + `Unsupported key usage for a ${name} key`, + 'SyntaxError'); + } + + const checkLength = keyData.symmetricKeySize * 8; + + // The Web Crypto spec allows for key lengths that are not multiples of + // 8. We don't. Our check here is stricter than that defined by the spec + // in that we require that algorithm.length match keyData.length * 8 if + // algorithm.length is specified. + if (length !== undefined && length !== checkLength) { + throw lazyDOMException('Invalid key length', 'DataError'); + } + return new InternalCryptoKey(keyData, { name }, keyUsages, false); + } + case 'raw': { + if (hasAnyNotIn(usagesSet, ['deriveKey', 'deriveBits'])) { + throw lazyDOMException( + `Unsupported key usage for a ${name} key`, + 'SyntaxError'); + } + + const checkLength = keyData.byteLength * 8; + + // The Web Crypto spec allows for key lengths that are not multiples of + // 8. We don't. Our check here is stricter than that defined by the spec + // in that we require that algorithm.length match keyData.length * 8 if + // algorithm.length is specified. + if (length !== undefined && length !== checkLength) { + throw lazyDOMException('Invalid key length', 'DataError'); + } + + const keyObject = createSecretKey(keyData); + return new InternalCryptoKey(keyObject, { name }, keyUsages, false); + } + } + + throw lazyDOMException( + `Unable to import ${name} key with format ${format}`, + 'NotSupportedError'); +} + module.exports = { // Public API. createSecretKey, @@ -822,4 +980,5 @@ module.exports = { PrivateKeyObject, isKeyObject, isCryptoKey, + importGenericSecretKey, }; diff --git a/lib/internal/crypto/mac.js b/lib/internal/crypto/mac.js index 112861523c605f..356ce5a8d79a31 100644 --- a/lib/internal/crypto/mac.js +++ b/lib/internal/crypto/mac.js @@ -82,7 +82,7 @@ function getAlgorithmName(hash) { } } -async function hmacImportKey( +function hmacImportKey( format, keyData, algorithm, @@ -96,6 +96,24 @@ async function hmacImportKey( } let keyObject; switch (format) { + case 'KeyObject': { + const checkLength = keyData.symmetricKeySize * 8; + + if (checkLength === 0 || algorithm.length === 0) + throw lazyDOMException('Zero-length key is not supported', 'DataError'); + + // The Web Crypto spec allows for key lengths that are not multiples of + // 8. We don't. Our check here is stricter than that defined by the spec + // in that we require that algorithm.length match keyData.length * 8 if + // algorithm.length is specified. + if (algorithm.length !== undefined && + algorithm.length !== checkLength) { + throw lazyDOMException('Invalid key length', 'DataError'); + } + + keyObject = keyData; + break; + } case 'raw': { const checkLength = keyData.byteLength * 8; diff --git a/lib/internal/crypto/rsa.js b/lib/internal/crypto/rsa.js index fd223d3cb632ab..99c12ebbe3b83f 100644 --- a/lib/internal/crypto/rsa.js +++ b/lib/internal/crypto/rsa.js @@ -200,7 +200,7 @@ function rsaExportKey(key, format) { kRsaVariants[key.algorithm.name])); } -async function rsaImportKey( +function rsaImportKey( format, keyData, algorithm, @@ -209,6 +209,11 @@ async function rsaImportKey( const usagesSet = new SafeSet(keyUsages); let keyObject; switch (format) { + case 'KeyObject': { + verifyAcceptableRsaKeyUse(algorithm.name, keyData.type === 'public', usagesSet); + keyObject = keyData; + break; + } case 'spki': { verifyAcceptableRsaKeyUse(algorithm.name, true, usagesSet); try { diff --git a/lib/internal/crypto/webcrypto.js b/lib/internal/crypto/webcrypto.js index 8cd27717532344..4758c2c1c7133a 100644 --- a/lib/internal/crypto/webcrypto.js +++ b/lib/internal/crypto/webcrypto.js @@ -8,7 +8,6 @@ const { ObjectDefineProperty, ReflectApply, ReflectConstruct, - SafeSet, StringPrototypeRepeat, SymbolToStringTag, } = primordials; @@ -36,8 +35,7 @@ const { const { CryptoKey, - InternalCryptoKey, - createSecretKey, + importGenericSecretKey, } = require('internal/crypto/keys'); const { @@ -46,7 +44,6 @@ const { const { getBlockSize, - hasAnyNotIn, normalizeAlgorithm, normalizeHashName, validateMaxBufferLength, @@ -526,50 +523,6 @@ async function exportKey(format, key) { 'Export format is unsupported', 'NotSupportedError'); } -async function importGenericSecretKey( - { name, length }, - format, - keyData, - extractable, - keyUsages) { - const usagesSet = new SafeSet(keyUsages); - if (extractable) - throw lazyDOMException(`${name} keys are not extractable`, 'SyntaxError'); - - if (hasAnyNotIn(usagesSet, ['deriveKey', 'deriveBits'])) { - throw lazyDOMException( - `Unsupported key usage for a ${name} key`, - 'SyntaxError'); - } - - switch (format) { - case 'raw': { - if (hasAnyNotIn(usagesSet, ['deriveKey', 'deriveBits'])) { - throw lazyDOMException( - `Unsupported key usage for a ${name} key`, - 'SyntaxError'); - } - - const checkLength = keyData.byteLength * 8; - - // The Web Crypto spec allows for key lengths that are not multiples of - // 8. We don't. Our check here is stricter than that defined by the spec - // in that we require that algorithm.length match keyData.length * 8 if - // algorithm.length is specified. - if (length !== undefined && length !== checkLength) { - throw lazyDOMException('Invalid key length', 'DataError'); - } - - const keyObject = createSecretKey(keyData); - return new InternalCryptoKey(keyObject, { name }, keyUsages, false); - } - } - - throw lazyDOMException( - `Unable to import ${name} key with format ${format}`, - 'NotSupportedError'); -} - async function importKey( format, keyData, @@ -611,13 +564,13 @@ async function importKey( case 'RSA-PSS': // Fall through case 'RSA-OAEP': - result = await require('internal/crypto/rsa') + result = require('internal/crypto/rsa') .rsaImportKey(format, keyData, algorithm, extractable, keyUsages); break; case 'ECDSA': // Fall through case 'ECDH': - result = await require('internal/crypto/ec') + result = require('internal/crypto/ec') .ecImportKey(format, keyData, algorithm, extractable, keyUsages); break; case 'Ed25519': @@ -627,11 +580,11 @@ async function importKey( case 'X25519': // Fall through case 'X448': - result = await require('internal/crypto/cfrg') + result = require('internal/crypto/cfrg') .cfrgImportKey(format, keyData, algorithm, extractable, keyUsages); break; case 'HMAC': - result = await require('internal/crypto/mac') + result = require('internal/crypto/mac') .hmacImportKey(format, keyData, algorithm, extractable, keyUsages); break; case 'AES-CTR': @@ -641,13 +594,13 @@ async function importKey( case 'AES-GCM': // Fall through case 'AES-KW': - result = await require('internal/crypto/aes') + result = require('internal/crypto/aes') .aesImportKey(algorithm, format, keyData, extractable, keyUsages); break; case 'HKDF': // Fall through case 'PBKDF2': - result = await importGenericSecretKey( + result = importGenericSecretKey( algorithm, format, keyData, diff --git a/test/parallel/test-crypto-key-objects-to-crypto-key.js b/test/parallel/test-crypto-key-objects-to-crypto-key.js new file mode 100644 index 00000000000000..1656f37a3c58b5 --- /dev/null +++ b/test/parallel/test-crypto-key-objects-to-crypto-key.js @@ -0,0 +1,182 @@ +'use strict'; + +const common = require('../common'); +if (!common.hasCrypto) + common.skip('missing crypto'); + +const assert = require('assert'); +const { + createSecretKey, + KeyObject, + randomBytes, + generateKeyPairSync, +} = require('crypto'); + +function assertCryptoKey(cryptoKey, keyObject, algorithm, extractable, usages) { + assert.strictEqual(cryptoKey instanceof CryptoKey, true); + assert.strictEqual(cryptoKey.type, keyObject.type); + assert.strictEqual(cryptoKey.algorithm.name, algorithm); + assert.strictEqual(cryptoKey.extractable, extractable); + assert.deepStrictEqual(cryptoKey.usages, usages); + assert.strictEqual(keyObject.equals(KeyObject.from(cryptoKey)), true); +} + +{ + for (const length of [128, 192, 256]) { + const aes = createSecretKey(randomBytes(length >> 3)); + for (const algorithm of ['AES-CTR', 'AES-CBC', 'AES-GCM', 'AES-KW']) { + const usages = algorithm === 'AES-KW' ? ['wrapKey', 'unwrapKey'] : ['encrypt', 'decrypt']; + for (const extractable of [true, false]) { + const cryptoKey = aes.toCryptoKey(algorithm, extractable, usages); + assertCryptoKey(cryptoKey, aes, algorithm, extractable, usages); + assert.strictEqual(cryptoKey.algorithm.length, length); + } + } + } +} + +{ + const pbkdf2 = createSecretKey(randomBytes(16)); + const algorithm = 'PBKDF2'; + const usages = ['deriveBits']; + assert.throws(() => pbkdf2.toCryptoKey(algorithm, true, usages), { + name: 'SyntaxError', + message: 'PBKDF2 keys are not extractable' + }); + assert.throws(() => pbkdf2.toCryptoKey(algorithm, false, ['wrapKey']), { + name: 'SyntaxError', + message: 'Unsupported key usage for a PBKDF2 key' + }); + const cryptoKey = pbkdf2.toCryptoKey(algorithm, false, usages); + assertCryptoKey(cryptoKey, pbkdf2, algorithm, false, usages); + assert.strictEqual(cryptoKey.algorithm.length, undefined); +} + +{ + for (const length of [128, 192, 256]) { + const hmac = createSecretKey(randomBytes(length >> 3)); + const algorithm = 'HMAC'; + const usages = ['sign', 'verify']; + + assert.throws(() => { + createSecretKey(Buffer.alloc(0)).toCryptoKey({ name: algorithm, hash: 'SHA-256' }, true, usages); + }, { + name: 'DataError', + message: 'Zero-length key is not supported', + }); + + assert.throws(() => { + hmac.toCryptoKey({ + name: algorithm, + hash: 'SHA-256', + }, true, []); + }, { + name: 'SyntaxError', + message: 'Usages cannot be empty when importing a secret key.' + }); + + for (const hash of ['SHA-1', 'SHA-256', 'SHA-384', 'SHA-512']) { + for (const extractable of [true, false]) { + assert.throws(() => { + hmac.toCryptoKey({ name: algorithm, hash: 'SHA-256', length: 0 }, true, usages); + }, { + name: 'DataError', + message: 'Zero-length key is not supported', + }); + const cryptoKey = hmac.toCryptoKey({ name: algorithm, hash }, extractable, usages); + assertCryptoKey(cryptoKey, hmac, algorithm, extractable, usages); + assert.strictEqual(cryptoKey.algorithm.length, length); + } + } + } +} + +{ + for (const algorithm of ['Ed25519', 'Ed448', 'X25519', 'X448']) { + const { publicKey, privateKey } = generateKeyPairSync(algorithm.toLowerCase()); + assert.throws(() => { + publicKey.toCryptoKey(algorithm === 'Ed25519' ? 'X25519' : 'Ed25519', true, []); + }, { + name: 'DataError', + message: 'Invalid key type' + }); + for (const key of [publicKey, privateKey]) { + let usages; + if (algorithm.startsWith('E')) { + usages = key.type === 'public' ? ['verify'] : ['sign']; + } else { + usages = key.type === 'public' ? [] : ['deriveBits']; + } + for (const extractable of [true, false]) { + const cryptoKey = key.toCryptoKey(algorithm, extractable, usages); + assertCryptoKey(cryptoKey, key, algorithm, extractable, usages); + } + } + } +} + +{ + const { publicKey, privateKey } = generateKeyPairSync('rsa', { modulusLength: 2048 }); + for (const key of [publicKey, privateKey]) { + for (const algorithm of ['RSASSA-PKCS1-v1_5', 'RSA-PSS', 'RSA-OAEP']) { + let usages; + if (algorithm === 'RSA-OAEP') { + usages = key.type === 'public' ? ['encrypt', 'wrapKey'] : ['decrypt', 'unwrapKey']; + } else { + usages = key.type === 'public' ? ['verify'] : ['sign']; + } + for (const extractable of [true, false]) { + for (const hash of ['SHA-1', 'SHA-256', 'SHA-384', 'SHA-512']) { + const cryptoKey = key.toCryptoKey({ + name: algorithm, + hash + }, extractable, usages); + assertCryptoKey(cryptoKey, key, algorithm, extractable, usages); + assert.strictEqual(cryptoKey.algorithm.hash.name, hash); + } + } + } + } +} + +{ + for (const namedCurve of ['P-256', 'P-384', 'P-521']) { + const { publicKey, privateKey } = generateKeyPairSync('ec', { namedCurve }); + assert.throws(() => { + privateKey.toCryptoKey({ + name: 'ECDH', + namedCurve, + }, true, []); + }, { + name: 'SyntaxError', + message: 'Usages cannot be empty when importing a private key.' + }); + assert.throws(() => { + publicKey.toCryptoKey({ + name: 'ECDH', + namedCurve: namedCurve === 'P-256' ? 'P-384' : 'P-256' + }, true, []); + }, { + name: 'DataError', + message: 'Named curve mismatch' + }); + for (const key of [publicKey, privateKey]) { + for (const algorithm of ['ECDH', 'ECDSA']) { + let usages; + if (algorithm === 'ECDH') { + usages = key.type === 'public' ? [] : ['deriveBits']; + } else { + usages = key.type === 'public' ? ['verify'] : ['sign']; + } + for (const extractable of [true, false]) { + const cryptoKey = key.toCryptoKey({ + name: algorithm, + namedCurve + }, extractable, usages); + assertCryptoKey(cryptoKey, key, algorithm, extractable, usages); + assert.strictEqual(cryptoKey.algorithm.namedCurve, namedCurve); + } + } + } + } +} diff --git a/test/parallel/test-webcrypto-export-import.js b/test/parallel/test-webcrypto-export-import.js index e7d45dbc5efeea..21a8d16edbea0e 100644 --- a/test/parallel/test-webcrypto-export-import.js +++ b/test/parallel/test-webcrypto-export-import.js @@ -21,6 +21,11 @@ const { subtle } = globalThis.crypto; subtle.importKey('not valid', keyData, {}, false, ['wrapKey']), { code: 'ERR_INVALID_ARG_VALUE' }); + await assert.rejects( + subtle.importKey('KeyObject', keyData, {}, false, ['wrapKey']), { + message: /'KeyObject' is not a valid enum value of type KeyFormat/, + code: 'ERR_INVALID_ARG_VALUE' + }); await assert.rejects( subtle.importKey('raw', 1, {}, false, ['deriveBits']), { code: 'ERR_INVALID_ARG_TYPE'