mirror of
https://github.com/nodejs/node.git
synced 2025-04-30 23:56:58 +00:00

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>
708 lines
17 KiB
JavaScript
708 lines
17 KiB
JavaScript
'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,
|
|
};
|