crypto: use WebIDL converters in WebCryptoAPI

WebCryptoAPI functions' arguments are now coersed and validated as per
their WebIDL definitions like in other Web Crypto API implementations.
This further improves interoperability with other implementations of
Web Crypto API.

PR-URL: https://github.com/nodejs/node/pull/46067
Reviewed-By: Antoine du Hamel <duhamelantoine1995@gmail.com>
Reviewed-By: Chengzhong Wu <legendecas@gmail.com>
This commit is contained in:
Filip Skokan 2023-01-17 09:57:58 +01:00 committed by GitHub
parent 66b1356ebc
commit 3ef38c4bd7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
34 changed files with 2040 additions and 422 deletions

View File

@ -2,6 +2,10 @@
<!-- YAML
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/46067
description: Arguments are now coersed and validated as per their WebIDL
definitions like in other Web Crypto API implementations.
- version: v19.0.0
pr-url: https://github.com/nodejs/node/pull/44897
description: No longer experimental except for the `Ed25519`, `Ed448`,

View File

@ -32,7 +32,6 @@ const {
} = internalBinding('crypto');
const {
getArrayBufferOrView,
hasAnyNotIn,
jobPromise,
validateByteLength,
@ -112,13 +111,10 @@ 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.
if (typeof length !== 'number' ||
length <= 0 ||
length > kMaxCounterLength) {
if (length === 0 || length > kMaxCounterLength) {
throw lazyDOMException(
'AES-CTR algorithm.length must be between 1 and 128',
'OperationError');
@ -135,7 +131,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 +161,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');
}
@ -281,16 +273,16 @@ async function aesImportKey(
break;
}
case 'jwk': {
if (keyData == null || typeof keyData !== 'object')
throw lazyDOMException('Invalid JWK keyData', 'DataError');
if (!keyData.kty)
throw lazyDOMException('Invalid keyData', 'DataError');
if (keyData.kty !== 'oct')
throw lazyDOMException('Invalid key type', 'DataError');
throw lazyDOMException('Invalid JWK "kty" Parameter', 'DataError');
if (usagesSet.size > 0 &&
keyData.use !== undefined &&
keyData.use !== 'enc') {
throw lazyDOMException('Invalid use type', 'DataError');
throw lazyDOMException('Invalid JWK "use" Parameter', 'DataError');
}
validateKeyOps(keyData.key_ops, usagesSet);
@ -298,7 +290,9 @@ async function aesImportKey(
if (keyData.ext !== undefined &&
keyData.ext === false &&
extractable === true) {
throw lazyDOMException('JWK is not extractable', 'DataError');
throw lazyDOMException(
'JWK "ext" Parameter and extractable mismatch',
'DataError');
}
const handle = new KeyObjectHandle();
@ -308,10 +302,10 @@ async function aesImportKey(
validateKeyLength(length);
if (keyData.alg !== undefined) {
if (typeof keyData.alg !== 'string')
throw lazyDOMException('Invalid alg', 'DataError');
if (keyData.alg !== getAlgorithmName(algorithm.name, length))
throw lazyDOMException('Algorithm mismatch', 'DataError');
throw lazyDOMException(
'JWK "alg" does not match the requested algorithm',
'DataError');
}
keyObject = new SecretKeyObject(handle);

View File

@ -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':
@ -237,12 +235,13 @@ async function cfrgImportKey(
break;
}
case 'jwk': {
if (keyData == null || typeof keyData !== 'object')
throw lazyDOMException('Invalid JWK keyData', 'DataError');
if (!keyData.kty)
throw lazyDOMException('Invalid keyData', 'DataError');
if (keyData.kty !== 'OKP')
throw lazyDOMException('Invalid key type', 'DataError');
throw lazyDOMException('Invalid JWK "kty" Parameter', 'DataError');
if (keyData.crv !== name)
throw lazyDOMException('Subtype mismatch', 'DataError');
throw lazyDOMException(
'JWK "crv" Parameter and algorithm name mismatch', 'DataError');
const isPublic = keyData.d === undefined;
if (usagesSet.size > 0 && keyData.use !== undefined) {
@ -260,7 +259,7 @@ async function cfrgImportKey(
break;
}
if (keyData.use !== checkUse)
throw lazyDOMException('Invalid use type', 'DataError');
throw lazyDOMException('Invalid JWK "use" Parameter', 'DataError');
}
validateKeyOps(keyData.key_ops, usagesSet);
@ -268,22 +267,24 @@ async function cfrgImportKey(
if (keyData.ext !== undefined &&
keyData.ext === false &&
extractable === true) {
throw lazyDOMException('JWK is not extractable', 'DataError');
throw lazyDOMException(
'JWK "ext" Parameter and extractable mismatch',
'DataError');
}
if (keyData.alg !== undefined) {
if (typeof keyData.alg !== 'string')
throw lazyDOMException('Invalid alg', 'DataError');
if (
(name === 'Ed25519' || name === 'Ed448') &&
keyData.alg !== 'EdDSA'
) {
throw lazyDOMException('Invalid alg', 'DataError');
throw lazyDOMException(
'JWK "alg" does not match the requested algorithm',
'DataError');
}
}
if (!isPublic && typeof keyData.x !== 'string') {
throw lazyDOMException('Invalid JWK keyData', 'DataError');
throw lazyDOMException('Invalid JWK', 'DataError');
}
verifyAcceptableCfrgKeyUse(
@ -305,7 +306,7 @@ async function cfrgImportKey(
false);
if (!createPublicKey(keyObject).equals(publicKeyObject)) {
throw lazyDOMException('Invalid JWK keyData', 'DataError');
throw lazyDOMException('Invalid JWK', 'DataError');
}
}
break;
@ -336,13 +337,9 @@ function eddsaSignVerify(key, data, { name, context }, signature) {
if (key.type !== type)
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');
}
if (name === 'Ed448' && context?.byteLength) {
throw lazyDOMException(
'Non zero-length context is not yet supported.', 'NotSupportedError');
}
return jobPromise(() => new SignJob(

View File

@ -34,7 +34,6 @@ const {
validateInt32,
validateObject,
validateString,
validateUint32,
} = require('internal/validators');
const {
@ -48,7 +47,6 @@ const {
const {
KeyObject,
isCryptoKey,
} = require('internal/crypto/keys');
const {
@ -320,13 +318,6 @@ function diffieHellman(options) {
async function ecdhDeriveBits(algorithm, baseKey, length) {
const { 'public': key } = algorithm;
// Null means that we're not asking for a specific number of bits, just
// 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(
'algorithm.public must be a public key', 'InvalidAccessError');

View File

@ -18,13 +18,6 @@ const {
} = internalBinding('crypto');
const {
codes: {
ERR_MISSING_OPTION,
}
} = require('internal/errors');
const {
getArrayBufferOrView,
getUsagesUnion,
hasAnyNotIn,
jobPromise,
@ -76,7 +69,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');
@ -204,12 +196,14 @@ async function ecImportKey(
break;
}
case 'jwk': {
if (keyData == null || typeof keyData !== 'object')
throw lazyDOMException('Invalid JWK keyData', 'DataError');
if (!keyData.kty)
throw lazyDOMException('Invalid keyData', 'DataError');
if (keyData.kty !== 'EC')
throw lazyDOMException('Invalid key type', 'DataError');
throw lazyDOMException('Invalid JWK "kty" Parameter', 'DataError');
if (keyData.crv !== namedCurve)
throw lazyDOMException('Named curve mismatch', 'DataError');
throw lazyDOMException(
'JWK "crv" does not match the requested algorithm',
'DataError');
verifyAcceptableEcKeyUse(
name,
@ -217,10 +211,9 @@ async function ecImportKey(
usagesSet);
if (usagesSet.size > 0 && keyData.use !== undefined) {
if (algorithm.name === 'ECDSA' && keyData.use !== 'sig')
throw lazyDOMException('Invalid use type', 'DataError');
if (algorithm.name === 'ECDH' && keyData.use !== 'enc')
throw lazyDOMException('Invalid use type', 'DataError');
const checkUse = name === 'ECDH' ? 'enc' : 'sig';
if (keyData.use !== checkUse)
throw lazyDOMException('Invalid JWK "use" Parameter', 'DataError');
}
validateKeyOps(keyData.key_ops, usagesSet);
@ -228,12 +221,12 @@ async function ecImportKey(
if (keyData.ext !== undefined &&
keyData.ext === false &&
extractable === true) {
throw lazyDOMException('JWK is not extractable', 'DataError');
throw lazyDOMException(
'JWK "ext" Parameter and extractable mismatch',
'DataError');
}
if (algorithm.name === 'ECDSA' && keyData.alg !== undefined) {
if (typeof keyData.alg !== 'string')
throw lazyDOMException('Invalid alg', 'DataError');
let algNamedCurve;
switch (keyData.alg) {
case 'ES256': algNamedCurve = 'P-256'; break;
@ -241,13 +234,15 @@ async function ecImportKey(
case 'ES512': algNamedCurve = 'P-521'; break;
}
if (algNamedCurve !== namedCurve)
throw lazyDOMException('Named curve mismatch', 'DataError');
throw lazyDOMException(
'JWK "alg" does not match the requested algorithm',
'DataError');
}
const handle = new KeyObjectHandle();
const type = handle.initJwk(keyData, namedCurve);
if (type === undefined)
throw lazyDOMException('Invalid JWK keyData', 'DataError');
throw lazyDOMException('Invalid JWK', 'DataError');
keyObject = type === kKeyTypePrivate ?
new PrivateKeyObject(handle) :
new PublicKeyObject(handle);
@ -289,8 +284,6 @@ function ecdsaSignVerify(key, data, { name, hash }, signature) {
if (key.type !== type)
throw lazyDOMException(`Key must be a ${type} key`, 'InvalidAccessError');
if (hash === undefined)
throw new ERR_MISSING_OPTION('algorithm.hash');
const hashname = normalizeHashName(hash.name);
return jobPromise(() => new SignJob(

View File

@ -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,11 +179,10 @@ async function asyncDigest(algorithm, data) {
return jobPromise(() => new HashJob(
kCryptoJobAsync,
normalizeHashName(algorithm.name),
data,
algorithm.length));
data));
}
throw lazyDOMException('Unrecognized name.', 'NotSupportedError');
throw lazyDOMException('Unrecognized algorithm name', 'NotSupportedError');
}
module.exports = {

View File

@ -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');

View File

@ -29,12 +29,6 @@ const {
promisify,
} = require('internal/util');
const {
codes: {
ERR_MISSING_OPTION,
}
} = require('internal/errors');
const {
generateKey: _generateKey,
} = require('internal/crypto/keygen');
@ -50,8 +44,6 @@ const generateKey = promisify(_generateKey);
async function hmacGenerateKey(algorithm, extractable, keyUsages) {
const { hash, name } = algorithm;
let { length } = algorithm;
if (hash === undefined)
throw new ERR_MISSING_OPTION('algorithm.hash');
if (length === undefined)
length = getBlockSize(hash.name);
@ -78,16 +70,24 @@ async function hmacGenerateKey(algorithm, extractable, keyUsages) {
extractable);
}
function getAlgorithmName(hash) {
switch (hash) {
case 'SHA-1': // Fall through
case 'SHA-256': // Fall through
case 'SHA-384': // Fall through
case 'SHA-512': // Fall through
return `HS${hash.slice(4)}`;
default:
throw lazyDOMException('Unsupported digest algorithm', 'DataError');
}
}
async function hmacImportKey(
format,
keyData,
algorithm,
extractable,
keyUsages) {
const { hash } = algorithm;
if (hash === undefined)
throw new ERR_MISSING_OPTION('algorithm.hash');
const usagesSet = new SafeSet(keyUsages);
if (hasAnyNotIn(usagesSet, ['sign', 'verify'])) {
throw lazyDOMException(
@ -115,16 +115,16 @@ async function hmacImportKey(
break;
}
case 'jwk': {
if (keyData == null || typeof keyData !== 'object')
throw lazyDOMException('Invalid JWK keyData', 'DataError');
if (!keyData.kty)
throw lazyDOMException('Invalid keyData', 'DataError');
if (keyData.kty !== 'oct')
throw lazyDOMException('Invalid key type', 'DataError');
throw lazyDOMException('Invalid JWK "kty" Parameter', 'DataError');
if (usagesSet.size > 0 &&
keyData.use !== undefined &&
keyData.use !== 'sig') {
throw lazyDOMException('Invalid use type', 'DataError');
throw lazyDOMException('Invalid JWK "use" Parameter', 'DataError');
}
validateKeyOps(keyData.key_ops, usagesSet);
@ -132,32 +132,16 @@ async function hmacImportKey(
if (keyData.ext !== undefined &&
keyData.ext === false &&
extractable === true) {
throw lazyDOMException('JWK is not extractable', 'DataError');
throw lazyDOMException(
'JWK "ext" Parameter and extractable mismatch',
'DataError');
}
if (keyData.alg !== undefined) {
if (typeof keyData.alg !== 'string')
throw lazyDOMException('Invalid alg', 'DataError');
switch (keyData.alg) {
case 'HS1':
if (algorithm.hash.name !== 'SHA-1')
throw lazyDOMException('Digest algorithm mismatch', 'DataError');
break;
case 'HS256':
if (algorithm.hash.name !== 'SHA-256')
throw lazyDOMException('Digest algorithm mismatch', 'DataError');
break;
case 'HS384':
if (algorithm.hash.name !== 'SHA-384')
throw lazyDOMException('Digest algorithm mismatch', 'DataError');
break;
case 'HS512':
if (algorithm.hash.name !== 'SHA-512')
throw lazyDOMException('Digest algorithm mismatch', 'DataError');
break;
default:
throw lazyDOMException('Unsupported digest algorithm', 'DataError');
}
if (keyData.alg !== getAlgorithmName(algorithm.hash.name))
throw lazyDOMException(
'JWK "alg" does not match the requested algorithm',
'DataError');
}
const handle = new KeyObjectHandle();

View File

@ -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',

View File

@ -40,7 +40,6 @@ const { Buffer, kMaxLength } = require('buffer');
const {
codes: {
ERR_INVALID_ARG_TYPE,
ERR_MISSING_ARGS,
ERR_OUT_OF_RANGE,
ERR_OPERATION_FAILED,
}
@ -316,8 +315,6 @@ function onJobDone(buf, callback, error) {
// not allowed to exceed 65536 bytes, and can only
// be an integer-type TypedArray.
function getRandomValues(data) {
if (arguments.length < 1)
throw new ERR_MISSING_ARGS('typedArray');
if (!isTypedArray(data) ||
isFloat32Array(data) ||
isFloat64Array(data)) {

View File

@ -21,21 +21,12 @@ const {
RSA_PKCS1_PSS_PADDING,
} = internalBinding('crypto');
const {
codes: {
ERR_INVALID_ARG_TYPE,
ERR_MISSING_OPTION,
}
} = require('internal/errors');
const {
validateInt32,
validateUint32,
} = require('internal/validators');
const {
bigIntArrayToUnsignedInt,
getArrayBufferOrView,
getUsagesUnion,
hasAnyNotIn,
jobPromise,
@ -51,10 +42,6 @@ const {
promisify,
} = require('internal/util');
const {
isUint8Array,
} = require('internal/util/types');
const {
InternalCryptoKey,
PrivateKeyObject,
@ -104,7 +91,6 @@ function rsaOaepCipher(mode, key, data, { label }) {
'InvalidAccessError');
}
if (label !== undefined) {
label = getArrayBufferOrView(label, 'algorithm.label');
validateMaxBufferLength(label, 'algorithm.label');
}
@ -130,16 +116,6 @@ async function rsaKeyGenerate(
hash
} = algorithm;
if (hash === undefined)
throw new ERR_MISSING_OPTION('algorithm.hash');
validateUint32(modulusLength, 'algorithm.modulusLength');
if (!isUint8Array(publicExponent)) {
throw new ERR_INVALID_ARG_TYPE(
'algorithm.publicExponent',
'Uint8Array',
publicExponent);
}
const usageSet = new SafeSet(keyUsages);
const publicExponentConverted = bigIntArrayToUnsignedInt(publicExponent);
@ -228,10 +204,6 @@ async function rsaImportKey(
algorithm,
extractable,
keyUsages) {
const { hash } = algorithm;
if (hash === undefined)
throw new ERR_MISSING_OPTION('algorithm.hash');
const usagesSet = new SafeSet(keyUsages);
let keyObject;
switch (format) {
@ -264,21 +236,21 @@ async function rsaImportKey(
break;
}
case 'jwk': {
if (keyData == null || typeof keyData !== 'object')
throw lazyDOMException('Invalid JWK keyData', 'DataError');
if (!keyData.kty)
throw lazyDOMException('Invalid keyData', 'DataError');
if (keyData.kty !== 'RSA')
throw lazyDOMException('Invalid JWK "kty" Parameter', 'DataError');
verifyAcceptableRsaKeyUse(
algorithm.name,
keyData.d === undefined,
usagesSet);
if (keyData.kty !== 'RSA')
throw lazyDOMException('Invalid key type', 'DataError');
if (usagesSet.size > 0 && keyData.use !== undefined) {
const checkUse = algorithm.name === 'RSA-OAEP' ? 'enc' : 'sig';
if (keyData.use !== checkUse)
throw lazyDOMException('Invalid use type', 'DataError');
throw lazyDOMException('Invalid JWK "use" Parameter', 'DataError');
}
validateKeyOps(keyData.key_ops, usagesSet);
@ -286,22 +258,24 @@ async function rsaImportKey(
if (keyData.ext !== undefined &&
keyData.ext === false &&
extractable === true) {
throw lazyDOMException('JWK is not extractable', 'DataError');
throw lazyDOMException(
'JWK "ext" Parameter and extractable mismatch',
'DataError');
}
if (keyData.alg !== undefined) {
if (typeof keyData.alg !== 'string')
throw lazyDOMException('Invalid alg', 'DataError');
const hash =
normalizeHashName(keyData.alg, normalizeHashName.kContextWebCrypto);
if (hash !== algorithm.hash.name)
throw lazyDOMException('Hash mismatch', 'DataError');
throw lazyDOMException(
'JWK "alg" does not match the requested algorithm',
'DataError');
}
const handle = new KeyObjectHandle();
const type = handle.initJwk(keyData);
if (type === undefined)
throw lazyDOMException('Invalid JWK keyData', 'DataError');
throw lazyDOMException('Invalid JWK', 'DataError');
keyObject = type === kKeyTypePrivate ?
new PrivateKeyObject(handle) :

View File

@ -1,15 +1,26 @@
'use strict';
const {
ArrayBufferIsView,
ArrayBufferPrototypeGetByteLength,
ArrayPrototypeIncludes,
ArrayPrototypePush,
BigInt,
DataViewPrototypeGetBuffer,
DataViewPrototypeGetByteLength,
DataViewPrototypeGetByteOffset,
FunctionPrototypeBind,
Number,
ObjectKeys,
ObjectPrototypeHasOwnProperty,
Promise,
StringPrototypeToLowerCase,
StringPrototypeToUpperCase,
Symbol,
TypedArrayPrototypeGetBuffer,
TypedArrayPrototypeGetByteLength,
TypedArrayPrototypeGetByteOffset,
TypedArrayPrototypeSlice,
Uint8Array,
} = primordials;
const {
@ -55,6 +66,7 @@ const {
} = require('internal/util');
const {
isDataView,
isArrayBufferView,
isAnyArrayBuffer,
} = require('internal/util/types');
@ -138,40 +150,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 +159,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 +280,99 @@ function validateMaxBufferLength(data, name) {
}
}
function normalizeAlgorithm(algorithm) {
if (algorithm != null) {
if (typeof algorithm === 'string')
algorithm = { name: algorithm };
let webidl;
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;
// https://w3c.github.io/webcrypto/#algorithm-normalization-normalize-an-algorithm
// adapted for Node.js from Deno's implementation
// https://github.com/denoland/deno/blob/v1.29.1/ext/crypto/00_crypto.js#L195
function normalizeAlgorithm(algorithm, op) {
if (typeof algorithm === 'string')
return normalizeAlgorithm({ name: algorithm }, op);
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 (
StringPrototypeToUpperCase(key) === StringPrototypeToUpperCase(algName)
) {
algName = key;
desiredType = registeredAlgorithms[key];
}
}
throw lazyDOMException('Unrecognized name.', 'NotSupportedError');
if (desiredType === undefined)
throw lazyDOMException('Unrecognized algorithm 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.
const dictKeys = dict ? ObjectKeys(dict) : [];
for (let i = 0; i < dictKeys.length; i++) {
const member = dictKeys[i];
if (!ObjectPrototypeHasOwnProperty(dict, member))
continue;
const idlType = dict[member];
const idlValue = normalizedAlgorithm[member];
// 3.
if (idlType === 'BufferSource' && idlValue) {
const isView = ArrayBufferIsView(idlValue);
normalizedAlgorithm[member] = TypedArrayPrototypeSlice(
new Uint8Array(
isView ? getDataViewOrTypedArrayBuffer(idlValue) : idlValue,
isView ? getDataViewOrTypedArrayByteOffset(idlValue) : 0,
isView ? getDataViewOrTypedArrayByteLength(idlValue) : ArrayBufferPrototypeGetByteLength(idlValue),
),
);
} 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 getDataViewOrTypedArrayBuffer(V) {
return isDataView(V) ?
DataViewPrototypeGetBuffer(V) : TypedArrayPrototypeGetBuffer(V);
}
function getDataViewOrTypedArrayByteOffset(V) {
return isDataView(V) ?
DataViewPrototypeGetByteOffset(V) : TypedArrayPrototypeGetByteOffset(V);
}
function getDataViewOrTypedArrayByteLength(V) {
return isDataView(V) ?
DataViewPrototypeGetByteLength(V) : TypedArrayPrototypeGetByteLength(V);
}
function hasAnyNotIn(set, checks) {
@ -402,6 +556,7 @@ module.exports = {
getArrayBufferOrView,
getCiphers,
getCurves,
getDataViewOrTypedArrayBuffer,
getDefaultEncoding,
getHashes,
kHandle,
@ -413,7 +568,6 @@ module.exports = {
kHashTypes,
kNamedCurveAliases,
kAesKeyLengths,
kExportFormats,
normalizeAlgorithm,
normalizeHashName,
hasAnyNotIn,

View File

@ -10,7 +10,6 @@ const {
ReflectConstruct,
SafeSet,
SymbolToStringTag,
StringPrototypeRepeat,
} = primordials;
const {
@ -21,14 +20,6 @@ const {
kWebCryptoCipherDecrypt,
} = internalBinding('crypto');
const {
validateArray,
validateBoolean,
validateObject,
validateOneOf,
validateString,
} = require('internal/validators');
const {
getOptionValue,
} = require('internal/options');
@ -38,7 +29,6 @@ const { TextDecoder, TextEncoder } = require('internal/encoding');
const {
codes: {
ERR_ILLEGAL_CONSTRUCTOR,
ERR_INVALID_ARG_TYPE,
ERR_INVALID_THIS,
}
} = require('internal/errors');
@ -47,7 +37,6 @@ const {
CryptoKey,
InternalCryptoKey,
createSecretKey,
isCryptoKey,
} = require('internal/crypto/keys');
const {
@ -55,13 +44,11 @@ const {
} = require('internal/crypto/hash');
const {
getArrayBufferOrView,
getBlockSize,
hasAnyNotIn,
normalizeAlgorithm,
normalizeHashName,
validateMaxBufferLength,
kExportFormats,
kHandle,
kKeyObject,
} = require('internal/crypto/util');
@ -76,9 +63,26 @@ const {
randomUUID: _randomUUID,
} = require('internal/crypto/random');
let webidl;
async function digest(algorithm, data) {
if (this !== subtle) throw new ERR_INVALID_THIS('SubtleCrypto');
return ReflectApply(asyncDigest, this, arguments);
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: '1st argument',
});
data = webidl.converters.BufferSource(data, {
prefix,
context: '2nd argument',
});
algorithm = normalizeAlgorithm(algorithm, 'digest');
return ReflectApply(asyncDigest, this, [algorithm, data]);
}
function randomUUID() {
@ -91,9 +95,24 @@ async function generateKey(
extractable,
keyUsages) {
if (this !== subtle) throw new ERR_INVALID_THIS('SubtleCrypto');
algorithm = normalizeAlgorithm(algorithm);
validateBoolean(extractable, 'extractable');
validateArray(keyUsages, 'keyUsages');
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: '1st argument',
});
extractable = webidl.converters.boolean(extractable, {
prefix,
context: '2nd argument',
});
keyUsages = webidl.converters['sequence<KeyUsage>'](keyUsages, {
prefix,
context: '3rd argument',
});
algorithm = normalizeAlgorithm(algorithm, 'generateKey');
let result;
let resultType;
switch (algorithm.name) {
@ -141,7 +160,7 @@ async function generateKey(
.aesGenerateKey(algorithm, extractable, keyUsages);
break;
default:
throw lazyDOMException('Unrecognized name.');
throw lazyDOMException('Unrecognized algorithm name', 'NotSupportedError');
}
if (
@ -160,9 +179,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);
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: '1st argument',
});
baseKey = webidl.converters.CryptoKey(baseKey, {
prefix,
context: '2nd argument',
});
if (length !== null) {
length = webidl.converters['unsigned long'](length, {
prefix,
context: '3rd argument',
});
}
algorithm = normalizeAlgorithm(algorithm, 'deriveBits');
if (!ArrayPrototypeIncludes(baseKey.usages, 'deriveBits')) {
throw lazyDOMException(
'baseKey does not have deriveBits usage',
@ -185,7 +221,7 @@ async function deriveBits(algorithm, baseKey, length) {
return require('internal/crypto/pbkdf2')
.pbkdf2DeriveBits(algorithm, baseKey, length);
}
throw lazyDOMException('Unrecognized name.');
throw lazyDOMException('Unrecognized algorithm name', 'NotSupportedError');
}
function getKeyLength({ name, length, hash }) {
@ -221,10 +257,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);
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: '1st argument',
});
baseKey = webidl.converters.CryptoKey(baseKey, {
prefix,
context: '2nd argument',
});
derivedKeyAlgorithm = webidl.converters.AlgorithmIdentifier(derivedKeyAlgorithm, {
prefix,
context: '3rd argument',
});
extractable = webidl.converters.boolean(extractable, {
prefix,
context: '4th argument',
});
keyUsages = webidl.converters['sequence<KeyUsage>'](keyUsages, {
prefix,
context: '5th argument',
});
algorithm = normalizeAlgorithm(algorithm, 'deriveBits');
derivedKeyAlgorithm = normalizeAlgorithm(derivedKeyAlgorithm, 'importKey');
if (!ArrayPrototypeIncludes(baseKey.usages, 'deriveKey')) {
throw lazyDOMException(
'baseKey does not have deriveKey usage',
@ -232,13 +291,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':
@ -258,7 +312,7 @@ async function deriveKey(
.pbkdf2DeriveBits(algorithm, baseKey, length);
break;
default:
throw lazyDOMException('Unrecognized name.');
throw lazyDOMException('Unrecognized algorithm name', 'NotSupportedError');
}
return ReflectApply(
@ -446,10 +500,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);
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: '1st argument',
});
key = webidl.converters.CryptoKey(key, {
prefix,
context: '2nd argument',
});
if (!key.extractable)
throw lazyDOMException('key is not extractable', 'InvalidAccessException');
@ -515,13 +577,33 @@ 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');
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: '1st argument',
});
const type = format === 'jwk' ? 'JsonWebKey' : 'BufferSource';
keyData = webidl.converters[type](keyData, {
prefix,
context: '2nd argument',
});
algorithm = webidl.converters.AlgorithmIdentifier(algorithm, {
prefix,
context: '3rd argument',
});
extractable = webidl.converters.boolean(extractable, {
prefix,
context: '4th argument',
});
keyUsages = webidl.converters['sequence<KeyUsage>'](keyUsages, {
prefix,
context: '5th argument',
});
algorithm = normalizeAlgorithm(algorithm, 'importKey');
switch (algorithm.name) {
case 'RSASSA-PKCS1-v1_5':
// Fall through
@ -567,22 +649,43 @@ async function importKey(
keyUsages);
}
throw lazyDOMException('Unrecognized name.', 'NotSupportedError');
throw lazyDOMException('Unrecognized algorithm name', 'NotSupportedError');
}
// subtle.wrapKey() is essentially a subtle.exportKey() followed
// by a subtle.encrypt().
async function wrapKey(format, key, wrappingKey, algorithm) {
if (this !== subtle) throw new ERR_INVALID_THIS('SubtleCrypto');
algorithm = normalizeAlgorithm(algorithm);
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: '1st argument',
});
key = webidl.converters.CryptoKey(key, {
prefix,
context: '2nd argument',
});
wrappingKey = webidl.converters.CryptoKey(wrappingKey, {
prefix,
context: '3rd argument',
});
algorithm = webidl.converters.AlgorithmIdentifier(algorithm, {
prefix,
context: '4th argument',
});
try {
algorithm = normalizeAlgorithm(algorithm, 'wrapKey');
} catch {
algorithm = normalizeAlgorithm(algorithm, 'encrypt');
}
let keyData = await ReflectApply(exportKey, this, [format, key]);
if (format === 'jwk') {
if (keyData == null || typeof keyData !== 'object')
throw lazyDOMException('Invalid exported JWK key', 'DataError');
const ec = new TextEncoder();
const raw = JSONStringify(keyData);
keyData = ec.encode(raw + StringPrototypeRepeat(' ', 8 - (raw.length % 8)));
keyData = new TextEncoder().encode(JSONStringify(keyData));
}
return cipherOrWrap(
@ -604,8 +707,48 @@ async function unwrapKey(
extractable,
keyUsages) {
if (this !== subtle) throw new ERR_INVALID_THIS('SubtleCrypto');
wrappedKey = getArrayBufferOrView(wrappedKey, 'wrappedKey');
unwrapAlgo = normalizeAlgorithm(unwrapAlgo);
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: '1st argument',
});
wrappedKey = webidl.converters.BufferSource(wrappedKey, {
prefix,
context: '2nd argument',
});
unwrappingKey = webidl.converters.CryptoKey(unwrappingKey, {
prefix,
context: '3rd argument',
});
unwrapAlgo = webidl.converters.AlgorithmIdentifier(unwrapAlgo, {
prefix,
context: '4th argument',
});
unwrappedKeyAlgo = webidl.converters.AlgorithmIdentifier(
unwrappedKeyAlgo,
{
prefix,
context: '5th argument',
},
);
extractable = webidl.converters.boolean(extractable, {
prefix,
context: '6th argument',
});
keyUsages = webidl.converters['sequence<KeyUsage>'](keyUsages, {
prefix,
context: '7th argument',
});
try {
unwrapAlgo = normalizeAlgorithm(unwrapAlgo, 'unwrapKey');
} catch {
unwrapAlgo = normalizeAlgorithm(unwrapAlgo, 'decrypt');
}
let keyData = await cipherOrWrap(
kWebCryptoCipherDecrypt,
unwrapAlgo,
@ -621,7 +764,7 @@ async function unwrapKey(
try {
keyData = JSONParse(dec.decode(keyData));
} catch {
throw lazyDOMException('Invalid imported JWK key', 'DataError');
throw lazyDOMException('Invalid wrapped JWK key', 'DataError');
}
}
@ -633,15 +776,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) {
@ -669,26 +808,61 @@ function signVerify(algorithm, key, data, signature) {
return require('internal/crypto/mac')
.hmacSignVerify(key, data, algorithm, signature);
}
throw lazyDOMException('Unrecognized named.', 'NotSupportedError');
throw lazyDOMException('Unrecognized algorithm name', 'NotSupportedError');
}
async function sign(algorithm, key, data) {
if (this !== subtle) throw new ERR_INVALID_THIS('SubtleCrypto');
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: '1st argument',
});
key = webidl.converters.CryptoKey(key, {
prefix,
context: '2nd argument',
});
data = webidl.converters.BufferSource(data, {
prefix,
context: '3rd argument',
});
return signVerify(algorithm, key, data);
}
async function verify(algorithm, key, signature, data) {
if (this !== subtle) throw new ERR_INVALID_THIS('SubtleCrypto');
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: '1st argument',
});
key = webidl.converters.CryptoKey(key, {
prefix,
context: '2nd argument',
});
signature = webidl.converters.BufferSource(signature, {
prefix,
context: '3rd argument',
});
data = webidl.converters.BufferSource(data, {
prefix,
context: '4th argument',
});
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 +872,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.
@ -724,16 +894,52 @@ async function cipherOrWrap(mode, algorithm, key, data, op) {
.aesCipher(mode, key, data, algorithm);
}
}
throw lazyDOMException('Unrecognized name.', 'NotSupportedError');
throw lazyDOMException('Unrecognized algorithm name', 'NotSupportedError');
}
async function encrypt(algorithm, key, data) {
if (this !== subtle) throw new ERR_INVALID_THIS('SubtleCrypto');
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: '1st argument',
});
key = webidl.converters.CryptoKey(key, {
prefix,
context: '2nd argument',
});
data = webidl.converters.BufferSource(data, {
prefix,
context: '3rd argument',
});
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');
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: '1st argument',
});
key = webidl.converters.CryptoKey(key, {
prefix,
context: '2nd argument',
});
data = webidl.converters.BufferSource(data, {
prefix,
context: '3rd argument',
});
algorithm = normalizeAlgorithm(algorithm, 'decrypt');
return cipherOrWrap(kWebCryptoCipherDecrypt, algorithm, key, data, 'decrypt');
}
@ -761,6 +967,11 @@ const crypto = ReflectConstruct(function() {}, [], Crypto);
function getRandomValues(array) {
if (this !== crypto) throw new ERR_INVALID_THIS('Crypto');
webidl ??= require('internal/crypto/webidl');
const prefix = "Failed to execute 'getRandomValues' on 'Crypto'";
webidl.requiredArguments(arguments.length, 1, { prefix });
return ReflectApply(_getRandomValues, this, arguments);
}

View File

@ -0,0 +1,707 @@
'use strict';
// Adapted from the following sources
// - 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.
// Changes include using primordials and stripping the code down to only what
// WebCryptoAPI needs.
const {
ArrayBufferIsView,
ArrayBufferPrototype,
ArrayPrototypePush,
ArrayPrototypeSort,
MathPow,
MathTrunc,
Number,
NumberIsFinite,
ObjectAssign,
ObjectPrototypeIsPrototypeOf,
SafeArrayIterator,
SafeSet,
String,
SymbolIterator,
TypedArrayPrototypeGetBuffer,
TypedArrayPrototypeGetSymbolToStringTag,
TypeError,
globalThis: {
SharedArrayBuffer,
},
} = primordials;
const {
kEmptyObject,
setOwnProperty,
} = require('internal/util');
const { CryptoKey } = require('internal/crypto/webcrypto');
const { getDataViewOrTypedArrayBuffer } = require('internal/crypto/util');
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) {
const prefix = opts.prefix ? opts.prefix + ': ' : '';
const context = opts.context?.length === 0 ?
'' : (opts.context ?? 'Value') + ' ';
return codedTypeError(
`${prefix}${context}${message}`,
{ code: opts.code || 'ERR_INVALID_ARG_TYPE' },
);
}
// https://tc39.es/ecma262/#sec-tonumber
function toNumber(value, opts = kEmptyObject) {
switch (typeof value) {
case 'number':
return value;
case 'bigint':
throw makeException(
'is a BigInt and cannot be converted to a number.',
opts);
case 'symbol':
throw makeException(
'is a Symbol and cannot be converted to a number.',
opts);
default:
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';
}
}
const integerPart = MathTrunc;
// This was updated to only consider bitlength up to 32 used by WebCryptoAPI
function createIntegerConversion(bitLength) {
const lowerBound = 0;
const upperBound = MathPow(2, bitLength) - 1;
const twoToTheBitLength = MathPow(2, bitLength);
return (V, opts = kEmptyObject) => {
let x = toNumber(V, opts);
if (opts.enforceRange) {
if (!NumberIsFinite(x)) {
throw makeException(
'is not a finite number.',
opts);
}
x = integerPart(x);
if (x < lowerBound || x > upperBound) {
throw makeException(
`is outside the expected range of ${lowerBound} to ${upperBound}.`,
{ __proto__: null, ...opts, code: 'ERR_OUT_OF_RANGE' },
);
}
return x;
}
if (!NumberIsFinite(x) || x === 0) {
return 0;
}
x = integerPart(x);
if (x >= lowerBound && x <= upperBound) {
return x;
}
x = x % twoToTheBitLength;
return x;
};
}
const converters = {};
converters.boolean = (val) => !!val;
converters.octet = createIntegerConversion(8);
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 and cannot be converted to a string.',
opts);
}
return String(V);
};
converters.object = (V, opts) => {
if (type(V) !== 'Object') {
throw makeException(
'is not an object.',
opts);
}
return V;
};
function isNonSharedArrayBuffer(V) {
return ObjectPrototypeIsPrototypeOf(ArrayBufferPrototype, V);
}
function isSharedArrayBuffer(V) {
return ObjectPrototypeIsPrototypeOf(SharedArrayBuffer.prototype, V);
}
converters.Uint8Array = (V, opts = kEmptyObject) => {
if (!ArrayBufferIsView(V) ||
TypedArrayPrototypeGetSymbolToStringTag(V) !== 'Uint8Array') {
throw makeException(
'is not an Uint8Array object.',
opts);
}
if (isSharedArrayBuffer(TypedArrayPrototypeGetBuffer(V))) {
throw makeException(
'is a view on a SharedArrayBuffer, which is not allowed.',
opts);
}
return V;
};
converters.BufferSource = (V, opts = kEmptyObject) => {
if (ArrayBufferIsView(V)) {
if (isSharedArrayBuffer(getDataViewOrTypedArrayBuffer(V))) {
throw makeException(
'is a view on a SharedArrayBuffer, which is not allowed.',
opts);
}
return V;
}
if (!isNonSharedArrayBuffer(V)) {
throw makeException(
'is not instance of ArrayBuffer, Buffer, TypedArray, or DataView.',
opts);
}
return V;
};
converters['sequence<DOMString>'] = createSequenceConverter(
converters.DOMString);
function requiredArguments(length, required, opts = kEmptyObject) {
if (length < required) {
throw makeException(
`${required} argument${
required === 1 ? '' : 's'
} required, but only ${length} present.`,
{ __proto__: null, ...opts, context: '', code: 'ERR_MISSING_ARGS' });
}
}
function createDictionaryConverter(name, dictionaries) {
let hasRequiredKey = false;
const allMembers = [];
for (let i = 0; i < dictionaries.length; i++) {
const member = dictionaries[i];
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;
});
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);
}
const esDict = V;
const idlDict = {};
// Fast path null and undefined.
if (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, {
__proto__: null,
...opts,
context,
});
setOwnProperty(idlDict, key, idlMemberValue);
} else if (member.required) {
throw makeException(
`can not be converted to '${name}' because '${key}' is required in '${name}'.`,
{ __proto__: null, ...opts, code: 'ERR_MISSING_OPTION' });
}
}
return idlDict;
};
}
function createEnumConverter(name, values) {
const E = new SafeSet(values);
return function(V, opts = kEmptyObject) {
const S = String(V);
if (!E.has(S)) {
throw makeException(
`value '${S}' is not a valid enum value of type ${name}.`,
{ __proto__: null, ...opts, code: 'ERR_INVALID_ARG_VALUE' });
}
return S;
};
}
function createSequenceConverter(converter) {
return function(V, opts = kEmptyObject) {
if (type(V) !== 'Object') {
throw makeException(
'can not be converted to sequence.',
opts);
}
const iter = V?.[SymbolIterator]?.();
if (iter === undefined) {
throw makeException(
'can not be converted to sequence.',
opts);
}
const array = [];
while (true) {
const res = iter?.next?.();
if (res === undefined) {
throw makeException(
'can not be converted to sequence.',
opts);
}
if (res.done === true) break;
const val = converter(res.value, {
__proto__: null,
...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);
}
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.KeyFormat = createEnumConverter('KeyFormat', [
'raw',
'pkcs8',
'spki',
'jwk',
]);
converters.KeyUsage = createEnumConverter('KeyUsage', [
'encrypt',
'decrypt',
'sign',
'verify',
'deriveKey',
'deriveBits',
'wrapKey',
'unwrapKey',
]);
converters['sequence<KeyUsage>'] = 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<RsaOtherPrimesInfo>'] = createSequenceConverter(
converters.RsaOtherPrimesInfo);
converters.JsonWebKey = createDictionaryConverter(
'JsonWebKey', [
simpleDomStringKey('kty'),
simpleDomStringKey('use'),
{
key: 'key_ops',
converter: converters['sequence<DOMString>'],
},
simpleDomStringKey('alg'),
{
key: 'ext',
converter: converters.boolean,
},
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<RsaOtherPrimesInfo>'],
},
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 short'](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.octet(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.octet(V, { ...opts, enforceRange: true }),
required: true,
},
]);
converters.CryptoKey = createInterfaceConverter(
'CryptoKey', CryptoKey.prototype);
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,
};

View File

@ -115,7 +115,7 @@ module.exports = function() {
const failing = [];
kKeyLengths.forEach((keyLength) => {
[24, 48, 72, 95, 129, 256].forEach((badTagLength) => {
[24, 48, 72, 95, 129].forEach((badTagLength) => {
failing.push({
keyBuffer: kKeyBytes[keyLength],
algorithm: {

View File

@ -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);
}

View File

@ -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' });
}
{

View File

@ -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' });
}
{

View File

@ -296,7 +296,8 @@ async function testDeriveBitsBadHash(
...algorithm,
hash: hash.substring(0, 3) + hash.substring(4)
}, baseKeys[size], 256), {
message: /Unrecognized name/
message: /Unrecognized algorithm name/,
name: 'NotSupportedError',
}),
assert.rejects(
subtle.deriveBits(
@ -305,7 +306,8 @@ async function testDeriveBitsBadHash(
hash: 'PBKDF2'
},
baseKeys[size], 256), {
message: /Unrecognized name/
message: /Unrecognized algorithm name/,
name: 'NotSupportedError',
}),
]);
}
@ -343,7 +345,7 @@ async function testDeriveBitsMissingSalt(
return assert.rejects(
subtle.deriveBits(algorithm, baseKeys[size], 0), {
code: 'ERR_INVALID_ARG_TYPE'
code: 'ERR_MISSING_OPTION'
});
}
@ -361,7 +363,7 @@ async function testDeriveBitsMissingInfo(
return assert.rejects(
subtle.deriveBits(algorithm, baseKeys[size], 0), {
code: 'ERR_INVALID_ARG_TYPE'
code: 'ERR_MISSING_OPTION'
});
}
@ -435,7 +437,10 @@ async function testDeriveKeyBadHash(
keyType,
true,
usages),
{ message: /Unrecognized name/ }),
{
message: /Unrecognized algorithm name/,
name: 'NotSupportedError',
}),
assert.rejects(
subtle.deriveKey(
{
@ -446,7 +451,10 @@ async function testDeriveKeyBadHash(
keyType,
true,
usages),
{ message: /Unrecognized name/ }),
{
message: /Unrecognized algorithm name/,
name: 'NotSupportedError',
}),
]);
}

View File

@ -110,7 +110,7 @@ async function prepareKeys() {
{ name: 'X448' },
keys.X448.privateKey,
...otherArgs),
{ code: 'ERR_INVALID_ARG_TYPE' });
{ code: 'ERR_MISSING_OPTION' });
}
{

View File

@ -127,7 +127,7 @@ async function prepareKeys() {
{ name: 'ECDH' },
keys['P-384'].privateKey,
...otherArgs),
{ code: 'ERR_INVALID_ARG_TYPE' });
{ code: 'ERR_MISSING_OPTION' });
}
{

View File

@ -66,11 +66,17 @@ 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 algorithm name/,
name: 'NotSupportedError',
})
)).then(common.mustCall());
assert.rejects(subtle.digest(''), { message: /Unrecognized name/ }).then(common.mustCall());
assert.rejects(subtle.digest('', Buffer.alloc(0)), {
message: /Unrecognized algorithm name/,
name: 'NotSupportedError',
}).then(common.mustCall());
Promise.all([1, [], {}, null, undefined].map((i) =>
assert.rejects(subtle.digest('SHA-256', i), {
@ -78,14 +84,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',

View File

@ -260,6 +260,38 @@ async function testImportJwk({ name, publicUsages, privateUsages }, extractable)
});
}
{
const invalidUse = name.startsWith('X') ? 'sig' : 'enc';
await assert.rejects(
subtle.importKey(
'jwk',
{ ...jwk, use: invalidUse },
{ name },
extractable,
privateUsages),
{ message: 'Invalid JWK "use" Parameter' });
}
if (name.startsWith('Ed')) {
await assert.rejects(
subtle.importKey(
'jwk',
{ kty: jwk.kty, x: jwk.x, crv: jwk.crv, alg: 'foo' },
{ name },
extractable,
publicUsages),
{ message: 'JWK "alg" does not match the requested algorithm' });
await assert.rejects(
subtle.importKey(
'jwk',
{ ...jwk, alg: 'foo' },
{ name },
extractable,
privateUsages),
{ message: 'JWK "alg" does not match the requested algorithm' });
}
for (const crv of [undefined, name === 'Ed25519' ? 'Ed448' : 'Ed25519']) {
await assert.rejects(
subtle.importKey(
@ -268,16 +300,16 @@ async function testImportJwk({ name, publicUsages, privateUsages }, extractable)
{ name },
extractable,
publicUsages),
{ message: /Subtype mismatch/ });
{ message: 'JWK "crv" Parameter and algorithm name mismatch' });
await assert.rejects(
subtle.importKey(
'jwk',
{ kty: jwk.kty, d: jwk.d, x: jwk.x, y: jwk.y, crv },
{ ...jwk, crv },
{ name },
extractable,
publicUsages),
{ message: /Subtype mismatch/ });
{ message: 'JWK "crv" Parameter and algorithm name mismatch' });
}
}

View File

@ -261,6 +261,38 @@ async function testImportJwk(
});
}
{
const invalidUse = name === 'ECDH' ? 'sig' : 'enc';
await assert.rejects(
subtle.importKey(
'jwk',
{ ...jwk, use: invalidUse },
{ name, namedCurve },
extractable,
privateUsages),
{ message: 'Invalid JWK "use" Parameter' });
}
if (name === 'ECDSA') {
await assert.rejects(
subtle.importKey(
'jwk',
{ kty: jwk.kty, x: jwk.x, y: jwk.y, crv: jwk.crv, alg: jwk.crv === 'P-256' ? 'ES384' : 'ES256' },
{ name, namedCurve },
extractable,
publicUsages),
{ message: 'JWK "alg" does not match the requested algorithm' });
await assert.rejects(
subtle.importKey(
'jwk',
{ ...jwk, alg: jwk.crv === 'P-256' ? 'ES384' : 'ES256' },
{ name, namedCurve },
extractable,
privateUsages),
{ message: 'JWK "alg" does not match the requested algorithm' });
}
for (const crv of [undefined, namedCurve === 'P-256' ? 'P-384' : 'P-256']) {
await assert.rejects(
subtle.importKey(
@ -269,16 +301,16 @@ async function testImportJwk(
{ name, namedCurve },
extractable,
publicUsages),
{ message: /Named curve mismatch/ });
{ message: 'JWK "crv" does not match the requested algorithm' });
await assert.rejects(
subtle.importKey(
'jwk',
{ kty: jwk.kty, d: jwk.d, x: jwk.x, y: jwk.y, crv },
{ ...jwk, crv },
{ name, namedCurve },
extractable,
publicUsages),
{ message: /Named curve mismatch/ });
privateUsages),
{ message: 'JWK "crv" does not match the requested algorithm' });
}
}

