1// Flags: --expose-internals 2'use strict'; 3 4const common = require('../common'); 5if (!common.hasCrypto) 6 common.skip('missing crypto'); 7 8const assert = require('assert'); 9 10const webidl = require('internal/crypto/webidl'); 11const { subtle } = require('node:crypto').webcrypto; 12const { generateKeySync } = require('crypto'); 13 14const { converters } = webidl; 15const prefix = "Failed to execute 'fn' on 'interface'"; 16const context = '1st argument'; 17const opts = { prefix, context }; 18 19// Required arguments.length 20{ 21 assert.throws(() => webidl.requiredArguments(0, 3, { prefix }), { 22 code: 'ERR_MISSING_ARGS', 23 name: 'TypeError', 24 message: `${prefix}: 3 arguments required, but only 0 present.` 25 }); 26 27 assert.throws(() => webidl.requiredArguments(0, 1, { prefix }), { 28 code: 'ERR_MISSING_ARGS', 29 name: 'TypeError', 30 message: `${prefix}: 1 argument required, but only 0 present.` 31 }); 32 33 // Does not throw when extra are added 34 webidl.requiredArguments(4, 3, { prefix }); 35} 36 37// boolean 38{ 39 assert.strictEqual(converters.boolean(0), false); 40 assert.strictEqual(converters.boolean(NaN), false); 41 assert.strictEqual(converters.boolean(undefined), false); 42 assert.strictEqual(converters.boolean(null), false); 43 assert.strictEqual(converters.boolean(false), false); 44 assert.strictEqual(converters.boolean(''), false); 45 46 assert.strictEqual(converters.boolean(1), true); 47 assert.strictEqual(converters.boolean(Number.POSITIVE_INFINITY), true); 48 assert.strictEqual(converters.boolean(Number.NEGATIVE_INFINITY), true); 49 assert.strictEqual(converters.boolean('1'), true); 50 assert.strictEqual(converters.boolean('0'), true); 51 assert.strictEqual(converters.boolean('false'), true); 52 assert.strictEqual(converters.boolean(function() {}), true); 53 assert.strictEqual(converters.boolean(Symbol()), true); 54 assert.strictEqual(converters.boolean([]), true); 55 assert.strictEqual(converters.boolean({}), true); 56} 57 58// int conversion 59// https://webidl.spec.whatwg.org/#abstract-opdef-converttoint 60{ 61 for (const [converter, max] of [ 62 [converters.octet, Math.pow(2, 8) - 1], 63 [converters['unsigned short'], Math.pow(2, 16) - 1], 64 [converters['unsigned long'], Math.pow(2, 32) - 1], 65 ]) { 66 assert.strictEqual(converter(0), 0); 67 assert.strictEqual(converter(max), max); 68 assert.strictEqual(converter('' + 0), 0); 69 assert.strictEqual(converter('' + max), max); 70 assert.strictEqual(converter(3), 3); 71 assert.strictEqual(converter('' + 3), 3); 72 assert.strictEqual(converter(3.1), 3); 73 assert.strictEqual(converter(3.7), 3); 74 75 assert.strictEqual(converter(max + 1), 0); 76 assert.strictEqual(converter(max + 2), 1); 77 assert.throws(() => converter(max + 1, { ...opts, enforceRange: true }), { 78 name: 'TypeError', 79 code: 'ERR_OUT_OF_RANGE', 80 message: `${prefix}: ${context} is outside the expected range of 0 to ${max}.`, 81 }); 82 83 assert.strictEqual(converter({}), 0); 84 assert.strictEqual(converter(NaN), 0); 85 assert.strictEqual(converter(false), 0); 86 assert.strictEqual(converter(true), 1); 87 assert.strictEqual(converter('1'), 1); 88 assert.strictEqual(converter('0'), 0); 89 assert.strictEqual(converter('{}'), 0); 90 assert.strictEqual(converter({}), 0); 91 assert.strictEqual(converter([]), 0); 92 assert.strictEqual(converter(function() {}), 0); 93 94 assert.throws(() => converter(Symbol(), opts), { 95 name: 'TypeError', 96 code: 'ERR_INVALID_ARG_TYPE', 97 message: `${prefix}: ${context} is a Symbol and cannot be converted to a number.` 98 }); 99 assert.throws(() => converter(0n, opts), { 100 name: 'TypeError', 101 code: 'ERR_INVALID_ARG_TYPE', 102 message: `${prefix}: ${context} is a BigInt and cannot be converted to a number.` 103 }); 104 } 105} 106 107// DOMString 108{ 109 assert.strictEqual(converters.DOMString(1), '1'); 110 assert.strictEqual(converters.DOMString(1n), '1'); 111 assert.strictEqual(converters.DOMString(false), 'false'); 112 assert.strictEqual(converters.DOMString(true), 'true'); 113 assert.strictEqual(converters.DOMString(undefined), 'undefined'); 114 assert.strictEqual(converters.DOMString(NaN), 'NaN'); 115 assert.strictEqual(converters.DOMString({}), '[object Object]'); 116 assert.strictEqual(converters.DOMString({ foo: 'bar' }), '[object Object]'); 117 assert.strictEqual(converters.DOMString([]), ''); 118 assert.strictEqual(converters.DOMString([1, 2]), '1,2'); 119 120 assert.throws(() => converters.DOMString(Symbol(), opts), { 121 name: 'TypeError', 122 code: 'ERR_INVALID_ARG_TYPE', 123 message: `${prefix}: ${context} is a Symbol and cannot be converted to a string.` 124 }); 125} 126 127// object 128{ 129 for (const good of [{}, [], new Array(), function() {}]) { 130 assert.deepStrictEqual(converters.object(good), good); 131 } 132 133 for (const bad of [undefined, null, NaN, false, true, 0, 1, '', 'foo', Symbol(), 9n]) { 134 assert.throws(() => converters.object(bad, opts), { 135 name: 'TypeError', 136 code: 'ERR_INVALID_ARG_TYPE', 137 message: `${prefix}: ${context} is not an object.` 138 }); 139 } 140} 141 142// Uint8Array 143{ 144 for (const good of [Buffer.alloc(0), new Uint8Array()]) { 145 assert.deepStrictEqual(converters.Uint8Array(good), good); 146 } 147 148 for (const bad of [new ArrayBuffer(), new SharedArrayBuffer(), [], null, 'foo', undefined, true]) { 149 assert.throws(() => converters.Uint8Array(bad, opts), { 150 name: 'TypeError', 151 code: 'ERR_INVALID_ARG_TYPE', 152 message: `${prefix}: ${context} is not an Uint8Array object.` 153 }); 154 } 155 156 assert.throws(() => converters.Uint8Array(new Uint8Array(new SharedArrayBuffer()), opts), { 157 name: 'TypeError', 158 code: 'ERR_INVALID_ARG_TYPE', 159 message: `${prefix}: ${context} is a view on a SharedArrayBuffer, which is not allowed.` 160 }); 161} 162 163// BufferSource 164{ 165 for (const good of [ 166 Buffer.alloc(0), 167 new Uint8Array(), 168 new ArrayBuffer(), 169 new DataView(new ArrayBuffer()), 170 new BigInt64Array(), 171 new BigUint64Array(), 172 new Float32Array(), 173 new Float64Array(), 174 new Int8Array(), 175 new Int16Array(), 176 new Int32Array(), 177 new Uint8ClampedArray(), 178 new Uint16Array(), 179 new Uint32Array(), 180 ]) { 181 assert.deepStrictEqual(converters.BufferSource(good), good); 182 } 183 184 for (const bad of [new SharedArrayBuffer(), [], null, 'foo', undefined, true]) { 185 assert.throws(() => converters.BufferSource(bad, opts), { 186 name: 'TypeError', 187 code: 'ERR_INVALID_ARG_TYPE', 188 message: `${prefix}: ${context} is not instance of ArrayBuffer, Buffer, TypedArray, or DataView.` 189 }); 190 } 191 192 assert.throws(() => converters.BufferSource(new Uint8Array(new SharedArrayBuffer()), opts), { 193 name: 'TypeError', 194 code: 'ERR_INVALID_ARG_TYPE', 195 message: `${prefix}: ${context} is a view on a SharedArrayBuffer, which is not allowed.` 196 }); 197} 198 199// CryptoKey 200{ 201 202 subtle.generateKey({ name: 'AES-CBC', length: 128 }, false, ['encrypt']).then((key) => { 203 assert.deepStrictEqual(converters.CryptoKey(key), key); 204 }).then(common.mustCall()); 205 206 for (const bad of [ 207 generateKeySync('aes', { length: 128 }), 208 undefined, null, 1, {}, Symbol(), true, false, [], 209 ]) { 210 assert.throws(() => converters.CryptoKey(bad, opts), { 211 name: 'TypeError', 212 code: 'ERR_INVALID_ARG_TYPE', 213 message: `${prefix}: ${context} is not of type CryptoKey.` 214 }); 215 } 216} 217 218// AlgorithmIdentifier (Union for (object or DOMString)) 219{ 220 assert.strictEqual(converters.AlgorithmIdentifier('foo'), 'foo'); 221 assert.deepStrictEqual(converters.AlgorithmIdentifier({ name: 'foo' }), { name: 'foo' }); 222} 223 224// JsonWebKey 225{ 226 for (const good of [ 227 {}, 228 { use: 'sig' }, 229 { key_ops: ['sign'] }, 230 { ext: true }, 231 { oth: [] }, 232 { oth: [{ r: '', d: '', t: '' }] }, 233 ]) { 234 assert.deepStrictEqual(converters.JsonWebKey(good), good); 235 assert.deepStrictEqual(converters.JsonWebKey({ ...good, filtered: 'out' }), good); 236 } 237} 238 239// KeyFormat 240{ 241 for (const good of ['jwk', 'spki', 'pkcs8', 'raw']) { 242 assert.strictEqual(converters.KeyFormat(good), good); 243 } 244 245 for (const bad of ['foo', 1, false]) { 246 assert.throws(() => converters.KeyFormat(bad, opts), { 247 name: 'TypeError', 248 code: 'ERR_INVALID_ARG_VALUE', 249 message: `${prefix}: ${context} value '${bad}' is not a valid enum value of type KeyFormat.`, 250 }); 251 } 252} 253 254// KeyUsage 255{ 256 for (const good of [ 257 'encrypt', 258 'decrypt', 259 'sign', 260 'verify', 261 'deriveKey', 262 'deriveBits', 263 'wrapKey', 264 'unwrapKey', 265 ]) { 266 assert.strictEqual(converters.KeyUsage(good), good); 267 } 268 269 for (const bad of ['foo', 1, false]) { 270 assert.throws(() => converters.KeyUsage(bad, opts), { 271 name: 'TypeError', 272 code: 'ERR_INVALID_ARG_VALUE', 273 message: `${prefix}: ${context} value '${bad}' is not a valid enum value of type KeyUsage.`, 274 }); 275 } 276} 277 278// Algorithm 279{ 280 const good = { name: 'RSA-PSS' }; 281 assert.deepStrictEqual(converters.Algorithm({ ...good, filtered: 'out' }, opts), good); 282 283 assert.throws(() => converters.Algorithm({}, opts), { 284 name: 'TypeError', 285 code: 'ERR_MISSING_OPTION', 286 message: `${prefix}: ${context} can not be converted to 'Algorithm' because 'name' is required in 'Algorithm'.`, 287 }); 288} 289 290// RsaHashedKeyGenParams 291{ 292 for (const good of [ 293 { 294 name: 'RSA-OAEP', 295 hash: { name: 'SHA-1' }, 296 modulusLength: 2048, 297 publicExponent: new Uint8Array([1, 0, 1]), 298 }, 299 { 300 name: 'RSA-OAEP', 301 hash: 'SHA-1', 302 modulusLength: 2048, 303 publicExponent: new Uint8Array([1, 0, 1]), 304 }, 305 ]) { 306 assert.deepStrictEqual(converters.RsaHashedKeyGenParams({ ...good, filtered: 'out' }, opts), good); 307 for (const required of ['hash', 'publicExponent', 'modulusLength']) { 308 assert.throws(() => converters.RsaHashedKeyGenParams({ ...good, [required]: undefined }, opts), { 309 name: 'TypeError', 310 code: 'ERR_MISSING_OPTION', 311 message: `${prefix}: ${context} can not be converted to 'RsaHashedKeyGenParams' because '${required}' is required in 'RsaHashedKeyGenParams'.`, 312 }); 313 } 314 } 315} 316 317// RsaHashedImportParams 318{ 319 for (const good of [ 320 { name: 'RSA-OAEP', hash: { name: 'SHA-1' } }, 321 { name: 'RSA-OAEP', hash: 'SHA-1' }, 322 ]) { 323 assert.deepStrictEqual(converters.RsaHashedImportParams({ ...good, filtered: 'out' }, opts), good); 324 assert.throws(() => converters.RsaHashedImportParams({ ...good, hash: undefined }, opts), { 325 name: 'TypeError', 326 code: 'ERR_MISSING_OPTION', 327 message: `${prefix}: ${context} can not be converted to 'RsaHashedImportParams' because 'hash' is required in 'RsaHashedImportParams'.`, 328 }); 329 } 330} 331 332// RsaPssParams 333{ 334 const good = { name: 'RSA-PSS', saltLength: 20 }; 335 assert.deepStrictEqual(converters.RsaPssParams({ ...good, filtered: 'out' }, opts), good); 336 337 assert.throws(() => converters.RsaPssParams({ ...good, saltLength: undefined }, opts), { 338 name: 'TypeError', 339 code: 'ERR_MISSING_OPTION', 340 message: `${prefix}: ${context} can not be converted to 'RsaPssParams' because 'saltLength' is required in 'RsaPssParams'.`, 341 }); 342} 343 344// RsaOaepParams 345{ 346 for (const good of [{ name: 'RSA-OAEP' }, { name: 'RSA-OAEP', label: Buffer.alloc(0) }]) { 347 assert.deepStrictEqual(converters.RsaOaepParams({ ...good, filtered: 'out' }, opts), good); 348 } 349} 350 351// EcKeyImportParams, EcKeyGenParams 352{ 353 for (const name of ['EcKeyImportParams', 'EcKeyGenParams']) { 354 const { [name]: converter } = converters; 355 356 const good = { name: 'ECDSA', namedCurve: 'P-256' }; 357 assert.deepStrictEqual(converter({ ...good, filtered: 'out' }, opts), good); 358 359 assert.throws(() => converter({ ...good, namedCurve: undefined }, opts), { 360 name: 'TypeError', 361 code: 'ERR_MISSING_OPTION', 362 message: `${prefix}: ${context} can not be converted to '${name}' because 'namedCurve' is required in '${name}'.`, 363 }); 364 } 365} 366 367// EcdsaParams 368{ 369 for (const good of [ 370 { name: 'ECDSA', hash: { name: 'SHA-1' } }, 371 { name: 'ECDSA', hash: 'SHA-1' }, 372 ]) { 373 assert.deepStrictEqual(converters.EcdsaParams({ ...good, filtered: 'out' }, opts), good); 374 assert.throws(() => converters.EcdsaParams({ ...good, hash: undefined }, opts), { 375 name: 'TypeError', 376 code: 'ERR_MISSING_OPTION', 377 message: `${prefix}: ${context} can not be converted to 'EcdsaParams' because 'hash' is required in 'EcdsaParams'.`, 378 }); 379 } 380} 381 382// HmacKeyGenParams, HmacImportParams 383{ 384 for (const name of ['HmacKeyGenParams', 'HmacImportParams']) { 385 const { [name]: converter } = converters; 386 387 for (const good of [ 388 { name: 'HMAC', hash: { name: 'SHA-1' } }, 389 { name: 'HMAC', hash: { name: 'SHA-1' }, length: 20 }, 390 { name: 'HMAC', hash: 'SHA-1' }, 391 { name: 'HMAC', hash: 'SHA-1', length: 20 }, 392 ]) { 393 assert.deepStrictEqual(converter({ ...good, filtered: 'out' }, opts), good); 394 assert.throws(() => converter({ ...good, hash: undefined }, opts), { 395 name: 'TypeError', 396 code: 'ERR_MISSING_OPTION', 397 message: `${prefix}: ${context} can not be converted to '${name}' because 'hash' is required in '${name}'.`, 398 }); 399 } 400 } 401} 402 403// AesKeyGenParams, AesDerivedKeyParams 404{ 405 for (const name of ['AesKeyGenParams', 'AesDerivedKeyParams']) { 406 const { [name]: converter } = converters; 407 408 const good = { name: 'AES-CBC', length: 128 }; 409 assert.deepStrictEqual(converter({ ...good, filtered: 'out' }, opts), good); 410 411 assert.throws(() => converter({ ...good, length: undefined }, opts), { 412 name: 'TypeError', 413 code: 'ERR_MISSING_OPTION', 414 message: `${prefix}: ${context} can not be converted to '${name}' because 'length' is required in '${name}'.`, 415 }); 416 } 417} 418 419// HkdfParams 420{ 421 for (const good of [ 422 { name: 'HKDF', hash: { name: 'SHA-1' }, salt: Buffer.alloc(0), info: Buffer.alloc(0) }, 423 { name: 'HKDF', hash: 'SHA-1', salt: Buffer.alloc(0), info: Buffer.alloc(0) }, 424 ]) { 425 assert.deepStrictEqual(converters.HkdfParams({ ...good, filtered: 'out' }, opts), good); 426 for (const required of ['hash', 'salt', 'info']) { 427 assert.throws(() => converters.HkdfParams({ ...good, [required]: undefined }, opts), { 428 name: 'TypeError', 429 code: 'ERR_MISSING_OPTION', 430 message: `${prefix}: ${context} can not be converted to 'HkdfParams' because '${required}' is required in 'HkdfParams'.`, 431 }); 432 } 433 } 434} 435 436// Pbkdf2Params 437{ 438 for (const good of [ 439 { name: 'PBKDF2', hash: { name: 'SHA-1' }, iterations: 5, salt: Buffer.alloc(0) }, 440 { name: 'PBKDF2', hash: 'SHA-1', iterations: 5, salt: Buffer.alloc(0) }, 441 ]) { 442 assert.deepStrictEqual(converters.Pbkdf2Params({ ...good, filtered: 'out' }, opts), good); 443 for (const required of ['hash', 'iterations', 'salt']) { 444 assert.throws(() => converters.Pbkdf2Params({ ...good, [required]: undefined }, opts), { 445 name: 'TypeError', 446 code: 'ERR_MISSING_OPTION', 447 message: `${prefix}: ${context} can not be converted to 'Pbkdf2Params' because '${required}' is required in 'Pbkdf2Params'.`, 448 }); 449 } 450 } 451} 452 453// AesCbcParams 454{ 455 const good = { name: 'AES-CBC', iv: Buffer.alloc(0) }; 456 assert.deepStrictEqual(converters.AesCbcParams({ ...good, filtered: 'out' }, opts), good); 457 458 assert.throws(() => converters.AesCbcParams({ ...good, iv: undefined }, opts), { 459 name: 'TypeError', 460 code: 'ERR_MISSING_OPTION', 461 message: `${prefix}: ${context} can not be converted to 'AesCbcParams' because 'iv' is required in 'AesCbcParams'.`, 462 }); 463} 464 465// AesGcmParams 466{ 467 for (const good of [ 468 { name: 'AES-GCM', iv: Buffer.alloc(0) }, 469 { name: 'AES-GCM', iv: Buffer.alloc(0), tagLength: 16 }, 470 { name: 'AES-GCM', iv: Buffer.alloc(0), tagLength: 16, additionalData: Buffer.alloc(0) }, 471 ]) { 472 assert.deepStrictEqual(converters.AesGcmParams({ ...good, filtered: 'out' }, opts), good); 473 474 assert.throws(() => converters.AesGcmParams({ ...good, iv: undefined }, opts), { 475 name: 'TypeError', 476 code: 'ERR_MISSING_OPTION', 477 message: `${prefix}: ${context} can not be converted to 'AesGcmParams' because 'iv' is required in 'AesGcmParams'.`, 478 }); 479 } 480} 481 482// AesCtrParams 483{ 484 const good = { name: 'AES-CTR', counter: Buffer.alloc(0), length: 20 }; 485 assert.deepStrictEqual(converters.AesCtrParams({ ...good, filtered: 'out' }, opts), good); 486 487 for (const required of ['counter', 'length']) { 488 assert.throws(() => converters.AesCtrParams({ ...good, [required]: undefined }, opts), { 489 name: 'TypeError', 490 code: 'ERR_MISSING_OPTION', 491 message: `${prefix}: ${context} can not be converted to 'AesCtrParams' because '${required}' is required in 'AesCtrParams'.`, 492 }); 493 } 494} 495 496// EcdhKeyDeriveParams 497{ 498 subtle.generateKey({ name: 'ECDH', namedCurve: 'P-256' }, false, ['deriveBits']).then((kp) => { 499 const good = { name: 'ECDH', public: kp.publicKey }; 500 assert.deepStrictEqual(converters.EcdhKeyDeriveParams({ ...good, filtered: 'out' }, opts), good); 501 502 assert.throws(() => converters.EcdhKeyDeriveParams({ ...good, public: undefined }, opts), { 503 name: 'TypeError', 504 code: 'ERR_MISSING_OPTION', 505 message: `${prefix}: ${context} can not be converted to 'EcdhKeyDeriveParams' because 'public' is required in 'EcdhKeyDeriveParams'.`, 506 }); 507 }).then(common.mustCall()); 508} 509 510// Ed448Params 511{ 512 for (const good of [ 513 { name: 'Ed448', context: new Uint8Array() }, 514 { name: 'Ed448' }, 515 ]) { 516 assert.deepStrictEqual(converters.Ed448Params({ ...good, filtered: 'out' }, opts), good); 517 } 518} 519