node/test/parallel/test-webcrypto-webidl.js

519 lines
17 KiB
JavaScript
Raw Normal View History

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