diff --git a/lib/internal/crypto/aes.js b/lib/internal/crypto/aes.js index 5b4e7d8f743469..67b6d306dc44e1 100644 --- a/lib/internal/crypto/aes.js +++ b/lib/internal/crypto/aes.js @@ -32,7 +32,6 @@ const { } = internalBinding('crypto'); const { - getArrayBufferOrView, hasAnyNotIn, jobPromise, validateByteLength, @@ -112,7 +111,6 @@ function getVariant(name, length) { } function asyncAesCtrCipher(mode, key, data, { counter, length }) { - counter = getArrayBufferOrView(counter, 'algorithm.counter'); validateByteLength(counter, 'algorithm.counter', 16); // The length must specify an integer between 1 and 128. While // there is no default, this should typically be 64. @@ -135,7 +133,6 @@ function asyncAesCtrCipher(mode, key, data, { counter, length }) { } function asyncAesCbcCipher(mode, key, data, { iv }) { - iv = getArrayBufferOrView(iv, 'algorithm.iv'); validateByteLength(iv, 'algorithm.iv', 16); return jobPromise(() => new AESCipherJob( kCryptoJobAsync, @@ -166,12 +163,9 @@ function asyncAesGcmCipher( 'OperationError')); } - iv = getArrayBufferOrView(iv, 'algorithm.iv'); validateMaxBufferLength(iv, 'algorithm.iv'); if (additionalData !== undefined) { - additionalData = - getArrayBufferOrView(additionalData, 'algorithm.additionalData'); validateMaxBufferLength(additionalData, 'algorithm.additionalData'); } diff --git a/lib/internal/crypto/cfrg.js b/lib/internal/crypto/cfrg.js index 6f098df41530b2..47a4f78e415b7a 100644 --- a/lib/internal/crypto/cfrg.js +++ b/lib/internal/crypto/cfrg.js @@ -18,7 +18,6 @@ const { } = internalBinding('crypto'); const { - getArrayBufferOrView, getUsagesUnion, hasAnyNotIn, jobPromise, @@ -73,7 +72,6 @@ function verifyAcceptableCfrgKeyUse(name, isPublic, usages) { function createCFRGRawKey(name, keyData, isPublic) { const handle = new KeyObjectHandle(); - keyData = getArrayBufferOrView(keyData, 'keyData'); switch (name) { case 'Ed25519': @@ -337,8 +335,6 @@ function eddsaSignVerify(key, data, { name, context }, signature) { throw lazyDOMException(`Key must be a ${type} key`, 'InvalidAccessError'); if (name === 'Ed448' && context !== undefined) { - context = - getArrayBufferOrView(context, 'algorithm.context'); if (context.byteLength !== 0) { throw lazyDOMException( 'Non zero-length context is not yet supported.', 'NotSupportedError'); diff --git a/lib/internal/crypto/diffiehellman.js b/lib/internal/crypto/diffiehellman.js index 391832fde46bc2..2ca2f46330414e 100644 --- a/lib/internal/crypto/diffiehellman.js +++ b/lib/internal/crypto/diffiehellman.js @@ -48,7 +48,6 @@ const { const { KeyObject, - isCryptoKey, } = require('internal/crypto/keys'); const { @@ -324,8 +323,6 @@ async function ecdhDeriveBits(algorithm, baseKey, length) { // give us everything that is generated. if (length !== null) validateUint32(length, 'length'); - if (!isCryptoKey(key)) - throw new ERR_INVALID_ARG_TYPE('algorithm.public', 'CryptoKey', key); if (key.type !== 'public') { throw lazyDOMException( diff --git a/lib/internal/crypto/ec.js b/lib/internal/crypto/ec.js index ef9e4f26b7759f..642b857a1a1655 100644 --- a/lib/internal/crypto/ec.js +++ b/lib/internal/crypto/ec.js @@ -24,7 +24,6 @@ const { } = require('internal/errors'); const { - getArrayBufferOrView, getUsagesUnion, hasAnyNotIn, jobPromise, @@ -76,7 +75,6 @@ function verifyAcceptableEcKeyUse(name, isPublic, usages) { function createECPublicKeyRaw(namedCurve, keyData) { const handle = new KeyObjectHandle(); - keyData = getArrayBufferOrView(keyData, 'keyData'); if (!handle.initECRaw(kNamedCurveAliases[namedCurve], keyData)) { throw lazyDOMException('Invalid keyData', 'DataError'); diff --git a/lib/internal/crypto/hash.js b/lib/internal/crypto/hash.js index 72d127b37cbeb6..c7eeed192afacb 100644 --- a/lib/internal/crypto/hash.js +++ b/lib/internal/crypto/hash.js @@ -14,11 +14,9 @@ const { } = internalBinding('crypto'); const { - getArrayBufferOrView, getDefaultEncoding, getStringOption, jobPromise, - normalizeAlgorithm, normalizeHashName, validateMaxBufferLength, kHandle, @@ -168,13 +166,8 @@ Hmac.prototype._transform = Hash.prototype._transform; // Implementation for WebCrypto subtle.digest() async function asyncDigest(algorithm, data) { - algorithm = normalizeAlgorithm(algorithm); - data = getArrayBufferOrView(data, 'data'); validateMaxBufferLength(data, 'data'); - if (algorithm.length !== undefined) - validateUint32(algorithm.length, 'algorithm.length'); - switch (algorithm.name) { case 'SHA-1': // Fall through @@ -186,8 +179,7 @@ async function asyncDigest(algorithm, data) { return jobPromise(() => new HashJob( kCryptoJobAsync, normalizeHashName(algorithm.name), - data, - algorithm.length)); + data)); } throw lazyDOMException('Unrecognized name.', 'NotSupportedError'); diff --git a/lib/internal/crypto/hkdf.js b/lib/internal/crypto/hkdf.js index ec676b3d666d1d..7f0fe5534ee843 100644 --- a/lib/internal/crypto/hkdf.js +++ b/lib/internal/crypto/hkdf.js @@ -19,7 +19,6 @@ const { const { kMaxLength } = require('buffer'); const { - getArrayBufferOrView, normalizeHashName, toBuf, validateByteSource, @@ -45,7 +44,6 @@ const { codes: { ERR_INVALID_ARG_TYPE, ERR_OUT_OF_RANGE, - ERR_MISSING_OPTION, }, hideStackFrames, } = require('internal/errors'); @@ -140,11 +138,7 @@ function hkdfSync(hash, key, salt, info, length) { const hkdfPromise = promisify(hkdf); async function hkdfDeriveBits(algorithm, baseKey, length) { - const { hash } = algorithm; - const salt = getArrayBufferOrView(algorithm.salt, 'algorithm.salt'); - const info = getArrayBufferOrView(algorithm.info, 'algorithm.info'); - if (hash === undefined) - throw new ERR_MISSING_OPTION('algorithm.hash'); + const { hash, salt, info } = algorithm; if (length === 0) throw lazyDOMException('length cannot be zero', 'OperationError'); diff --git a/lib/internal/crypto/pbkdf2.js b/lib/internal/crypto/pbkdf2.js index 0ebe865776eca7..a46d5120236fba 100644 --- a/lib/internal/crypto/pbkdf2.js +++ b/lib/internal/crypto/pbkdf2.js @@ -15,12 +15,9 @@ const { const { validateFunction, validateInt32, - validateInteger, validateString, } = require('internal/validators'); -const { ERR_MISSING_OPTION } = require('internal/errors').codes; - const { getArrayBufferOrView, getDefaultEncoding, @@ -101,19 +98,12 @@ function check(password, salt, iterations, keylen, digest) { const pbkdf2Promise = promisify(pbkdf2); async function pbkdf2DeriveBits(algorithm, baseKey, length) { - const { iterations } = algorithm; - let { hash } = algorithm; - const salt = getArrayBufferOrView(algorithm.salt, 'algorithm.salt'); - if (hash === undefined) - throw new ERR_MISSING_OPTION('algorithm.hash'); - validateInteger(iterations, 'algorithm.iterations'); + const { iterations, hash, salt } = algorithm; if (iterations === 0) throw lazyDOMException( 'iterations cannot be zero', 'OperationError'); - hash = normalizeHashName(hash.name); - const raw = baseKey[kKeyObject].export(); if (length === 0) @@ -128,7 +118,9 @@ async function pbkdf2DeriveBits(algorithm, baseKey, length) { let result; try { - result = await pbkdf2Promise(raw, salt, iterations, length / 8, hash); + result = await pbkdf2Promise( + raw, salt, iterations, length / 8, normalizeHashName(hash.name), + ); } catch (err) { throw lazyDOMException( 'The operation failed for an operation-specific reason', diff --git a/lib/internal/crypto/rsa.js b/lib/internal/crypto/rsa.js index 5776d06fd18699..b8668b0c30255e 100644 --- a/lib/internal/crypto/rsa.js +++ b/lib/internal/crypto/rsa.js @@ -35,7 +35,6 @@ const { const { bigIntArrayToUnsignedInt, - getArrayBufferOrView, getUsagesUnion, hasAnyNotIn, jobPromise, @@ -104,7 +103,6 @@ function rsaOaepCipher(mode, key, data, { label }) { 'InvalidAccessError'); } if (label !== undefined) { - label = getArrayBufferOrView(label, 'algorithm.label'); validateMaxBufferLength(label, 'algorithm.label'); } diff --git a/lib/internal/crypto/util.js b/lib/internal/crypto/util.js index d7e6356486221e..2ac7c061ba8c15 100644 --- a/lib/internal/crypto/util.js +++ b/lib/internal/crypto/util.js @@ -1,15 +1,18 @@ 'use strict'; const { + ArrayBufferIsView, ArrayPrototypeIncludes, ArrayPrototypePush, BigInt, FunctionPrototypeBind, Number, - ObjectKeys, Promise, + TypedArrayPrototypeSlice, + ObjectPrototypeHasOwnProperty, StringPrototypeToLowerCase, Symbol, + Uint8Array, } = primordials; const { @@ -138,40 +141,6 @@ const kNamedCurveAliases = { const kAesKeyLengths = [128, 192, 256]; -// These are the only algorithms we currently support -// via the Web Crypto API -const kAlgorithms = { - 'rsassa-pkcs1-v1_5': 'RSASSA-PKCS1-v1_5', - 'rsa-pss': 'RSA-PSS', - 'rsa-oaep': 'RSA-OAEP', - 'ecdsa': 'ECDSA', - 'ecdh': 'ECDH', - 'aes-ctr': 'AES-CTR', - 'aes-cbc': 'AES-CBC', - 'aes-gcm': 'AES-GCM', - 'aes-kw': 'AES-KW', - 'hmac': 'HMAC', - 'sha-1': 'SHA-1', - 'sha-256': 'SHA-256', - 'sha-384': 'SHA-384', - 'sha-512': 'SHA-512', - 'hkdf': 'HKDF', - 'pbkdf2': 'PBKDF2', - 'ed25519': 'Ed25519', - 'ed448': 'Ed448', - 'x25519': 'X25519', - 'x448': 'X448', -}; -const kAlgorithmsKeys = ObjectKeys(kAlgorithms); - -// These are the only export and import formats we currently -// support via the Web Crypto API -const kExportFormats = [ - 'raw', - 'pkcs8', - 'spki', - 'jwk']; - // These are the only hash algorithms we currently support via // the Web Crypto API. const kHashTypes = [ @@ -181,6 +150,119 @@ const kHashTypes = [ 'SHA-512', ]; +const kSupportedAlgorithms = { + 'digest': { + 'SHA-1': null, + 'SHA-256': null, + 'SHA-384': null, + 'SHA-512': null, + }, + 'generateKey': { + 'RSASSA-PKCS1-v1_5': 'RsaHashedKeyGenParams', + 'RSA-PSS': 'RsaHashedKeyGenParams', + 'RSA-OAEP': 'RsaHashedKeyGenParams', + 'ECDSA': 'EcKeyGenParams', + 'ECDH': 'EcKeyGenParams', + 'AES-CTR': 'AesKeyGenParams', + 'AES-CBC': 'AesKeyGenParams', + 'AES-GCM': 'AesKeyGenParams', + 'AES-KW': 'AesKeyGenParams', + 'HMAC': 'HmacKeyGenParams', + 'X25519': null, + 'Ed25519': null, + 'X448': null, + 'Ed448': null, + }, + 'sign': { + 'RSASSA-PKCS1-v1_5': null, + 'RSA-PSS': 'RsaPssParams', + 'ECDSA': 'EcdsaParams', + 'HMAC': null, + 'Ed25519': null, + 'Ed448': 'Ed448Params', + }, + 'verify': { + 'RSASSA-PKCS1-v1_5': null, + 'RSA-PSS': 'RsaPssParams', + 'ECDSA': 'EcdsaParams', + 'HMAC': null, + 'Ed25519': null, + 'Ed448': 'Ed448Params', + }, + 'importKey': { + 'RSASSA-PKCS1-v1_5': 'RsaHashedImportParams', + 'RSA-PSS': 'RsaHashedImportParams', + 'RSA-OAEP': 'RsaHashedImportParams', + 'ECDSA': 'EcKeyImportParams', + 'ECDH': 'EcKeyImportParams', + 'HMAC': 'HmacImportParams', + 'HKDF': null, + 'PBKDF2': null, + 'AES-CTR': null, + 'AES-CBC': null, + 'AES-GCM': null, + 'AES-KW': null, + 'Ed25519': null, + 'X25519': null, + 'Ed448': null, + 'X448': null, + }, + 'deriveBits': { + 'HKDF': 'HkdfParams', + 'PBKDF2': 'Pbkdf2Params', + 'ECDH': 'EcdhKeyDeriveParams', + 'X25519': 'EcdhKeyDeriveParams', + 'X448': 'EcdhKeyDeriveParams', + }, + 'encrypt': { + 'RSA-OAEP': 'RsaOaepParams', + 'AES-CBC': 'AesCbcParams', + 'AES-GCM': 'AesGcmParams', + 'AES-CTR': 'AesCtrParams', + }, + 'decrypt': { + 'RSA-OAEP': 'RsaOaepParams', + 'AES-CBC': 'AesCbcParams', + 'AES-GCM': 'AesGcmParams', + 'AES-CTR': 'AesCtrParams', + }, + 'get key length': { + 'AES-CBC': 'AesDerivedKeyParams', + 'AES-CTR': 'AesDerivedKeyParams', + 'AES-GCM': 'AesDerivedKeyParams', + 'AES-KW': 'AesDerivedKeyParams', + 'HMAC': 'HmacImportParams', + 'HKDF': null, + 'PBKDF2': null, + }, + 'wrapKey': { + 'AES-KW': null, + }, + 'unwrapKey': { + 'AES-KW': null, + }, +}; + +const simpleAlgorithmDictionaries = { + AesGcmParams: { iv: 'BufferSource', additionalData: 'BufferSource' }, + RsaHashedKeyGenParams: { hash: 'HashAlgorithmIdentifier' }, + EcKeyGenParams: {}, + HmacKeyGenParams: { hash: 'HashAlgorithmIdentifier' }, + RsaPssParams: {}, + EcdsaParams: { hash: 'HashAlgorithmIdentifier' }, + HmacImportParams: { hash: 'HashAlgorithmIdentifier' }, + HkdfParams: { + hash: 'HashAlgorithmIdentifier', + salt: 'BufferSource', + info: 'BufferSource', + }, + Ed448Params: { context: 'BufferSource' }, + Pbkdf2Params: { hash: 'HashAlgorithmIdentifier', salt: 'BufferSource' }, + RsaOaepParams: { label: 'BufferSource' }, + RsaHashedImportParams: { hash: 'HashAlgorithmIdentifier' }, + EcKeyImportParams: {}, +}; + function validateMaxBufferLength(data, name) { if (data.byteLength > kMaxBufferLength) { throw lazyDOMException( @@ -189,36 +271,76 @@ function validateMaxBufferLength(data, name) { } } -function normalizeAlgorithm(algorithm) { - if (algorithm != null) { - if (typeof algorithm === 'string') - algorithm = { name: algorithm }; - - if (typeof algorithm === 'object') { - const { name } = algorithm; - if (typeof name !== 'string' || - !ArrayPrototypeIncludes( - kAlgorithmsKeys, - StringPrototypeToLowerCase(name))) { - throw lazyDOMException('Unrecognized name.', 'NotSupportedError'); - } - let { hash } = algorithm; - if (hash !== undefined) { - hash = normalizeAlgorithm(hash); - if (!ArrayPrototypeIncludes(kHashTypes, hash.name)) - throw lazyDOMException('Unrecognized name.', 'NotSupportedError'); - } - const normalized = { - ...algorithm, - name: kAlgorithms[StringPrototypeToLowerCase(name)], - }; - if (hash) { - normalized.hash = hash; - } - return normalized; +function normalizeAlgorithm(algorithm, op) { + if (typeof algorithm === 'string') + return normalizeAlgorithm({ name: algorithm }, op); + + const webidl = require('internal/crypto/webidl'); + + // 1. + const registeredAlgorithms = kSupportedAlgorithms[op]; + // 2. 3. + const initialAlg = webidl.converters.Algorithm(algorithm, { + prefix: 'Failed to normalize algorithm', + context: 'passed algorithm', + }); + // 4. + let algName = initialAlg.name; + + // 5. + let desiredType; + for (const key in registeredAlgorithms) { + if (!ObjectPrototypeHasOwnProperty(registeredAlgorithms, key)) { + continue; + } + if ( + StringPrototypeToLowerCase(key) === StringPrototypeToLowerCase(algName) + ) { + algName = key; + desiredType = registeredAlgorithms[key]; } } - throw lazyDOMException('Unrecognized name.', 'NotSupportedError'); + if (desiredType === undefined) + throw lazyDOMException('Unrecognized name.', 'NotSupportedError'); + + // Fast path everything below if the registered dictionary is null + if (desiredType === null) + return { name: algName }; + + // 6. + const normalizedAlgorithm = webidl.converters[desiredType](algorithm, { + prefix: 'Failed to normalize algorithm', + context: 'passed algorithm', + }); + // 7. + normalizedAlgorithm.name = algName; + + // 9. + const dict = simpleAlgorithmDictionaries[desiredType]; + // 10. + for (const member in dict) { + if (!ObjectPrototypeHasOwnProperty(dict, member)) + continue; + const idlType = dict[member]; + const idlValue = normalizedAlgorithm[member]; + // 3. + if (idlType === 'BufferSource' && idlValue) { + normalizedAlgorithm[member] = TypedArrayPrototypeSlice( + new Uint8Array( + ArrayBufferIsView(idlValue) ? idlValue.buffer : idlValue, + idlValue.byteOffset ?? 0, + idlValue.byteLength, + ), + ); + } else if (idlType === 'HashAlgorithmIdentifier') { + normalizedAlgorithm[member] = normalizeAlgorithm(idlValue, 'digest'); + } else if (idlType === 'AlgorithmIdentifier') { + // This extension point is not used by any supported algorithm (yet?) + throw lazyDOMException('Not implemented.', 'NotSupportedError'); + } + } + + return normalizedAlgorithm; } function hasAnyNotIn(set, checks) { @@ -413,7 +535,6 @@ module.exports = { kHashTypes, kNamedCurveAliases, kAesKeyLengths, - kExportFormats, normalizeAlgorithm, normalizeHashName, hasAnyNotIn, diff --git a/lib/internal/crypto/webcrypto.js b/lib/internal/crypto/webcrypto.js index 1360fc6d757bdf..72dded86ed526d 100644 --- a/lib/internal/crypto/webcrypto.js +++ b/lib/internal/crypto/webcrypto.js @@ -21,14 +21,6 @@ const { kWebCryptoCipherDecrypt, } = internalBinding('crypto'); -const { - validateArray, - validateBoolean, - validateObject, - validateOneOf, - validateString, -} = require('internal/validators'); - const { getOptionValue, } = require('internal/options'); @@ -38,7 +30,6 @@ const { TextDecoder, TextEncoder } = require('internal/encoding'); const { codes: { ERR_ILLEGAL_CONSTRUCTOR, - ERR_INVALID_ARG_TYPE, ERR_INVALID_THIS, } } = require('internal/errors'); @@ -47,7 +38,6 @@ const { CryptoKey, InternalCryptoKey, createSecretKey, - isCryptoKey, } = require('internal/crypto/keys'); const { @@ -55,13 +45,11 @@ const { } = require('internal/crypto/hash'); const { - getArrayBufferOrView, getBlockSize, hasAnyNotIn, normalizeAlgorithm, normalizeHashName, validateMaxBufferLength, - kExportFormats, kHandle, kKeyObject, } = require('internal/crypto/util'); @@ -78,7 +66,22 @@ const { async function digest(algorithm, data) { if (this !== subtle) throw new ERR_INVALID_THIS('SubtleCrypto'); - return ReflectApply(asyncDigest, this, arguments); + + const webidl = require('internal/crypto/webidl'); + const prefix = "Failed to execute 'digest' on 'SubtleCrypto'"; + webidl.requiredArguments(arguments.length, 2, { prefix }); + algorithm = webidl.converters.AlgorithmIdentifier(algorithm, { + prefix, + context: 'Argument 1', + }); + data = webidl.converters.BufferSource(data, { + prefix, + context: 'Argument 2', + }); + + algorithm = normalizeAlgorithm(algorithm, 'digest'); + + return ReflectApply(asyncDigest, this, [algorithm, data]); } function randomUUID() { @@ -91,9 +94,24 @@ async function generateKey( extractable, keyUsages) { if (this !== subtle) throw new ERR_INVALID_THIS('SubtleCrypto'); - algorithm = normalizeAlgorithm(algorithm); - validateBoolean(extractable, 'extractable'); - validateArray(keyUsages, 'keyUsages'); + + const webidl = require('internal/crypto/webidl'); + const prefix = "Failed to execute 'generateKey' on 'SubtleCrypto'"; + webidl.requiredArguments(arguments.length, 3, { prefix }); + algorithm = webidl.converters.AlgorithmIdentifier(algorithm, { + prefix, + context: 'Argument 1', + }); + extractable = webidl.converters.boolean(extractable, { + prefix, + context: 'Argument 2', + }); + keyUsages = webidl.converters['sequence'](keyUsages, { + prefix, + context: 'Argument 3', + }); + + algorithm = normalizeAlgorithm(algorithm, 'generateKey'); let result; let resultType; switch (algorithm.name) { @@ -160,9 +178,26 @@ async function generateKey( async function deriveBits(algorithm, baseKey, length) { if (this !== subtle) throw new ERR_INVALID_THIS('SubtleCrypto'); - algorithm = normalizeAlgorithm(algorithm); - if (!isCryptoKey(baseKey)) - throw new ERR_INVALID_ARG_TYPE('baseKey', 'CryptoKey', baseKey); + + const webidl = require('internal/crypto/webidl'); + const prefix = "Failed to execute 'deriveBits' on 'SubtleCrypto'"; + webidl.requiredArguments(arguments.length, 3, { prefix }); + algorithm = webidl.converters.AlgorithmIdentifier(algorithm, { + prefix, + context: 'Argument 1', + }); + baseKey = webidl.converters.CryptoKey(baseKey, { + prefix, + context: 'Argument 2', + }); + if (length !== null) { + length = webidl.converters['unsigned long'](length, { + prefix, + context: 'Argument 3', + }); + } + + algorithm = normalizeAlgorithm(algorithm, 'deriveBits'); if (!ArrayPrototypeIncludes(baseKey.usages, 'deriveBits')) { throw lazyDOMException( 'baseKey does not have deriveBits usage', @@ -221,10 +256,33 @@ async function deriveKey( extractable, keyUsages) { if (this !== subtle) throw new ERR_INVALID_THIS('SubtleCrypto'); - algorithm = normalizeAlgorithm(algorithm); - derivedKeyAlgorithm = normalizeAlgorithm(derivedKeyAlgorithm); - if (!isCryptoKey(baseKey)) - throw new ERR_INVALID_ARG_TYPE('baseKey', 'CryptoKey', baseKey); + + const webidl = require('internal/crypto/webidl'); + const prefix = "Failed to execute 'deriveKey' on 'SubtleCrypto'"; + webidl.requiredArguments(arguments.length, 5, { prefix }); + algorithm = webidl.converters.AlgorithmIdentifier(algorithm, { + prefix, + context: 'Argument 1', + }); + baseKey = webidl.converters.CryptoKey(baseKey, { + prefix, + context: 'Argument 2', + }); + derivedKeyAlgorithm = webidl.converters.AlgorithmIdentifier(derivedKeyAlgorithm, { + prefix, + context: 'Argument 3', + }); + extractable = webidl.converters.boolean(extractable, { + prefix, + context: 'Argument 4', + }); + keyUsages = webidl.converters['sequence'](keyUsages, { + prefix, + context: 'Argument 5', + }); + + algorithm = normalizeAlgorithm(algorithm, 'deriveBits'); + derivedKeyAlgorithm = normalizeAlgorithm(derivedKeyAlgorithm, 'importKey'); if (!ArrayPrototypeIncludes(baseKey.usages, 'deriveKey')) { throw lazyDOMException( 'baseKey does not have deriveKey usage', @@ -232,13 +290,8 @@ async function deriveKey( } if (baseKey.algorithm.name !== algorithm.name) throw lazyDOMException('Key algorithm mismatch', 'InvalidAccessError'); - validateObject(derivedKeyAlgorithm, 'derivedKeyAlgorithm', { - allowArray: true, allowFunction: true, - }); - validateBoolean(extractable, 'extractable'); - validateArray(keyUsages, 'keyUsages'); - const length = getKeyLength(derivedKeyAlgorithm); + const length = getKeyLength(normalizeAlgorithm(arguments[2], 'get key length')); let bits; switch (algorithm.name) { case 'X25519': @@ -446,10 +499,18 @@ async function exportKeyJWK(key) { async function exportKey(format, key) { if (this !== subtle) throw new ERR_INVALID_THIS('SubtleCrypto'); - validateString(format, 'format'); - validateOneOf(format, 'format', kExportFormats); - if (!isCryptoKey(key)) - throw new ERR_INVALID_ARG_TYPE('key', 'CryptoKey', key); + + const webidl = require('internal/crypto/webidl'); + const prefix = "Failed to execute 'exportKey' on 'SubtleCrypto'"; + webidl.requiredArguments(arguments.length, 2, { prefix }); + format = webidl.converters.KeyFormat(format, { + prefix, + context: 'Argument 1', + }); + key = webidl.converters.CryptoKey(key, { + prefix, + context: 'Argument 2', + }); if (!key.extractable) throw lazyDOMException('key is not extractable', 'InvalidAccessException'); @@ -515,13 +576,32 @@ async function importKey( extractable, keyUsages) { if (this !== subtle) throw new ERR_INVALID_THIS('SubtleCrypto'); - validateString(format, 'format'); - validateOneOf(format, 'format', kExportFormats); - if (format !== 'jwk') - keyData = getArrayBufferOrView(keyData, 'keyData'); - algorithm = normalizeAlgorithm(algorithm); - validateBoolean(extractable, 'extractable'); - validateArray(keyUsages, 'keyUsages'); + + const webidl = require('internal/crypto/webidl'); + const prefix = "Failed to execute 'importKey' on 'SubtleCrypto'"; + webidl.requiredArguments(arguments.length, 4, { prefix }); + format = webidl.converters.KeyFormat(format, { + prefix, + context: 'Argument 1', + }); + keyData = webidl.converters['BufferSource or JsonWebKey'](keyData, { + prefix, + context: 'Argument 2', + }); + algorithm = webidl.converters.AlgorithmIdentifier(algorithm, { + prefix, + context: 'Argument 3', + }); + extractable = webidl.converters.boolean(extractable, { + prefix, + context: 'Argument 4', + }); + keyUsages = webidl.converters['sequence'](keyUsages, { + prefix, + context: 'Argument 5', + }); + + algorithm = normalizeAlgorithm(algorithm, 'importKey'); switch (algorithm.name) { case 'RSASSA-PKCS1-v1_5': // Fall through @@ -574,7 +654,32 @@ async function importKey( // by a subtle.encrypt(). async function wrapKey(format, key, wrappingKey, algorithm) { if (this !== subtle) throw new ERR_INVALID_THIS('SubtleCrypto'); - algorithm = normalizeAlgorithm(algorithm); + + const webidl = require('internal/crypto/webidl'); + const prefix = "Failed to execute 'wrapKey' on 'SubtleCrypto'"; + webidl.requiredArguments(arguments.length, 4, { prefix }); + format = webidl.converters.KeyFormat(format, { + prefix, + context: 'Argument 1', + }); + key = webidl.converters.CryptoKey(key, { + prefix, + context: 'Argument 2', + }); + wrappingKey = webidl.converters.CryptoKey(wrappingKey, { + prefix, + context: 'Argument 3', + }); + algorithm = webidl.converters.AlgorithmIdentifier(algorithm, { + prefix, + context: 'Argument 4', + }); + + try { + algorithm = normalizeAlgorithm(algorithm, 'wrapKey'); + } catch { + algorithm = normalizeAlgorithm(algorithm, 'encrypt'); + } let keyData = await ReflectApply(exportKey, this, [format, key]); if (format === 'jwk') { @@ -604,8 +709,48 @@ async function unwrapKey( extractable, keyUsages) { if (this !== subtle) throw new ERR_INVALID_THIS('SubtleCrypto'); - wrappedKey = getArrayBufferOrView(wrappedKey, 'wrappedKey'); - unwrapAlgo = normalizeAlgorithm(unwrapAlgo); + + const webidl = require('internal/crypto/webidl'); + const prefix = "Failed to execute 'unwrapKey' on 'SubtleCrypto'"; + webidl.requiredArguments(arguments.length, 7, { prefix }); + format = webidl.converters.KeyFormat(format, { + prefix, + context: 'Argument 1', + }); + wrappedKey = webidl.converters.BufferSource(wrappedKey, { + prefix, + context: 'Argument 2', + }); + unwrappingKey = webidl.converters.CryptoKey(unwrappingKey, { + prefix, + context: 'Argument 3', + }); + unwrapAlgo = webidl.converters.AlgorithmIdentifier(unwrapAlgo, { + prefix, + context: 'Argument 4', + }); + unwrappedKeyAlgo = webidl.converters.AlgorithmIdentifier( + unwrappedKeyAlgo, + { + prefix, + context: 'Argument 5', + }, + ); + extractable = webidl.converters.boolean(extractable, { + prefix, + context: 'Argument 6', + }); + keyUsages = webidl.converters['sequence'](keyUsages, { + prefix, + context: 'Argument 7', + }); + + try { + unwrapAlgo = normalizeAlgorithm(unwrapAlgo, 'unwrapKey'); + } catch { + unwrapAlgo = normalizeAlgorithm(unwrapAlgo, 'decrypt'); + } + let keyData = await cipherOrWrap( kWebCryptoCipherDecrypt, unwrapAlgo, @@ -633,15 +778,11 @@ async function unwrapKey( } function signVerify(algorithm, key, data, signature) { - algorithm = normalizeAlgorithm(algorithm); - if (!isCryptoKey(key)) - throw new ERR_INVALID_ARG_TYPE('key', 'CryptoKey', key); - data = getArrayBufferOrView(data, 'data'); let usage = 'sign'; if (signature !== undefined) { - signature = getArrayBufferOrView(signature, 'signature'); usage = 'verify'; } + algorithm = normalizeAlgorithm(algorithm, usage); if (!ArrayPrototypeIncludes(key.usages, usage) || algorithm.name !== key.algorithm.name) { @@ -674,21 +815,56 @@ function signVerify(algorithm, key, data, signature) { async function sign(algorithm, key, data) { if (this !== subtle) throw new ERR_INVALID_THIS('SubtleCrypto'); + + const webidl = require('internal/crypto/webidl'); + const prefix = "Failed to execute 'sign' on 'SubtleCrypto'"; + webidl.requiredArguments(arguments.length, 3, { prefix }); + algorithm = webidl.converters.AlgorithmIdentifier(algorithm, { + prefix, + context: 'Argument 1', + }); + key = webidl.converters.CryptoKey(key, { + prefix, + context: 'Argument 2', + }); + data = webidl.converters.BufferSource(data, { + prefix, + context: 'Argument 3', + }); + return signVerify(algorithm, key, data); } async function verify(algorithm, key, signature, data) { if (this !== subtle) throw new ERR_INVALID_THIS('SubtleCrypto'); + + const webidl = require('internal/crypto/webidl'); + const prefix = "Failed to execute 'verify' on 'SubtleCrypto'"; + webidl.requiredArguments(arguments.length, 4, { prefix }); + algorithm = webidl.converters.AlgorithmIdentifier(algorithm, { + prefix, + context: 'Argument 1', + }); + key = webidl.converters.CryptoKey(key, { + prefix, + context: 'Argument 2', + }); + signature = webidl.converters.BufferSource(signature, { + prefix, + context: 'Argument 3', + }); + data = webidl.converters.BufferSource(data, { + prefix, + context: 'Argument 4', + }); + return signVerify(algorithm, key, data, signature); } async function cipherOrWrap(mode, algorithm, key, data, op) { - algorithm = normalizeAlgorithm(algorithm); // We use a Node.js style error here instead of a DOMException because // the WebCrypto spec is not specific what kind of error is to be thrown // in this case. Both Firefox and Chrome throw simple TypeErrors here. - if (!isCryptoKey(key)) - throw new ERR_INVALID_ARG_TYPE('key', 'CryptoKey', key); // The key algorithm and cipher algorithm must match, and the // key must have the proper usage. if (key.algorithm.name !== algorithm.name || @@ -698,10 +874,6 @@ async function cipherOrWrap(mode, algorithm, key, data, op) { 'InvalidAccessError'); } - // For the Web Crypto API, the input data can be any ArrayBuffer, - // TypedArray, or DataView. - data = getArrayBufferOrView(data, 'data'); - // While WebCrypto allows for larger input buffer sizes, we limit // those to sizes that can fit within uint32_t because of limitations // in the OpenSSL API. @@ -729,11 +901,47 @@ async function cipherOrWrap(mode, algorithm, key, data, op) { async function encrypt(algorithm, key, data) { if (this !== subtle) throw new ERR_INVALID_THIS('SubtleCrypto'); + + const webidl = require('internal/crypto/webidl'); + const prefix = "Failed to execute 'encrypt' on 'SubtleCrypto'"; + webidl.requiredArguments(arguments.length, 3, { prefix }); + algorithm = webidl.converters.AlgorithmIdentifier(algorithm, { + prefix, + context: 'Argument 1', + }); + key = webidl.converters.CryptoKey(key, { + prefix, + context: 'Argument 2', + }); + data = webidl.converters.BufferSource(data, { + prefix, + context: 'Argument 3', + }); + + algorithm = normalizeAlgorithm(algorithm, 'encrypt'); return cipherOrWrap(kWebCryptoCipherEncrypt, algorithm, key, data, 'encrypt'); } async function decrypt(algorithm, key, data) { if (this !== subtle) throw new ERR_INVALID_THIS('SubtleCrypto'); + + const webidl = require('internal/crypto/webidl'); + const prefix = "Failed to execute 'decrypt' on 'SubtleCrypto'"; + webidl.requiredArguments(arguments.length, 3, { prefix }); + algorithm = webidl.converters.AlgorithmIdentifier(algorithm, { + prefix, + context: 'Argument 1', + }); + key = webidl.converters.CryptoKey(key, { + prefix, + context: 'Argument 2', + }); + data = webidl.converters.BufferSource(data, { + prefix, + context: 'Argument 3', + }); + + algorithm = normalizeAlgorithm(algorithm, 'decrypt'); return cipherOrWrap(kWebCryptoCipherDecrypt, algorithm, key, data, 'decrypt'); } diff --git a/lib/internal/crypto/webidl.js b/lib/internal/crypto/webidl.js new file mode 100644 index 00000000000000..93cf9ffa32b1b9 --- /dev/null +++ b/lib/internal/crypto/webidl.js @@ -0,0 +1,795 @@ +'use strict'; + +// Adapted from +// - https://github.com/jsdom/webidl-conversions +// Copyright Domenic Denicola. Licensed under BSD-2-Clause License. +// Original license at https://github.com/jsdom/webidl-conversions/blob/master/LICENSE.md. +// - https://github.com/denoland/deno +// Copyright Deno authors. Licensed under MIT License. +// Original license at https://github.com/denoland/deno/blob/main/LICENSE.md. + +const { + ArrayBufferIsView, + ArrayBufferPrototype, + ArrayPrototypePush, + ArrayPrototypeSort, + globalThis, + MathPow, + MathTrunc, + Number, + NumberIsFinite, + NumberMAX_SAFE_INTEGER, + ObjectAssign, + ObjectDefineProperty, + ObjectGetOwnPropertyDescriptor, + ObjectGetPrototypeOf, + ObjectPrototypeIsPrototypeOf, + ReflectHas, + SafeArrayIterator, + Set, + String, + SymbolIterator, + SymbolToStringTag, + TypeError, + Uint8Array, +} = primordials; + +const { kEmptyObject } = require('internal/util'); + +const { CryptoKey } = require('internal/crypto/webcrypto'); + +const { SharedArrayBuffer } = globalThis; + +function codedTypeError(message, errorProperties = kEmptyObject) { + // eslint-disable-next-line no-restricted-syntax + const err = new TypeError(message); + ObjectAssign(err, errorProperties); + return err; +} + +function makeException(message, opts = kEmptyObject) { + return codedTypeError( + `${opts.prefix ? opts.prefix + ': ' : ''}${ + opts.context ? opts.context : 'Value' + } ${message}`, + opts.code ? { code: opts.code } : kEmptyObject, + ); +} + +function toNumber(value) { + if (typeof value === 'bigint') { + // eslint-disable-next-line no-restricted-syntax + throw new TypeError('Cannot convert a BigInt value to a number'); + } + return Number(value); +} + +function type(V) { + if (V === null) + return 'Null'; + + switch (typeof V) { + case 'undefined': + return 'Undefined'; + case 'boolean': + return 'Boolean'; + case 'number': + return 'Number'; + case 'string': + return 'String'; + case 'symbol': + return 'Symbol'; + case 'bigint': + return 'BigInt'; + case 'object': // Fall through + case 'function': // Fall through + default: + // Per ES spec, typeof returns an implemention-defined value that is not + // any of the existing ones for uncallable non-standard exotic objects. + // Yet Type() which the Web IDL spec depends on returns Object for such + // cases. So treat the default case as an object. + return 'Object'; + } +} + +function integerPart(n) { + return censorNegativeZero(MathTrunc(n)); +} + +function sign(x) { + return x < 0 ? -1 : 1; +} + +function modulo(x, y) { + // https://tc39.github.io/ecma262/#eqn-modulo + // Note that http://stackoverflow.com/a/4467559/3191 does NOT work for large modulos + const signMightNotMatch = x % y; + if (sign(y) !== sign(signMightNotMatch)) { + return signMightNotMatch + y; + } + return signMightNotMatch; +} + +function censorNegativeZero(x) { + return x === 0 ? 0 : x; +} + +function createIntegerConversion(bitLength) { + const isSigned = false; + + const lowerBound = 0; + let upperBound; + if (bitLength === 64) { + upperBound = NumberMAX_SAFE_INTEGER; + } else { + upperBound = MathPow(2, bitLength) - 1; + } + + const twoToTheBitLength = MathPow(2, bitLength); + const twoToOneLessThanTheBitLength = MathPow(2, bitLength - 1); + + return (V, opts = kEmptyObject) => { + let x = toNumber(V); + x = censorNegativeZero(x); + + if (opts.enforceRange) { + if (!NumberIsFinite(x)) { + throw makeException( + 'is not a finite number', + { ...opts, code: 'ERR_INVALID_ARG_TYPE' }); + } + + x = integerPart(x); + + if (x < lowerBound || x > upperBound) { + throw makeException( + `is outside the accepted range of ${lowerBound} to ${upperBound}, inclusive`, + { ...opts, code: 'ERR_OUT_OF_RANGE' }, + ); + } + + return x; + } + + if (!NumberIsFinite(x) || x === 0) { + return 0; + } + x = integerPart(x); + + // Math.pow(2, 64) is not accurately representable in JavaScript, so try to + // avoid these per-spec operations if possible. Hopefully it's an + // optimization for the non-64-bitLength cases too. + if (x >= lowerBound && x <= upperBound) { + return x; + } + + // These will not work great for bitLength of 64, but oh well. + x = modulo(x, twoToTheBitLength); + if (isSigned && x >= twoToOneLessThanTheBitLength) { + return x - twoToTheBitLength; + } + return x; + }; +} + +const converters = {}; + +converters.boolean = (val) => !!val; +converters['unsigned short'] = createIntegerConversion(16); +converters['unsigned long'] = createIntegerConversion(32); + +converters.DOMString = function(V, opts = kEmptyObject) { + if (typeof V === 'string') { + return V; + } else if (typeof V === 'symbol') { + throw makeException( + 'is a symbol, which cannot be converted to a string', + { ...opts, code: 'ERR_INVALID_ARG_TYPE' }); + } + + return String(V); +}; + +converters.object = (V, opts) => { + if (type(V) !== 'Object') { + throw makeException( + 'is not an object', + { ...opts, code: 'ERR_INVALID_ARG_TYPE' }); + } + + return V; +}; + +function isNonSharedArrayBuffer(V) { + return ObjectPrototypeIsPrototypeOf(ArrayBufferPrototype, V); +} + +function isSharedArrayBuffer(V) { + return ObjectPrototypeIsPrototypeOf(SharedArrayBuffer.prototype, V); +} + +// Returns the unforgeable `TypedArray` constructor name or `undefined`, +// if the `this` value isn't a valid `TypedArray` object. +// +// https://tc39.es/ecma262/#sec-get-%typedarray%.prototype-@@tostringtag +const typedArrayNameGetter = ObjectGetOwnPropertyDescriptor( + ObjectGetPrototypeOf(Uint8Array).prototype, + SymbolToStringTag +).get; +converters.Uint8Array = (V, opts = kEmptyObject) => { + if (!ArrayBufferIsView(V) || typedArrayNameGetter.call(V) !== 'Uint8Array') { + throw makeException( + 'is not an Uint8Array object', + { ...opts, code: 'ERR_INVALID_ARG_TYPE' }); + } + if (isSharedArrayBuffer(V.buffer)) { + throw makeException( + 'is a view on a SharedArrayBuffer, which is not allowed', + { ...opts, code: 'ERR_INVALID_ARG_TYPE' }); + } + + return V; +}; + +converters.BufferSource = (V, opts = kEmptyObject) => { + if (ArrayBufferIsView(V)) { + if (isSharedArrayBuffer(V.buffer)) { + throw makeException( + 'is a view on a SharedArrayBuffer, which is not allowed', + { ...opts, code: 'ERR_INVALID_ARG_TYPE' }); + } + + return V; + } + + if (!isNonSharedArrayBuffer(V)) { + throw makeException( + 'is not an ArrayBuffer or a view on one', + { ...opts, code: 'ERR_INVALID_ARG_TYPE' }); + } + + return V; +}; + +converters['sequence'] = createSequenceConverter( + converters.DOMString); + +function requiredArguments(length, required, opts = kEmptyObject) { + if (length < required) { + throw makeException( + `${required} argument${ + required === 1 ? '' : 's' + } required, but only ${length} present.`, + { ...opts, code: 'ERR_MISSING_ARGS' }); + } +} + +function createDictionaryConverter(name, ...dictionaries) { + let hasRequiredKey = false; + const allMembers = []; + for (const members of new SafeArrayIterator(dictionaries)) { + for (const member of new SafeArrayIterator(members)) { + if (member.required) { + hasRequiredKey = true; + } + ArrayPrototypePush(allMembers, member); + } + } + ArrayPrototypeSort(allMembers, (a, b) => { + if (a.key === b.key) { + return 0; + } + return a.key < b.key ? -1 : 1; + }); + + const defaultValues = {}; + for (const member of new SafeArrayIterator(allMembers)) { + if (ReflectHas(member, 'defaultValue')) { + const idlMemberValue = member.defaultValue; + const imvType = typeof idlMemberValue; + // Copy by value types can be directly assigned, copy by reference types + // need to be re-created for each allocation. + if ( + imvType === 'number' || + imvType === 'boolean' || + imvType === 'string' || + imvType === 'bigint' || + imvType === 'undefined' + ) { + defaultValues[member.key] = member.converter(idlMemberValue, {}); + } else { + ObjectDefineProperty(defaultValues, member.key, { + __proto__: null, + get() { + return member.converter(idlMemberValue, member.defaultValue); + }, + enumerable: true, + }); + } + } + } + + return function(V, opts = kEmptyObject) { + const typeV = type(V); + switch (typeV) { + case 'Undefined': + case 'Null': + case 'Object': + break; + default: + throw makeException( + 'can not be converted to a dictionary', + { ...opts, code: 'ERR_INVALID_ARG_TYPE' }); + } + const esDict = V; + + const idlDict = ObjectAssign({}, defaultValues); + + // NOTE: fast path Null and Undefined. + if ((V === undefined || V === null) && !hasRequiredKey) { + return idlDict; + } + + for (const member of new SafeArrayIterator(allMembers)) { + const key = member.key; + + let esMemberValue; + if (typeV === 'Undefined' || typeV === 'Null') { + esMemberValue = undefined; + } else { + esMemberValue = esDict[key]; + } + + if (esMemberValue !== undefined) { + const context = `'${key}' of '${name}'${ + opts.context ? ` (${opts.context})` : '' + }`; + const converter = member.converter; + const idlMemberValue = converter(esMemberValue, { ...opts, context }); + idlDict[key] = idlMemberValue; + } else if (member.required) { + throw makeException( + `can not be converted to '${name}' because '${key}' is required in '${name}'.`, + { ...opts, code: 'ERR_MISSING_OPTION' }); + } + } + + return idlDict; + }; +} + +// https://heycam.github.io/webidl/#es-enumeration +function createEnumConverter(name, values) { + const E = new Set(values); + + return function(V, opts = kEmptyObject) { + const S = String(V); + + if (!E.has(S)) { + throw makeException( + `The provided value '${S}' is not a valid enum value of type ${name}.`, + { ...opts, code: 'ERR_INVALID_ARG_VALUE' }); + } + + return S; + }; +} + +// https://heycam.github.io/webidl/#es-sequence +function createSequenceConverter(converter) { + return function(V, opts = kEmptyObject) { + if (type(V) !== 'Object') { + throw makeException( + 'can not be converted to sequence.', + { ...opts, code: 'ERR_INVALID_ARG_TYPE' }); + } + const iter = V?.[SymbolIterator]?.(); + if (iter === undefined) { + throw makeException( + 'can not be converted to sequence.', + { ...opts, code: 'ERR_INVALID_ARG_TYPE' }); + } + const array = []; + while (true) { + const res = iter?.next?.(); + if (res === undefined) { + throw makeException( + 'can not be converted to sequence.', + { ...opts, code: 'ERR_INVALID_ARG_TYPE' }); + } + if (res.done === true) break; + const val = converter(res.value, { + ...opts, + context: `${opts.context}, index ${array.length}`, + }); + ArrayPrototypePush(array, val); + } + return array; + }; +} + +function createInterfaceConverter(name, prototype) { + return (V, opts) => { + if (!ObjectPrototypeIsPrototypeOf(prototype, V)) { + throw makeException( + `is not of type ${name}.`, + { ...opts, code: 'ERR_INVALID_ARG_TYPE' }); + } + return V; + }; +} + +converters.AlgorithmIdentifier = (V, opts) => { + // Union for (object or DOMString) + if (type(V) === 'Object') { + return converters.object(V, opts); + } + return converters.DOMString(V, opts); +}; + +converters['BufferSource or JsonWebKey'] = (V, opts) => { + // Union for (BufferSource or JsonWebKey) + if ( + ArrayBufferIsView(V) || + ObjectPrototypeIsPrototypeOf(ArrayBufferPrototype, V) + ) { + return converters.BufferSource(V, opts); + } + return converters.JsonWebKey(V, opts); +}; + +converters.KeyType = createEnumConverter('KeyType', [ + 'public', + 'private', + 'secret', +]); + +converters.KeyFormat = createEnumConverter('KeyFormat', [ + 'raw', + 'pkcs8', + 'spki', + 'jwk', +]); + +converters.KeyUsage = createEnumConverter('KeyUsage', [ + 'encrypt', + 'decrypt', + 'sign', + 'verify', + 'deriveKey', + 'deriveBits', + 'wrapKey', + 'unwrapKey', +]); + +converters['sequence'] = createSequenceConverter(converters.KeyUsage); + +converters.HashAlgorithmIdentifier = converters.AlgorithmIdentifier; + +const dictAlgorithm = [ + { + key: 'name', + converter: converters.DOMString, + required: true, + }, +]; + +converters.Algorithm = createDictionaryConverter( + 'Algorithm', dictAlgorithm); + +converters.BigInteger = converters.Uint8Array; + +const dictRsaKeyGenParams = [ + ...new SafeArrayIterator(dictAlgorithm), + { + key: 'modulusLength', + converter: (V, opts) => + converters['unsigned long'](V, { ...opts, enforceRange: true }), + required: true, + }, + { + key: 'publicExponent', + converter: converters.BigInteger, + required: true, + }, +]; + +converters.RsaKeyGenParams = createDictionaryConverter( + 'RsaKeyGenParams', dictRsaKeyGenParams); + +converters.RsaHashedKeyGenParams = createDictionaryConverter( + 'RsaHashedKeyGenParams', [ + ...new SafeArrayIterator(dictRsaKeyGenParams), + { + key: 'hash', + converter: converters.HashAlgorithmIdentifier, + required: true, + }, + ]); + +converters.RsaHashedImportParams = createDictionaryConverter( + 'RsaHashedImportParams', [ + ...new SafeArrayIterator(dictAlgorithm), + { + key: 'hash', + converter: converters.HashAlgorithmIdentifier, + required: true, + }, + ]); + +converters.NamedCurve = converters.DOMString; + +converters.EcKeyImportParams = createDictionaryConverter( + 'EcKeyImportParams', [ + ...new SafeArrayIterator(dictAlgorithm), + { + key: 'namedCurve', + converter: converters.NamedCurve, + required: true, + }, + ]); + +converters.EcKeyGenParams = createDictionaryConverter( + 'EcKeyGenParams', [ + ...new SafeArrayIterator(dictAlgorithm), + { + key: 'namedCurve', + converter: converters.NamedCurve, + required: true, + }, + ]); + +converters.AesKeyGenParams = createDictionaryConverter( + 'AesKeyGenParams', [ + ...new SafeArrayIterator(dictAlgorithm), + { + key: 'length', + converter: (V, opts) => + converters['unsigned short'](V, { ...opts, enforceRange: true }), + required: true, + }, + ]); + +converters.HmacKeyGenParams = createDictionaryConverter( + 'HmacKeyGenParams', [ + ...new SafeArrayIterator(dictAlgorithm), + { + key: 'hash', + converter: converters.HashAlgorithmIdentifier, + required: true, + }, + { + key: 'length', + converter: (V, opts) => + converters['unsigned long'](V, { ...opts, enforceRange: true }), + }, + ]); + +converters.RsaPssParams = createDictionaryConverter( + 'RsaPssParams', [ + ...new SafeArrayIterator(dictAlgorithm), + { + key: 'saltLength', + converter: (V, opts) => + converters['unsigned long'](V, { ...opts, enforceRange: true }), + required: true, + }, + ]); + +converters.RsaOaepParams = createDictionaryConverter( + 'RsaOaepParams', [ + ...new SafeArrayIterator(dictAlgorithm), + { + key: 'label', + converter: converters.BufferSource, + }, + ]); + +converters.EcdsaParams = createDictionaryConverter( + 'EcdsaParams', [ + ...new SafeArrayIterator(dictAlgorithm), + { + key: 'hash', + converter: converters.HashAlgorithmIdentifier, + required: true, + }, + ]); + +converters.HmacImportParams = createDictionaryConverter( + 'HmacImportParams', [ + ...new SafeArrayIterator(dictAlgorithm), + { + key: 'hash', + converter: converters.HashAlgorithmIdentifier, + required: true, + }, + { + key: 'length', + converter: (V, opts) => + converters['unsigned long'](V, { ...opts, enforceRange: true }), + }, + ]); + +const simpleDomStringKey = (key) => ({ key, converter: converters.DOMString }); + +converters.RsaOtherPrimesInfo = createDictionaryConverter( + 'RsaOtherPrimesInfo', [ + simpleDomStringKey('r'), + simpleDomStringKey('d'), + simpleDomStringKey('t'), + ]); +converters['sequence'] = createSequenceConverter( + converters.RsaOtherPrimesInfo); + +converters.JsonWebKey = createDictionaryConverter( + 'JsonWebKey', [ + // Sections 4.2 and 4.3 of RFC7517. + // https://datatracker.ietf.org/doc/html/rfc7517#section-4 + simpleDomStringKey('kty'), + simpleDomStringKey('use'), + { + key: 'key_ops', + converter: converters['sequence'], + }, + simpleDomStringKey('alg'), + // JSON Web Key Parameters Registration + { + key: 'ext', + converter: converters.boolean, + }, + // Section 6 of RFC7518 JSON Web Algorithms + // https://datatracker.ietf.org/doc/html/rfc7518#section-6 + simpleDomStringKey('crv'), + simpleDomStringKey('x'), + simpleDomStringKey('y'), + simpleDomStringKey('d'), + simpleDomStringKey('n'), + simpleDomStringKey('e'), + simpleDomStringKey('p'), + simpleDomStringKey('q'), + simpleDomStringKey('dp'), + simpleDomStringKey('dq'), + simpleDomStringKey('qi'), + { + key: 'oth', + converter: converters['sequence'], + }, + simpleDomStringKey('k'), + ]); + +converters.HkdfParams = createDictionaryConverter( + 'HkdfParams', [ + ...new SafeArrayIterator(dictAlgorithm), + { + key: 'hash', + converter: converters.HashAlgorithmIdentifier, + required: true, + }, + { + key: 'salt', + converter: converters.BufferSource, + required: true, + }, + { + key: 'info', + converter: converters.BufferSource, + required: true, + }, + ]); + +converters.Pbkdf2Params = createDictionaryConverter( + 'Pbkdf2Params', [ + ...new SafeArrayIterator(dictAlgorithm), + { + key: 'hash', + converter: converters.HashAlgorithmIdentifier, + required: true, + }, + { + key: 'iterations', + converter: (V, opts) => + converters['unsigned long'](V, { ...opts, enforceRange: true }), + required: true, + }, + { + key: 'salt', + converter: converters.BufferSource, + required: true, + }, + ]); + +converters.AesDerivedKeyParams = createDictionaryConverter( + 'AesDerivedKeyParams', [ + ...new SafeArrayIterator(dictAlgorithm), + { + key: 'length', + converter: (V, opts) => + converters['unsigned long'](V, { ...opts, enforceRange: true }), + required: true, + }, + ]); + +converters.AesCbcParams = createDictionaryConverter( + 'AesCbcParams', [ + ...new SafeArrayIterator(dictAlgorithm), + { + key: 'iv', + converter: converters.BufferSource, + required: true, + }, + ]); + +converters.AesGcmParams = createDictionaryConverter( + 'AesGcmParams', [ + ...new SafeArrayIterator(dictAlgorithm), + { + key: 'iv', + converter: converters.BufferSource, + required: true, + }, + { + key: 'tagLength', + converter: (V, opts) => + converters['unsigned long'](V, { ...opts, enforceRange: true }), + }, + { + key: 'additionalData', + converter: converters.BufferSource, + }, + ]); + +converters.AesCtrParams = createDictionaryConverter( + 'AesCtrParams', [ + ...new SafeArrayIterator(dictAlgorithm), + { + key: 'counter', + converter: converters.BufferSource, + required: true, + }, + { + key: 'length', + converter: (V, opts) => + converters['unsigned short'](V, { ...opts, enforceRange: true }), + required: true, + }, + ]); + +converters.CryptoKey = createInterfaceConverter( + 'CryptoKey', CryptoKey.prototype); + +converters.CryptoKeyPair = createDictionaryConverter( + 'CryptoKeyPair', [ + { + key: 'publicKey', + converter: converters.CryptoKey, + }, + { + key: 'privateKey', + converter: converters.CryptoKey, + }, + ]); + +converters.EcdhKeyDeriveParams = createDictionaryConverter( + 'EcdhKeyDeriveParams', [ + ...new SafeArrayIterator(dictAlgorithm), + { + key: 'public', + converter: converters.CryptoKey, + required: true, + }, + ]); + +converters.Ed448Params = createDictionaryConverter( + 'Ed448Params', [ + ...new SafeArrayIterator(dictAlgorithm), + { + key: 'context', + converter: converters.BufferSource, + required: false, + }, + ]); + +module.exports = { + converters, + requiredArguments, +}; diff --git a/test/parallel/test-webcrypto-cryptokey-workers.js b/test/parallel/test-webcrypto-cryptokey-workers.js index 4f800fdb9b6871..4de221ec6e822a 100644 --- a/test/parallel/test-webcrypto-cryptokey-workers.js +++ b/test/parallel/test-webcrypto-cryptokey-workers.js @@ -25,7 +25,7 @@ const sig = '13691a79fb55a0417e4d6699a32f91ad29283fa2c1439865cc0632931f4f48dc'; async function doSig(key) { const signature = await subtle.sign({ name: 'HMAC' - }, key, 'some data'); + }, key, Buffer.from('some data')); assert.strictEqual(Buffer.from(signature).toString('hex'), sig); } diff --git a/test/parallel/test-webcrypto-derivebits-cfrg.js b/test/parallel/test-webcrypto-derivebits-cfrg.js index 104ee5f1a9f3b9..d92d2a18610767 100644 --- a/test/parallel/test-webcrypto-derivebits-cfrg.js +++ b/test/parallel/test-webcrypto-derivebits-cfrg.js @@ -144,7 +144,7 @@ async function prepareKeys() { { name: 'X448' }, keys.X448.privateKey, 8 * keys.X448.size), - { code: 'ERR_INVALID_ARG_TYPE' }); + { code: 'ERR_MISSING_OPTION' }); } { diff --git a/test/parallel/test-webcrypto-derivebits-ecdh.js b/test/parallel/test-webcrypto-derivebits-ecdh.js index 9405dbc2d7b8bd..d28899f7f912d7 100644 --- a/test/parallel/test-webcrypto-derivebits-ecdh.js +++ b/test/parallel/test-webcrypto-derivebits-ecdh.js @@ -165,7 +165,7 @@ async function prepareKeys() { { name: 'ECDH' }, keys['P-384'].privateKey, 8 * keys['P-384'].size), - { code: 'ERR_INVALID_ARG_TYPE' }); + { code: 'ERR_MISSING_OPTION' }); } { diff --git a/test/parallel/test-webcrypto-derivebits-hkdf.js b/test/parallel/test-webcrypto-derivebits-hkdf.js index 670a5e0a953b75..73ea97a0d12774 100644 --- a/test/parallel/test-webcrypto-derivebits-hkdf.js +++ b/test/parallel/test-webcrypto-derivebits-hkdf.js @@ -343,7 +343,7 @@ async function testDeriveBitsMissingSalt( return assert.rejects( subtle.deriveBits(algorithm, baseKeys[size], 0), { - code: 'ERR_INVALID_ARG_TYPE' + code: 'ERR_MISSING_OPTION' }); } @@ -361,7 +361,7 @@ async function testDeriveBitsMissingInfo( return assert.rejects( subtle.deriveBits(algorithm, baseKeys[size], 0), { - code: 'ERR_INVALID_ARG_TYPE' + code: 'ERR_MISSING_OPTION' }); } diff --git a/test/parallel/test-webcrypto-derivekey-cfrg.js b/test/parallel/test-webcrypto-derivekey-cfrg.js index 08897aed1baa34..213463355ff758 100644 --- a/test/parallel/test-webcrypto-derivekey-cfrg.js +++ b/test/parallel/test-webcrypto-derivekey-cfrg.js @@ -110,7 +110,7 @@ async function prepareKeys() { { name: 'X448' }, keys.X448.privateKey, ...otherArgs), - { code: 'ERR_INVALID_ARG_TYPE' }); + { code: 'ERR_MISSING_OPTION' }); } { diff --git a/test/parallel/test-webcrypto-derivekey-ecdh.js b/test/parallel/test-webcrypto-derivekey-ecdh.js index 2afec3db26b34b..4f801da75afdf0 100644 --- a/test/parallel/test-webcrypto-derivekey-ecdh.js +++ b/test/parallel/test-webcrypto-derivekey-ecdh.js @@ -127,7 +127,7 @@ async function prepareKeys() { { name: 'ECDH' }, keys['P-384'].privateKey, ...otherArgs), - { code: 'ERR_INVALID_ARG_TYPE' }); + { code: 'ERR_MISSING_OPTION' }); } { diff --git a/test/parallel/test-webcrypto-digest.js b/test/parallel/test-webcrypto-digest.js index ea8c258cf1c2ed..7e8ad8671b5440 100644 --- a/test/parallel/test-webcrypto-digest.js +++ b/test/parallel/test-webcrypto-digest.js @@ -66,11 +66,11 @@ const kData = (new TextEncoder()).encode('hello'); })); })().then(common.mustCall()); -Promise.all([1, [], {}, null, undefined].map((i) => - assert.rejects(subtle.digest(i), { message: /Unrecognized name/ }) +Promise.all([1, null, undefined].map((i) => + assert.rejects(subtle.digest(i, Buffer.alloc(0)), { message: /Unrecognized name/ }) )).then(common.mustCall()); -assert.rejects(subtle.digest(''), { message: /Unrecognized name/ }).then(common.mustCall()); +assert.rejects(subtle.digest('', Buffer.alloc(0)), { message: /Unrecognized name/ }).then(common.mustCall()); Promise.all([1, [], {}, null, undefined].map((i) => assert.rejects(subtle.digest('SHA-256', i), { @@ -78,14 +78,6 @@ Promise.all([1, [], {}, null, undefined].map((i) => }) )).then(common.mustCall()); -// If there is a mismatch between length and the expected digest size for -// the selected algorithm, we fail. The length is a Node.js specific -// addition to the API, and is added as a support for future additional -// hash algorithms that support variable digest output lengths. -assert.rejects(subtle.digest({ name: 'SHA-512', length: 510 }, kData), { - name: 'OperationError', -}).then(common.mustCall()); - const kSourceData = { empty: '', short: '156eea7cc14c56cb94db030a4a9d95ff', diff --git a/test/parallel/test-webcrypto-export-import.js b/test/parallel/test-webcrypto-export-import.js index 23940464b81246..32467e0489b5ca 100644 --- a/test/parallel/test-webcrypto-export-import.js +++ b/test/parallel/test-webcrypto-export-import.js @@ -14,19 +14,17 @@ const { subtle } = globalThis.crypto; await Promise.all([1, null, undefined, {}, []].map((format) => assert.rejects( subtle.importKey(format, keyData, {}, false, ['wrapKey']), { - code: 'ERR_INVALID_ARG_TYPE' + code: 'ERR_INVALID_ARG_VALUE' }) )); await assert.rejects( subtle.importKey('not valid', keyData, {}, false, ['wrapKey']), { code: 'ERR_INVALID_ARG_VALUE' }); - await Promise.all([1, null, undefined, {}, []].map((keyData) => - assert.rejects( - subtle.importKey('raw', keyData, {}, false, ['deriveBits']), { - code: 'ERR_INVALID_ARG_TYPE' - }) - )); + await assert.rejects( + subtle.importKey('raw', 1, {}, false, ['deriveBits']), { + code: 'ERR_INVALID_ARG_TYPE' + }); await assert.rejects( subtle.importKey('raw', keyData, { name: 'HMAC' @@ -65,7 +63,7 @@ const { subtle } = globalThis.crypto; hash: 'SHA-256', }, false, ['sign', 'verify']), { name: 'DataError', - message: 'Invalid JWK keyData' + message: 'Invalid key type' }); } diff --git a/test/parallel/test-webcrypto-keygen.js b/test/parallel/test-webcrypto-keygen.js index 7cb02c9863f4eb..a949c0ae2b8a7d 100644 --- a/test/parallel/test-webcrypto-keygen.js +++ b/test/parallel/test-webcrypto-keygen.js @@ -163,7 +163,7 @@ const vectors = { return assert.rejects( // The extractable and usages values are invalid here also, // but the unrecognized algorithm name should be caught first. - subtle.generateKey(algorithm, 7, ['zebra']), { + subtle.generateKey(algorithm, 7, []), { message: /Unrecognized name/ }); } @@ -299,12 +299,12 @@ const vectors = { // Missing parameters await assert.rejects( subtle.generateKey({ name, publicExponent, hash }, true, usages), { - code: 'ERR_INVALID_ARG_TYPE' + code: 'ERR_MISSING_OPTION' }); await assert.rejects( subtle.generateKey({ name, modulusLength, hash }, true, usages), { - code: 'ERR_INVALID_ARG_TYPE' + code: 'ERR_MISSING_OPTION' }); await assert.rejects( @@ -312,7 +312,7 @@ const vectors = { code: 'ERR_MISSING_OPTION' }); - await Promise.all(['', true, {}].map((modulusLength) => { + await Promise.all([{}].map((modulusLength) => { return assert.rejects(subtle.generateKey({ name, modulusLength, @@ -338,7 +338,7 @@ const vectors = { { code: 'ERR_INVALID_ARG_TYPE' }); })); - await Promise.all([true, {}, 1, []].map((hash) => { + await Promise.all([true, 1].map((hash) => { return assert.rejects(subtle.generateKey({ name, modulusLength, @@ -349,17 +349,6 @@ const vectors = { }); })); - await Promise.all(['', {}, 1, []].map((extractable) => { - return assert.rejects(subtle.generateKey({ - name, - modulusLength, - publicExponent, - hash - }, extractable, usages), { - code: 'ERR_INVALID_ARG_TYPE' - }); - })); - await Promise.all(['', {}, 1, false].map((usages) => { return assert.rejects(subtle.generateKey({ name, @@ -449,12 +438,17 @@ const vectors = { assert.strictEqual(privateKey.algorithm.namedCurve, namedCurve); // Invalid parameters - [1, true, {}, [], undefined, null].forEach(async (namedCurve) => { + [1, true, {}, [], null].forEach(async (namedCurve) => { await assert.rejects( subtle.generateKey({ name, namedCurve }, true, privateUsages), { name: 'NotSupportedError' }); }); + await assert.rejects( + subtle.generateKey({ name, namedCurve: undefined }, true, privateUsages), { + name: 'TypeError', + code: 'ERR_MISSING_OPTION' + }); } const kTests = [ @@ -509,19 +503,18 @@ const vectors = { assert.strictEqual(key.algorithm.length, length); // Invalid parameters - [1, 100, 257].forEach(async (length) => { + [1, 100, 257, '', false, null].forEach(async (length) => { await assert.rejects( subtle.generateKey({ name, length }, true, usages), { name: 'OperationError' }); }); - ['', {}, [], false, null, undefined].forEach(async (length) => { - await assert.rejects( - subtle.generateKey({ name, length }, true, usages), { - name: 'OperationError', - }); - }); + await assert.rejects( + subtle.generateKey({ name, length: undefined }, true, usages), { + name: 'TypeError', + code: 'ERR_MISSING_OPTION' + }); } const kTests = [ @@ -568,14 +561,7 @@ const vectors = { assert.strictEqual(key.algorithm.length, length); assert.strictEqual(key.algorithm.hash.name, hash); - ['', {}, [], false, null].forEach(async (length) => { - await assert.rejects( - subtle.generateKey({ name: 'HMAC', length, hash }, true, usages), { - code: 'ERR_INVALID_ARG_TYPE' - }); - }); - - [1, {}, [], false, null].forEach(async (hash) => { + [1, false, null].forEach(async (hash) => { await assert.rejects( subtle.generateKey({ name: 'HMAC', length, hash }, true, usages), { message: /Unrecognized name/ diff --git a/test/parallel/test-webcrypto-sign-verify-hmac.js b/test/parallel/test-webcrypto-sign-verify-hmac.js index 9a5ed318e72fd7..98cf61f5c9d179 100644 --- a/test/parallel/test-webcrypto-sign-verify-hmac.js +++ b/test/parallel/test-webcrypto-sign-verify-hmac.js @@ -91,16 +91,14 @@ async function testVerify({ hash, // Test failure when wrong hash is used { const otherhash = hash === 'SHA-1' ? 'SHA-256' : 'SHA-1'; - assert(!(await subtle.verify({ - name, - hash: otherhash - }, key, signature, copy))); + const keyWithOtherHash = await subtle.importKey( + 'raw', + keyBuffer, + { name, hash: otherhash }, + false, + ['verify']); + assert(!(await subtle.verify({ name }, keyWithOtherHash, signature, plaintext))); } - - await assert.rejects( - subtle.verify({ name, hash: 'sha256' }, key, signature, copy), { - message: /Unrecognized name/ - }); } async function testSign({ hash, @@ -156,7 +154,6 @@ async function testSign({ hash, subtle.generateKey({ name }, false, ['sign', 'verify']), { name: 'TypeError', code: 'ERR_MISSING_OPTION', - message: 'algorithm.hash is required' }); // Test failure when no sign usage diff --git a/test/parallel/test-webcrypto-sign-verify-rsa.js b/test/parallel/test-webcrypto-sign-verify-rsa.js index bd727f2f998fa7..6057d2f6ef9325 100644 --- a/test/parallel/test-webcrypto-sign-verify-rsa.js +++ b/test/parallel/test-webcrypto-sign-verify-rsa.js @@ -112,19 +112,14 @@ async function testVerify({ // Test failure when wrong hash is used { const otherhash = hash === 'SHA-1' ? 'SHA-256' : 'SHA-1'; - assert(!(await subtle.verify({ - ...algorithm, - hash: otherhash - }, publicKey, signature, copy))); + const keyWithOtherHash = await subtle.importKey( + 'spki', + publicKeyBuffer, + { name: algorithm.name, hash: otherhash }, + false, + ['verify']); + assert(!(await subtle.verify(algorithm, keyWithOtherHash, signature, plaintext))); } - - await assert.rejects( - subtle.verify( - { ...algorithm, hash: 'sha256' }, - publicKey, - signature, - copy), - { message: /Unrecognized name/ }); } async function testSign({ diff --git a/test/parallel/test-webcrypto-util.js b/test/parallel/test-webcrypto-util.js index 4bb14a7f91494f..b8d5361433fd0e 100644 --- a/test/parallel/test-webcrypto-util.js +++ b/test/parallel/test-webcrypto-util.js @@ -11,15 +11,9 @@ const { normalizeAlgorithm, } = require('internal/crypto/util'); -{ - // Check that normalizeAlgorithm does not add an undefined hash property. - assert.strictEqual('hash' in normalizeAlgorithm({ name: 'ECDH' }), false); - assert.strictEqual('hash' in normalizeAlgorithm('ECDH'), false); -} - { // Check that normalizeAlgorithm does not mutate object inputs. - const algorithm = { name: 'ECDH', hash: 'SHA-256' }; - assert.strictEqual(normalizeAlgorithm(algorithm) !== algorithm, true); - assert.deepStrictEqual(algorithm, { name: 'ECDH', hash: 'SHA-256' }); + const algorithm = { name: 'ECDSA', hash: 'SHA-256' }; + assert.strictEqual(normalizeAlgorithm(algorithm, 'sign') !== algorithm, true); + assert.deepStrictEqual(algorithm, { name: 'ECDSA', hash: 'SHA-256' }); } diff --git a/test/pummel/test-webcrypto-derivebits-pbkdf2.js b/test/pummel/test-webcrypto-derivebits-pbkdf2.js index 243856d372774a..e0f65536fc4ab2 100644 --- a/test/pummel/test-webcrypto-derivebits-pbkdf2.js +++ b/test/pummel/test-webcrypto-derivebits-pbkdf2.js @@ -382,7 +382,7 @@ async function setupBaseKeys() { promises.push( subtle.importKey( 'raw', - kPasswords[size], + Buffer.from(kPasswords[size], 'hex'), { name: 'PBKDF2' }, false, ['deriveBits']) @@ -391,7 +391,7 @@ async function setupBaseKeys() { promises.push( subtle.importKey( 'raw', - kPasswords[size], + Buffer.from(kPasswords[size], 'hex'), { name: 'PBKDF2' }, false, ['deriveKey']) @@ -474,7 +474,7 @@ async function testDeriveBitsBadHash( hash, iterations) { const salt = Buffer.from(kSalts[saltSize], 'hex'); - const algorithm = { name: 'HKDF', salt, iterations }; + const algorithm = { name: 'PBKDF2', salt, iterations }; return Promise.all([ assert.rejects(