View File

@ -444,6 +444,57 @@ async function testImportJwk(
message: /key is not extractable/
});
}
{
const invalidUse = name === 'RSA-OAEP' ? 'sig' : 'enc';
await assert.rejects(
subtle.importKey(
'jwk',
{ kty: jwk.kty, n: jwk.n, e: jwk.e, use: invalidUse },
{ name, hash },
extractable,
publicUsages),
{ message: 'Invalid JWK "use" Parameter' });
await assert.rejects(
subtle.importKey(
'jwk',
{ ...jwk, use: invalidUse },
{ name, hash },
extractable,
privateUsages),
{ message: 'Invalid JWK "use" Parameter' });
}
{
let invalidAlg = name === 'RSA-OAEP' ? name : name === 'RSA-PSS' ? 'PS' : 'RS';
switch (name) {
case 'RSA-OAEP':
if (hash === 'SHA-1')
invalidAlg += '-256';
break;
default:
if (hash === 'SHA-256')
invalidAlg += '384';
else
invalidAlg += '256';
}
await assert.rejects(
subtle.importKey(
'jwk',
{ kty: jwk.kty, n: jwk.n, e: jwk.e, alg: invalidAlg },
{ name, hash },
extractable,
publicUsages),
{ message: 'JWK "alg" does not match the requested algorithm' });
await assert.rejects(
subtle.importKey(
'jwk',
{ ...jwk, alg: invalidAlg },
{ name, hash },
extractable,
privateUsages),
{ message: 'JWK "alg" does not match the requested algorithm' });
}
}
// combinations to test

View File

@ -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 keyData'
});
}

View File

@ -163,8 +163,9 @@ 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']), {
message: /Unrecognized name/
subtle.generateKey(algorithm, 7, []), {
message: /Unrecognized algorithm name/,
name: 'NotSupportedError',
});
}
@ -299,12 +300,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 +313,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,25 +339,15 @@ 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,
publicExponent,
hash
}, true, usages), {
message: /Unrecognized name/
});
}));
await Promise.all(['', {}, 1, []].map((extractable) => {
return assert.rejects(subtle.generateKey({
name,
modulusLength,
publicExponent,
hash
}, extractable, usages), {
code: 'ERR_INVALID_ARG_TYPE'
message: /Unrecognized algorithm name/,
name: 'NotSupportedError',
});
}));
@ -449,12 +440,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 +505,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,17 +563,11 @@ const vectors = {
assert.strictEqual(key.algorithm.length, length);
assert.strictEqual(key.algorithm.hash.name, hash);
['', {}, [], false, null].forEach(async (length) => {
[1, false, null].forEach(async (hash) => {
await assert.rejects(
subtle.generateKey({ name: 'HMAC', length, hash }, true, usages), {
code: 'ERR_INVALID_ARG_TYPE'
});
});
[1, {}, [], false, null].forEach(async (hash) => {
await assert.rejects(
subtle.generateKey({ name: 'HMAC', length, hash }, true, usages), {
message: /Unrecognized name/
message: /Unrecognized algorithm name/,
name: 'NotSupportedError',
});
});
}

View File

@ -135,7 +135,8 @@ async function testVerify({ name,
await assert.rejects(
subtle.verify({ name, hash: 'sha256' }, publicKey, signature, copy), {
message: /Unrecognized name/
message: /Unrecognized algorithm name/,
name: 'NotSupportedError',
});
}

View File

@ -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

View File

@ -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({

View File

@ -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' });
}

View File

@ -0,0 +1,518 @@
// Flags: --expose-internals
'use strict';
const common = require('../common');
if (!common.hasCrypto)
common.skip('missing crypto');
const assert = require('assert');
const webidl = require('internal/crypto/webidl');
const { subtle } = globalThis.crypto;
const { generateKeySync } = require('crypto');
const { converters } = webidl;
const prefix = "Failed to execute 'fn' on 'interface'";
const context = '1st argument';
const opts = { prefix, context };
// Required arguments.length
{
assert.throws(() => webidl.requiredArguments(0, 3, { prefix }), {
code: 'ERR_MISSING_ARGS',
name: 'TypeError',
message: `${prefix}: 3 arguments required, but only 0 present.`
});
assert.throws(() => webidl.requiredArguments(0, 1, { prefix }), {
code: 'ERR_MISSING_ARGS',
name: 'TypeError',
message: `${prefix}: 1 argument required, but only 0 present.`
});
// Does not throw when extra are added
webidl.requiredArguments(4, 3, { prefix });
}
// boolean
{
assert.strictEqual(converters.boolean(0), false);
assert.strictEqual(converters.boolean(NaN), false);
assert.strictEqual(converters.boolean(undefined), false);
assert.strictEqual(converters.boolean(null), false);
assert.strictEqual(converters.boolean(false), false);
assert.strictEqual(converters.boolean(''), false);
assert.strictEqual(converters.boolean(1), true);
assert.strictEqual(converters.boolean(Number.POSITIVE_INFINITY), true);
assert.strictEqual(converters.boolean(Number.NEGATIVE_INFINITY), true);
assert.strictEqual(converters.boolean('1'), true);
assert.strictEqual(converters.boolean('0'), true);
assert.strictEqual(converters.boolean('false'), true);
assert.strictEqual(converters.boolean(function() {}), true);
assert.strictEqual(converters.boolean(Symbol()), true);
assert.strictEqual(converters.boolean([]), true);
assert.strictEqual(converters.boolean({}), true);
}
// int conversion
// https://webidl.spec.whatwg.org/#abstract-opdef-converttoint
{
for (const [converter, max] of [
[converters.octet, Math.pow(2, 8) - 1],
[converters['unsigned short'], Math.pow(2, 16) - 1],
[converters['unsigned long'], Math.pow(2, 32) - 1],
]) {
assert.strictEqual(converter(0), 0);
assert.strictEqual(converter(max), max);
assert.strictEqual(converter('' + 0), 0);
assert.strictEqual(converter('' + max), max);
assert.strictEqual(converter(3), 3);
assert.strictEqual(converter('' + 3), 3);
assert.strictEqual(converter(3.1), 3);
assert.strictEqual(converter(3.7), 3);
assert.strictEqual(converter(max + 1), 0);
assert.strictEqual(converter(max + 2), 1);
assert.throws(() => converter(max + 1, { ...opts, enforceRange: true }), {
name: 'TypeError',
code: 'ERR_OUT_OF_RANGE',
message: `${prefix}: ${context} is outside the expected range of 0 to ${max}.`,
});
assert.strictEqual(converter({}), 0);
assert.strictEqual(converter(NaN), 0);
assert.strictEqual(converter(false), 0);
assert.strictEqual(converter(true), 1);
assert.strictEqual(converter('1'), 1);
assert.strictEqual(converter('0'), 0);
assert.strictEqual(converter('{}'), 0);
assert.strictEqual(converter({}), 0);
assert.strictEqual(converter([]), 0);
assert.strictEqual(converter(function() {}), 0);
assert.throws(() => converter(Symbol(), opts), {
name: 'TypeError',
code: 'ERR_INVALID_ARG_TYPE',
message: `${prefix}: ${context} is a Symbol and cannot be converted to a number.`
});
assert.throws(() => converter(0n, opts), {
name: 'TypeError',
code: 'ERR_INVALID_ARG_TYPE',
message: `${prefix}: ${context} is a BigInt and cannot be converted to a number.`
});
}
}
// DOMString
{
assert.strictEqual(converters.DOMString(1), '1');
assert.strictEqual(converters.DOMString(1n), '1');
assert.strictEqual(converters.DOMString(false), 'false');
assert.strictEqual(converters.DOMString(true), 'true');
assert.strictEqual(converters.DOMString(undefined), 'undefined');
assert.strictEqual(converters.DOMString(NaN), 'NaN');
assert.strictEqual(converters.DOMString({}), '[object Object]');
assert.strictEqual(converters.DOMString({ foo: 'bar' }), '[object Object]');
assert.strictEqual(converters.DOMString([]), '');
assert.strictEqual(converters.DOMString([1, 2]), '1,2');
assert.throws(() => converters.DOMString(Symbol(), opts), {
name: 'TypeError',
code: 'ERR_INVALID_ARG_TYPE',
message: `${prefix}: ${context} is a Symbol and cannot be converted to a string.`
});
}
// object
{
for (const good of [{}, [], new Array(), function() {}]) {
assert.deepStrictEqual(converters.object(good), good);
}
for (const bad of [undefined, null, NaN, false, true, 0, 1, '', 'foo', Symbol(), 9n]) {
assert.throws(() => converters.object(bad, opts), {
name: 'TypeError',
code: 'ERR_INVALID_ARG_TYPE',
message: `${prefix}: ${context} is not an object.`
});
}
}
// Uint8Array
{
for (const good of [Buffer.alloc(0), new Uint8Array()]) {
assert.deepStrictEqual(converters.Uint8Array(good), good);
}
for (const bad of [new ArrayBuffer(), new SharedArrayBuffer(), [], null, 'foo', undefined, true]) {
assert.throws(() => converters.Uint8Array(bad, opts), {
name: 'TypeError',
code: 'ERR_INVALID_ARG_TYPE',
message: `${prefix}: ${context} is not an Uint8Array object.`
});
}
assert.throws(() => converters.Uint8Array(new Uint8Array(new SharedArrayBuffer()), opts), {
name: 'TypeError',
code: 'ERR_INVALID_ARG_TYPE',
message: `${prefix}: ${context} is a view on a SharedArrayBuffer, which is not allowed.`
});
}
// BufferSource
{
for (const good of [
Buffer.alloc(0),
new Uint8Array(),
new ArrayBuffer(),
new DataView(new ArrayBuffer()),
new BigInt64Array(),
new BigUint64Array(),
new Float32Array(),
new Float64Array(),
new Int8Array(),
new Int16Array(),
new Int32Array(),
new Uint8ClampedArray(),
new Uint16Array(),
new Uint32Array(),
]) {
assert.deepStrictEqual(converters.BufferSource(good), good);
}
for (const bad of [new SharedArrayBuffer(), [], null, 'foo', undefined, true]) {
assert.throws(() => converters.BufferSource(bad, opts), {
name: 'TypeError',
code: 'ERR_INVALID_ARG_TYPE',
message: `${prefix}: ${context} is not instance of ArrayBuffer, Buffer, TypedArray, or DataView.`
});
}
assert.throws(() => converters.BufferSource(new Uint8Array(new SharedArrayBuffer()), opts), {
name: 'TypeError',
code: 'ERR_INVALID_ARG_TYPE',
message: `${prefix}: ${context} is a view on a SharedArrayBuffer, which is not allowed.`
});
}
// CryptoKey
{
subtle.generateKey({ name: 'AES-CBC', length: 128 }, false, ['encrypt']).then((key) => {
assert.deepStrictEqual(converters.CryptoKey(key), key);
}).then(common.mustCall());
for (const bad of [
generateKeySync('aes', { length: 128 }),
undefined, null, 1, {}, Symbol(), true, false, [],
]) {
assert.throws(() => converters.CryptoKey(bad, opts), {
name: 'TypeError',
code: 'ERR_INVALID_ARG_TYPE',
message: `${prefix}: ${context} is not of type CryptoKey.`
});
}
}
// AlgorithmIdentifier (Union for (object or DOMString))
{
assert.strictEqual(converters.AlgorithmIdentifier('foo'), 'foo');
assert.deepStrictEqual(converters.AlgorithmIdentifier({ name: 'foo' }), { name: 'foo' });
}
// JsonWebKey
{
for (const good of [
{},
{ use: 'sig' },
{ key_ops: ['sign'] },
{ ext: true },
{ oth: [] },
{ oth: [{ r: '', d: '', t: '' }] },
]) {
assert.deepStrictEqual(converters.JsonWebKey(good), good);
assert.deepStrictEqual(converters.JsonWebKey({ ...good, filtered: 'out' }), good);
}
}
// KeyFormat
{
for (const good of ['jwk', 'spki', 'pkcs8', 'raw']) {
assert.strictEqual(converters.KeyFormat(good), good);
}
for (const bad of ['foo', 1, false]) {
assert.throws(() => converters.KeyFormat(bad, opts), {
name: 'TypeError',
code: 'ERR_INVALID_ARG_VALUE',
message: `${prefix}: ${context} value '${bad}' is not a valid enum value of type KeyFormat.`,
});
}
}
// KeyUsage
{
for (const good of [
'encrypt',
'decrypt',
'sign',
'verify',
'deriveKey',
'deriveBits',
'wrapKey',
'unwrapKey',
]) {
assert.strictEqual(converters.KeyUsage(good), good);
}
for (const bad of ['foo', 1, false]) {
assert.throws(() => converters.KeyUsage(bad, opts), {
name: 'TypeError',
code: 'ERR_INVALID_ARG_VALUE',
message: `${prefix}: ${context} value '${bad}' is not a valid enum value of type KeyUsage.`,
});
}
}
// Algorithm
{
const good = { name: 'RSA-PSS' };
assert.deepStrictEqual(converters.Algorithm({ ...good, filtered: 'out' }, opts), good);
assert.throws(() => converters.Algorithm({}, opts), {
name: 'TypeError',
code: 'ERR_MISSING_OPTION',
message: `${prefix}: ${context} can not be converted to 'Algorithm' because 'name' is required in 'Algorithm'.`,
});
}
// RsaHashedKeyGenParams
{
for (const good of [
{
name: 'RSA-OAEP',
hash: { name: 'SHA-1' },
modulusLength: 2048,
publicExponent: new Uint8Array([1, 0, 1]),
},
{
name: 'RSA-OAEP',
hash: 'SHA-1',
modulusLength: 2048,
publicExponent: new Uint8Array([1, 0, 1]),
},
]) {
assert.deepStrictEqual(converters.RsaHashedKeyGenParams({ ...good, filtered: 'out' }, opts), good);
for (const required of ['hash', 'publicExponent', 'modulusLength']) {
assert.throws(() => converters.RsaHashedKeyGenParams({ ...good, [required]: undefined }, opts), {
name: 'TypeError',
code: 'ERR_MISSING_OPTION',
message: `${prefix}: ${context} can not be converted to 'RsaHashedKeyGenParams' because '${required}' is required in 'RsaHashedKeyGenParams'.`,
});
}
}
}
// RsaHashedImportParams
{
for (const good of [
{ name: 'RSA-OAEP', hash: { name: 'SHA-1' } },
{ name: 'RSA-OAEP', hash: 'SHA-1' },
]) {
assert.deepStrictEqual(converters.RsaHashedImportParams({ ...good, filtered: 'out' }, opts), good);
assert.throws(() => converters.RsaHashedImportParams({ ...good, hash: undefined }, opts), {
name: 'TypeError',
code: 'ERR_MISSING_OPTION',
message: `${prefix}: ${context} can not be converted to 'RsaHashedImportParams' because 'hash' is required in 'RsaHashedImportParams'.`,
});
}
}
// RsaPssParams
{
const good = { name: 'RSA-PSS', saltLength: 20 };
assert.deepStrictEqual(converters.RsaPssParams({ ...good, filtered: 'out' }, opts), good);
assert.throws(() => converters.RsaPssParams({ ...good, saltLength: undefined }, opts), {
name: 'TypeError',
code: 'ERR_MISSING_OPTION',
message: `${prefix}: ${context} can not be converted to 'RsaPssParams' because 'saltLength' is required in 'RsaPssParams'.`,
});
}
// RsaOaepParams
{
for (const good of [{ name: 'RSA-OAEP' }, { name: 'RSA-OAEP', label: Buffer.alloc(0) }]) {
assert.deepStrictEqual(converters.RsaOaepParams({ ...good, filtered: 'out' }, opts), good);
}
}
// EcKeyImportParams, EcKeyGenParams
{
for (const name of ['EcKeyImportParams', 'EcKeyGenParams']) {
const { [name]: converter } = converters;
const good = { name: 'ECDSA', namedCurve: 'P-256' };
assert.deepStrictEqual(converter({ ...good, filtered: 'out' }, opts), good);
assert.throws(() => converter({ ...good, namedCurve: undefined }, opts), {
name: 'TypeError',
code: 'ERR_MISSING_OPTION',
message: `${prefix}: ${context} can not be converted to '${name}' because 'namedCurve' is required in '${name}'.`,
});
}
}
// EcdsaParams
{
for (const good of [
{ name: 'ECDSA', hash: { name: 'SHA-1' } },
{ name: 'ECDSA', hash: 'SHA-1' },
]) {
assert.deepStrictEqual(converters.EcdsaParams({ ...good, filtered: 'out' }, opts), good);
assert.throws(() => converters.EcdsaParams({ ...good, hash: undefined }, opts), {
name: 'TypeError',
code: 'ERR_MISSING_OPTION',
message: `${prefix}: ${context} can not be converted to 'EcdsaParams' because 'hash' is required in 'EcdsaParams'.`,
});
}
}
// HmacKeyGenParams, HmacImportParams
{
for (const name of ['HmacKeyGenParams', 'HmacImportParams']) {
const { [name]: converter } = converters;
for (const good of [
{ name: 'HMAC', hash: { name: 'SHA-1' } },
{ name: 'HMAC', hash: { name: 'SHA-1' }, length: 20 },
{ name: 'HMAC', hash: 'SHA-1' },
{ name: 'HMAC', hash: 'SHA-1', length: 20 },
]) {
assert.deepStrictEqual(converter({ ...good, filtered: 'out' }, opts), good);
assert.throws(() => converter({ ...good, hash: undefined }, opts), {
name: 'TypeError',
code: 'ERR_MISSING_OPTION',
message: `${prefix}: ${context} can not be converted to '${name}' because 'hash' is required in '${name}'.`,
});
}
}
}
// AesKeyGenParams, AesDerivedKeyParams
{
for (const name of ['AesKeyGenParams', 'AesDerivedKeyParams']) {
const { [name]: converter } = converters;
const good = { name: 'AES-CBC', length: 128 };
assert.deepStrictEqual(converter({ ...good, filtered: 'out' }, opts), good);
assert.throws(() => converter({ ...good, length: undefined }, opts), {
name: 'TypeError',
code: 'ERR_MISSING_OPTION',
message: `${prefix}: ${context} can not be converted to '${name}' because 'length' is required in '${name}'.`,
});
}
}
// HkdfParams
{
for (const good of [
{ name: 'HKDF', hash: { name: 'SHA-1' }, salt: Buffer.alloc(0), info: Buffer.alloc(0) },
{ name: 'HKDF', hash: 'SHA-1', salt: Buffer.alloc(0), info: Buffer.alloc(0) },
]) {
assert.deepStrictEqual(converters.HkdfParams({ ...good, filtered: 'out' }, opts), good);
for (const required of ['hash', 'salt', 'info']) {
assert.throws(() => converters.HkdfParams({ ...good, [required]: undefined }, opts), {
name: 'TypeError',
code: 'ERR_MISSING_OPTION',
message: `${prefix}: ${context} can not be converted to 'HkdfParams' because '${required}' is required in 'HkdfParams'.`,
});
}
}
}
// Pbkdf2Params
{
for (const good of [
{ name: 'PBKDF2', hash: { name: 'SHA-1' }, iterations: 5, salt: Buffer.alloc(0) },
{ name: 'PBKDF2', hash: 'SHA-1', iterations: 5, salt: Buffer.alloc(0) },
]) {
assert.deepStrictEqual(converters.Pbkdf2Params({ ...good, filtered: 'out' }, opts), good);
for (const required of ['hash', 'iterations', 'salt']) {
assert.throws(() => converters.Pbkdf2Params({ ...good, [required]: undefined }, opts), {
name: 'TypeError',
code: 'ERR_MISSING_OPTION',
message: `${prefix}: ${context} can not be converted to 'Pbkdf2Params' because '${required}' is required in 'Pbkdf2Params'.`,
});
}
}
}
// AesCbcParams
{
const good = { name: 'AES-CBC', iv: Buffer.alloc(0) };
assert.deepStrictEqual(converters.AesCbcParams({ ...good, filtered: 'out' }, opts), good);
assert.throws(() => converters.AesCbcParams({ ...good, iv: undefined }, opts), {
name: 'TypeError',
code: 'ERR_MISSING_OPTION',
message: `${prefix}: ${context} can not be converted to 'AesCbcParams' because 'iv' is required in 'AesCbcParams'.`,
});
}
// AesGcmParams
{
for (const good of [
{ name: 'AES-GCM', iv: Buffer.alloc(0) },
{ name: 'AES-GCM', iv: Buffer.alloc(0), tagLength: 16 },
{ name: 'AES-GCM', iv: Buffer.alloc(0), tagLength: 16, additionalData: Buffer.alloc(0) },
]) {
assert.deepStrictEqual(converters.AesGcmParams({ ...good, filtered: 'out' }, opts), good);
assert.throws(() => converters.AesGcmParams({ ...good, iv: undefined }, opts), {
name: 'TypeError',
code: 'ERR_MISSING_OPTION',
message: `${prefix}: ${context} can not be converted to 'AesGcmParams' because 'iv' is required in 'AesGcmParams'.`,
});
}
}
// AesCtrParams
{
const good = { name: 'AES-CTR', counter: Buffer.alloc(0), length: 20 };
assert.deepStrictEqual(converters.AesCtrParams({ ...good, filtered: 'out' }, opts), good);
for (const required of ['counter', 'length']) {
assert.throws(() => converters.AesCtrParams({ ...good, [required]: undefined }, opts), {
name: 'TypeError',
code: 'ERR_MISSING_OPTION',
message: `${prefix}: ${context} can not be converted to 'AesCtrParams' because '${required}' is required in 'AesCtrParams'.`,
});
}
}
// EcdhKeyDeriveParams
{
subtle.generateKey({ name: 'ECDH', namedCurve: 'P-256' }, false, ['deriveBits']).then((kp) => {
const good = { name: 'ECDH', public: kp.publicKey };
assert.deepStrictEqual(converters.EcdhKeyDeriveParams({ ...good, filtered: 'out' }, opts), good);
assert.throws(() => converters.EcdhKeyDeriveParams({ ...good, public: undefined }, opts), {
name: 'TypeError',
code: 'ERR_MISSING_OPTION',
message: `${prefix}: ${context} can not be converted to 'EcdhKeyDeriveParams' because 'public' is required in 'EcdhKeyDeriveParams'.`,
});
}).then(common.mustCall());
}
// Ed448Params
{
for (const good of [
{ name: 'Ed448', context: new Uint8Array() },
{ name: 'Ed448' },
]) {
assert.deepStrictEqual(converters.Ed448Params({ ...good, filtered: 'out' }, opts), good);
}
}

View File

@ -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(
@ -483,7 +483,8 @@ async function testDeriveBitsBadHash(
...algorithm,
hash: hash.substring(0, 3) + hash.substring(4)
}, baseKeys[size], 256), {
message: /Unrecognized name/
message: /Unrecognized algorithm name/,
name: 'NotSupportedError',
}),
assert.rejects(
subtle.deriveBits(
@ -492,7 +493,8 @@ async function testDeriveBitsBadHash(
hash: 'HKDF'
},
baseKeys[size], 256), {
message: /Unrecognized name/
message: /Unrecognized algorithm name/,
name: 'NotSupportedError',
}),
]);
}
@ -569,7 +571,10 @@ async function testDeriveKeyBadHash(
keyType,
true,
usages),
{ message: /Unrecognized name/ }),
{
message: /Unrecognized algorithm name/,
name: 'NotSupportedError',
}),
assert.rejects(
subtle.deriveKey(
{
@ -580,7 +585,10 @@ async function testDeriveKeyBadHash(
keyType,
true,
usages),
{ message: /Unrecognized name/ }),
{
message: /Unrecognized algorithm name/,
name: 'NotSupportedError',
}),
]);
}

View File

@ -1,4 +1,17 @@
{
"encrypt_decrypt/aes_gcm.https.any.js": {
"fail": {
"note": "We're throwing correct error for the WebIDL definition, WPT needs update: https://github.com/web-platform-tests/wpt/pull/37734",
"expected": [
"AES-GCM 128-bit key, illegal tag length 256-bits",
"AES-GCM 192-bit key, illegal tag length 256-bits",
"AES-GCM 256-bit key, illegal tag length 256-bits",
"AES-GCM 128-bit key, illegal tag length 256-bits decryption",
"AES-GCM 192-bit key, illegal tag length 256-bits decryption",
"AES-GCM 256-bit key, illegal tag length 256-bits decryption"
]
}
},
"algorithm-discards-context.https.window.js": {
"skip": "Not relevant in Node.js context"
},