From 9952d1f43e60e5da52e58d5e7a4748232657c1f3 Mon Sep 17 00:00:00 2001 From: Filip Skokan Date: Sat, 2 Jul 2022 18:42:30 +0200 Subject: [PATCH 01/17] crypto: pass all webcrypto WPTs --- lib/internal/crypto/aes.js | 12 +- lib/internal/crypto/ec.js | 29 +- lib/internal/crypto/hkdf.js | 4 +- lib/internal/crypto/keys.js | 3 - lib/internal/crypto/mac.js | 3 - lib/internal/crypto/pbkdf2.js | 10 +- lib/internal/crypto/util.js | 6 +- lib/internal/crypto/webcrypto.js | 3 - src/crypto/crypto_hkdf.cc | 39 +- src/crypto/crypto_keys.cc | 1 - test/fixtures/wpt/README.md | 2 +- ...algorithm-discards-context.https.window.js | 213 +++++++++ .../cfrg_curves_bits.https.any.js | 9 + .../derive_bits_keys/cfrg_curves_bits.js | 245 ++++++++++ .../cfrg_curves_keys.https.any.js | 9 + .../derive_bits_keys/cfrg_curves_keys.js | 213 +++++++++ .../wpt/WebCryptoAPI/generateKey/failures.js | 6 +- .../generateKey/failures_Ed25519.https.any.js | 5 + .../generateKey/failures_Ed448.https.any.js | 5 + .../generateKey/failures_X25519.https.any.js | 5 + .../generateKey/failures_X448.https.any.js | 5 + .../wpt/WebCryptoAPI/generateKey/successes.js | 6 +- .../successes_Ed25519.https.any.js | 6 + .../generateKey/successes_Ed448.https.any.js | 6 + .../generateKey/successes_X25519.https.any.js | 6 + .../generateKey/successes_X448.https.any.js | 6 + .../wpt/WebCryptoAPI/getRandomValues.any.js | 18 +- .../import_export/okp_importKey.https.any.js | 282 ++++++++++++ .../wpt/WebCryptoAPI/randomUUID.https.any.js | 2 +- .../sign_verify/eddsa.https.any.js | 6 + .../wpt/WebCryptoAPI/sign_verify/eddsa.js | 422 ++++++++++++++++++ .../WebCryptoAPI/sign_verify/eddsa_vectors.js | 58 +++ .../fixtures/wpt/WebCryptoAPI/util/helpers.js | 13 +- test/fixtures/wpt/versions.json | 2 +- test/parallel/test-crypto-key-objects.js | 12 - .../test-webcrypto-derivebits-hkdf.js | 9 +- .../test-webcrypto-encrypt-decrypt-aes.js | 2 +- .../test-webcrypto-encrypt-decrypt-rsa.js | 2 +- test/parallel/test-webcrypto-export-import.js | 9 - test/parallel/test-webcrypto-keygen.js | 6 +- .../test-webcrypto-derivebits-pbkdf2.js | 9 +- test/wpt/status/WebCryptoAPI.json | 87 +--- test/wpt/test-webcrypto.js | 4 + 43 files changed, 1631 insertions(+), 169 deletions(-) create mode 100644 test/fixtures/wpt/WebCryptoAPI/algorithm-discards-context.https.window.js create mode 100644 test/fixtures/wpt/WebCryptoAPI/derive_bits_keys/cfrg_curves_bits.https.any.js create mode 100644 test/fixtures/wpt/WebCryptoAPI/derive_bits_keys/cfrg_curves_bits.js create mode 100644 test/fixtures/wpt/WebCryptoAPI/derive_bits_keys/cfrg_curves_keys.https.any.js create mode 100644 test/fixtures/wpt/WebCryptoAPI/derive_bits_keys/cfrg_curves_keys.js create mode 100644 test/fixtures/wpt/WebCryptoAPI/generateKey/failures_Ed25519.https.any.js create mode 100644 test/fixtures/wpt/WebCryptoAPI/generateKey/failures_Ed448.https.any.js create mode 100644 test/fixtures/wpt/WebCryptoAPI/generateKey/failures_X25519.https.any.js create mode 100644 test/fixtures/wpt/WebCryptoAPI/generateKey/failures_X448.https.any.js create mode 100644 test/fixtures/wpt/WebCryptoAPI/generateKey/successes_Ed25519.https.any.js create mode 100644 test/fixtures/wpt/WebCryptoAPI/generateKey/successes_Ed448.https.any.js create mode 100644 test/fixtures/wpt/WebCryptoAPI/generateKey/successes_X25519.https.any.js create mode 100644 test/fixtures/wpt/WebCryptoAPI/generateKey/successes_X448.https.any.js create mode 100644 test/fixtures/wpt/WebCryptoAPI/import_export/okp_importKey.https.any.js create mode 100644 test/fixtures/wpt/WebCryptoAPI/sign_verify/eddsa.https.any.js create mode 100644 test/fixtures/wpt/WebCryptoAPI/sign_verify/eddsa.js create mode 100644 test/fixtures/wpt/WebCryptoAPI/sign_verify/eddsa_vectors.js diff --git a/lib/internal/crypto/aes.js b/lib/internal/crypto/aes.js index 324662e1f8b1b4..48cde6b31972e0 100644 --- a/lib/internal/crypto/aes.js +++ b/lib/internal/crypto/aes.js @@ -60,11 +60,6 @@ const { generateKey, } = require('internal/crypto/keygen'); -const { - validateInteger, - validateOneOf, -} = require('internal/validators'); - const kMaxCounterLength = 128; const kTagLengths = [32, 64, 96, 104, 112, 120, 128]; @@ -227,8 +222,11 @@ function aesCipher(mode, key, data, algorithm) { async function aesGenerateKey(algorithm, extractable, keyUsages) { const { name, length } = algorithm; - validateInteger(length, 'algorithm.length'); - validateOneOf(length, 'algorithm.length', kAesKeyLengths); + if (!ArrayPrototypeIncludes(kAesKeyLengths, length)) { + throw lazyDOMException( + 'AES key length must be 128, 192, or 256 bits', + 'OperationError'); + } const checkUsages = ['wrapKey', 'unwrapKey']; if (name !== 'AES-KW') diff --git a/lib/internal/crypto/ec.js b/lib/internal/crypto/ec.js index 809f531069bf8b..8741328dd3aa68 100644 --- a/lib/internal/crypto/ec.js +++ b/lib/internal/crypto/ec.js @@ -1,6 +1,7 @@ 'use strict'; const { + ArrayPrototypeIncludes, ObjectKeys, Promise, SafeSet, @@ -17,11 +18,6 @@ const { kSigEncP1363, } = internalBinding('crypto'); -const { - validateOneOf, - validateString, -} = require('internal/validators'); - const { codes: { ERR_MISSING_OPTION, @@ -88,11 +84,12 @@ function createECPublicKeyRaw(namedCurve, keyData) { async function ecGenerateKey(algorithm, extractable, keyUsages) { const { name, namedCurve } = algorithm; - validateString(namedCurve, 'algorithm.namedCurve'); - validateOneOf( - namedCurve, - 'algorithm.namedCurve', - ObjectKeys(kNamedCurveAliases)); + + if (!ArrayPrototypeIncludes(ObjectKeys(kNamedCurveAliases), namedCurve)) { + throw lazyDOMException( + 'Unrecognized namedCurve', + 'NotSupportedError'); + } const usageSet = new SafeSet(keyUsages); switch (name) { @@ -168,11 +165,13 @@ async function ecImportKey( keyUsages) { const { name, namedCurve } = algorithm; - validateString(namedCurve, 'algorithm.namedCurve'); - validateOneOf( - namedCurve, - 'algorithm.namedCurve', - ObjectKeys(kNamedCurveAliases)); + + if (!ArrayPrototypeIncludes(ObjectKeys(kNamedCurveAliases), namedCurve)) { + throw lazyDOMException( + 'Unrecognized namedCurve', + 'NotSupportedError'); + } + let keyObject; const usagesSet = new SafeSet(keyUsages); switch (format) { diff --git a/lib/internal/crypto/hkdf.js b/lib/internal/crypto/hkdf.js index 724217c08eb2f4..7764eb83469e4d 100644 --- a/lib/internal/crypto/hkdf.js +++ b/lib/internal/crypto/hkdf.js @@ -142,7 +142,6 @@ function hkdfSync(hash, key, salt, info, length) { } async function hkdfDeriveBits(algorithm, baseKey, length) { - validateUint32(length, 'length'); const { hash } = algorithm; const salt = getArrayBufferOrView(algorithm.salt, 'algorithm.salt'); const info = getArrayBufferOrView(algorithm.info, 'algorithm.info'); @@ -153,6 +152,9 @@ async function hkdfDeriveBits(algorithm, baseKey, length) { if (length !== undefined) { if (length === 0) throw lazyDOMException('length cannot be zero', 'OperationError'); + if (length === null) + throw lazyDOMException('length cannot be null', 'OperationError'); + validateUint32(length, 'length'); if (length % 8) { throw lazyDOMException( 'length must be a multiple of 8', diff --git a/lib/internal/crypto/keys.js b/lib/internal/crypto/keys.js index 5d00727813250a..e4c8d5a04595a3 100644 --- a/lib/internal/crypto/keys.js +++ b/lib/internal/crypto/keys.js @@ -38,7 +38,6 @@ const { ERR_ILLEGAL_CONSTRUCTOR, ERR_INVALID_ARG_TYPE, ERR_INVALID_ARG_VALUE, - ERR_OUT_OF_RANGE, } } = require('internal/errors'); @@ -588,8 +587,6 @@ function prepareSecretKey(key, encoding, bufferOnly = false) { function createSecretKey(key, encoding) { key = prepareSecretKey(key, encoding, true); - if (key.byteLength === 0) - throw new ERR_OUT_OF_RANGE('key.byteLength', '> 0', key.byteLength); const handle = new KeyObjectHandle(); handle.init(kKeyTypeSecret, key); return new SecretKeyObject(handle); diff --git a/lib/internal/crypto/mac.js b/lib/internal/crypto/mac.js index 3054f66b27d4f2..310d0538eaaf06 100644 --- a/lib/internal/crypto/mac.js +++ b/lib/internal/crypto/mac.js @@ -100,9 +100,6 @@ async function hmacImportKey( case 'raw': { const checkLength = keyData.byteLength * 8; - if (checkLength === 0 || algorithm.length === 0) - throw lazyDOMException('Zero-length key is not supported', 'DataError'); - // The Web Crypto spec allows for key lengths that are not multiples of // 8. We don't. Our check here is stricter than that defined by the spec // in that we require that algorithm.length match keyData.length * 8 if diff --git a/lib/internal/crypto/pbkdf2.js b/lib/internal/crypto/pbkdf2.js index b3a6303136f506..986ea557d63bf3 100644 --- a/lib/internal/crypto/pbkdf2.js +++ b/lib/internal/crypto/pbkdf2.js @@ -98,13 +98,16 @@ function check(password, salt, iterations, keylen, digest) { } async function pbkdf2DeriveBits(algorithm, baseKey, length) { - validateUint32(length, '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', 1); + validateInteger(iterations, 'algorithm.iterations'); + if (iterations === 0) + throw lazyDOMException( + 'iterations cannot be zero', + 'OperationError'); hash = normalizeHashName(hash.name); @@ -114,6 +117,9 @@ async function pbkdf2DeriveBits(algorithm, baseKey, length) { if (length !== undefined) { if (length === 0) throw lazyDOMException('length cannot be zero', 'OperationError'); + if (length === null) + throw lazyDOMException('length cannot be null', 'OperationError'); + validateUint32(length, 'length'); if (length % 8) { throw lazyDOMException( 'length must be a multiple of 8', diff --git a/lib/internal/crypto/util.js b/lib/internal/crypto/util.js index 40a677606bf3f0..1e042d0c74406d 100644 --- a/lib/internal/crypto/util.js +++ b/lib/internal/crypto/util.js @@ -278,7 +278,11 @@ const validateByteSource = hideStackFrames((val, name) => { }); function onDone(resolve, reject, err, result) { - if (err) return reject(err); + if (err) { + return reject(lazyDOMException( + 'The operation failed for an operation-specific reason', + 'OperationError')); + } resolve(result); } diff --git a/lib/internal/crypto/webcrypto.js b/lib/internal/crypto/webcrypto.js index b7760efaa234c8..df4bb072043def 100644 --- a/lib/internal/crypto/webcrypto.js +++ b/lib/internal/crypto/webcrypto.js @@ -494,9 +494,6 @@ async function importGenericSecretKey( const checkLength = keyData.byteLength * 8; - if (checkLength === 0 || length === 0) - throw lazyDOMException('Zero-length key is not supported', 'DataError'); - // The Web Crypto spec allows for key lengths that are not multiples of // 8. We don't. Our check here is stricter than that defined by the spec // in that we require that algorithm.length match keyData.length * 8 if diff --git a/src/crypto/crypto_hkdf.cc b/src/crypto/crypto_hkdf.cc index 79a84c12f55853..73936712115e11 100644 --- a/src/crypto/crypto_hkdf.cc +++ b/src/crypto/crypto_hkdf.cc @@ -103,20 +103,43 @@ bool HKDFTraits::DeriveBits( EVPKeyCtxPointer ctx = EVPKeyCtxPointer(EVP_PKEY_CTX_new_id(EVP_PKEY_HKDF, nullptr)); if (!ctx || !EVP_PKEY_derive_init(ctx.get()) || - !EVP_PKEY_CTX_hkdf_mode(ctx.get(), - EVP_PKEY_HKDEF_MODE_EXTRACT_AND_EXPAND) || !EVP_PKEY_CTX_set_hkdf_md(ctx.get(), params.digest) || - !EVP_PKEY_CTX_set1_hkdf_salt( - ctx.get(), params.salt.data(), params.salt.size()) || - !EVP_PKEY_CTX_set1_hkdf_key( - ctx.get(), - reinterpret_cast(params.key->GetSymmetricKey()), - params.key->GetSymmetricKeySize()) || !EVP_PKEY_CTX_add1_hkdf_info( ctx.get(), params.info.data(), params.info.size())) { return false; } + if (params.key->GetSymmetricKeySize() != 0) { + if (!EVP_PKEY_CTX_hkdf_mode(ctx.get(), + EVP_PKEY_HKDEF_MODE_EXTRACT_AND_EXPAND) || + !EVP_PKEY_CTX_set1_hkdf_salt( + ctx.get(), params.salt.data(), params.salt.size()) || + !EVP_PKEY_CTX_set1_hkdf_key(ctx.get(), + reinterpret_cast( + params.key->GetSymmetricKey()), + params.key->GetSymmetricKeySize())) { + return false; + } + } else { + unsigned int len = EVP_MD_size(params.digest); + uint8_t tempKey[len]; // NOLINT(runtime/arrays) + if (params.salt.size()) { + HMAC(params.digest, + params.salt.data(), + params.salt.size(), + nullptr, + 0, + tempKey, + &len); + } else { + HMAC(params.digest, new char[len]{}, len, nullptr, 0, tempKey, &len); + } + if (!EVP_PKEY_CTX_hkdf_mode(ctx.get(), EVP_PKEY_HKDEF_MODE_EXPAND_ONLY) || + !EVP_PKEY_CTX_set1_hkdf_key(ctx.get(), tempKey, len)) { + return false; + } + } + size_t length = params.length; ByteSource::Builder buf(length); if (EVP_PKEY_derive(ctx.get(), buf.data(), &length) <= 0) diff --git a/src/crypto/crypto_keys.cc b/src/crypto/crypto_keys.cc index ba37f24c48dad5..75e54d8abb988e 100644 --- a/src/crypto/crypto_keys.cc +++ b/src/crypto/crypto_keys.cc @@ -871,7 +871,6 @@ void KeyObjectData::MemoryInfo(MemoryTracker* tracker) const { } std::shared_ptr KeyObjectData::CreateSecret(ByteSource key) { - CHECK(key); return std::shared_ptr(new KeyObjectData(std::move(key))); } diff --git a/test/fixtures/wpt/README.md b/test/fixtures/wpt/README.md index bf5539e384a777..158858431e3a98 100644 --- a/test/fixtures/wpt/README.md +++ b/test/fixtures/wpt/README.md @@ -29,7 +29,7 @@ Last update: - user-timing: https://github.com/web-platform-tests/wpt/tree/df24fb604e/user-timing - wasm/jsapi: https://github.com/web-platform-tests/wpt/tree/1dd414c796/wasm/jsapi - wasm/webapi: https://github.com/web-platform-tests/wpt/tree/fd1b23eeaa/wasm/webapi -- WebCryptoAPI: https://github.com/web-platform-tests/wpt/tree/cdd0f03df4/WebCryptoAPI +- WebCryptoAPI: https://github.com/web-platform-tests/wpt/tree/edca84af42/WebCryptoAPI - webidl/ecmascript-binding/es-exceptions: https://github.com/web-platform-tests/wpt/tree/a370aad338/webidl/ecmascript-binding/es-exceptions [Web Platform Tests]: https://github.com/web-platform-tests/wpt diff --git a/test/fixtures/wpt/WebCryptoAPI/algorithm-discards-context.https.window.js b/test/fixtures/wpt/WebCryptoAPI/algorithm-discards-context.https.window.js new file mode 100644 index 00000000000000..e648bc2f230f0c --- /dev/null +++ b/test/fixtures/wpt/WebCryptoAPI/algorithm-discards-context.https.window.js @@ -0,0 +1,213 @@ +// META: title=WebCryptoAPI: Properties discard the context in algorithm normalization + +let nextTest = 0; +let tests = {}; +function closeChild(testId) { + if (tests[testId]) { + let {child, t} = tests[testId]; + delete tests[testId]; + document.body.removeChild(child); + t.done(); + } +} + +function runInChild(t, childScript) { + let testId = nextTest++; + const preamble = ` +let testId = ${testId}; +function closeChildOnAccess(obj, key) { + const oldValue = obj[key]; + Object.defineProperty(obj, key, {get: () => { + top.closeChild(testId); + return oldValue; + }}); +} +`; + childScript = preamble + childScript; + + let child = document.createElement("iframe"); + tests[testId] = {t, child}; + document.body.appendChild(child); + let script = document.createElement("script"); + script.textContent = childScript; + child.contentDocument.body.appendChild(script); +} + +async_test((t) => { + const childScript = ` +let algorithm = {name: "AES-GCM", length: 128}; +closeChildOnAccess(algorithm, "name"); +crypto.subtle.generateKey(algorithm, true, ["encrypt", "decrypt"]);`; + runInChild(t, childScript); +}, "Context is discarded in generateKey"); + +async_test((t) => { + const childScript = ` +let algorithm = {name: "AES-GCM"}; +closeChildOnAccess(algorithm, "name"); +crypto.subtle.importKey("raw", new Uint8Array(16), algorithm, true, + ["encrypt", "decrypt"]);`; + runInChild(t, childScript); +}, "Context is discarded in importKey"); + +async_test((t) => { + const childScript = ` +(async () => { + let key = await crypto.subtle.generateKey( + {name: "AES-GCM", length: 128}, true, ["encrypt", "decrypt"]); + let algorithm = {name: "AES-GCM", iv: new Uint8Array(12)}; + closeChildOnAccess(algorithm, "name"); + crypto.subtle.encrypt(algorithm, key, new Uint8Array()); +})();`; + runInChild(t, childScript); +}, "Context is discarded in encrypt"); + +async_test((t) => { + const childScript = ` +(async () => { + let key = await crypto.subtle.generateKey( + {name: "AES-GCM", length: 128}, true, ["encrypt", "decrypt"]); + let algorithm = {name: "AES-GCM", iv: new Uint8Array(12)}; + let encrypted = await crypto.subtle.encrypt(algorithm, key, new Uint8Array()); + closeChildOnAccess(algorithm, "name"); + crypto.subtle.decrypt(algorithm, key, encrypted); +})();`; + runInChild(t, childScript); +}, "Context is discarded in decrypt"); + +async_test((t) => { + const childScript = ` +let algorithm = {name: "SHA-256"}; +closeChildOnAccess(algorithm, "name"); +crypto.subtle.digest(algorithm, new Uint8Array());`; + runInChild(t, childScript); +}, "Context is discarded in digest"); + +async_test((t) => { + const childScript = ` +(async () => { + let key = await crypto.subtle.generateKey( + {name: "ECDSA", namedCurve: "P-256"}, true, ["sign", "verify"]); + let algorithm = {name: "ECDSA", hash: "SHA-256"}; + closeChildOnAccess(algorithm, "name"); + crypto.subtle.sign(algorithm, key.privateKey, new Uint8Array()); +})();`; + runInChild(t, childScript); +}, "Context is discarded in sign"); + +async_test((t) => { + const childScript = ` +(async () => { + let key = await crypto.subtle.generateKey( + {name: "ECDSA", namedCurve: "P-256"}, true, ["sign", "verify"]); + let algorithm = {name: "ECDSA", hash: "SHA-256"}; + let data = new Uint8Array(); + let signature = await crypto.subtle.sign(algorithm, key.privateKey, data); + closeChildOnAccess(algorithm, "name"); + crypto.subtle.verify(algorithm, key.publicKey, signature, data); +})();`; + runInChild(t, childScript); +}, "Context is discarded in verify"); + +async_test((t) => { + const childScript = ` +(async () => { + let key = await crypto.subtle.importKey( + "raw", new Uint8Array(16), "HKDF", false, ["deriveBits"]); + let algorithm = { + name: "HKDF", + hash: "SHA-256", + salt: new Uint8Array(), + info: new Uint8Array(), + }; + closeChildOnAccess(algorithm, "name"); + crypto.subtle.deriveBits(algorithm, key, 16); +})();`; + runInChild(t, childScript); +}, "Context is discarded in deriveBits"); + +async_test((t) => { + const childScript = ` +(async () => { + let key = await crypto.subtle.importKey( + "raw", new Uint8Array(16), "HKDF", false, ["deriveKey"]); + let algorithm = { + name: "HKDF", + hash: "SHA-256", + salt: new Uint8Array(), + info: new Uint8Array(), + }; + let derivedAlgorithm = {name: "AES-GCM", length: 128}; + closeChildOnAccess(algorithm, "name"); + crypto.subtle.deriveKey(algorithm, key, derivedAlgorithm, true, + ["encrypt", "decrypt"]); +})();`; + runInChild(t, childScript); +}, "Context is discarded in deriveKey"); + +async_test((t) => { + const childScript = ` +(async () => { + let key = await crypto.subtle.importKey( + "raw", new Uint8Array(16), "HKDF", false, ["deriveKey"]); + let algorithm = { + name: "HKDF", + hash: "SHA-256", + salt: new Uint8Array(), + info: new Uint8Array(), + }; + let derivedAlgorithm = {name: "AES-GCM", length: 128}; + closeChildOnAccess(derivedAlgorithm, "name"); + crypto.subtle.deriveKey(algorithm, key, derivedAlgorithm, true, + ["encrypt", "decrypt"]); +})();`; + runInChild(t, childScript); +}, "Context is discarded in deriveKey (2)"); + +async_test((t) => { + const childScript = ` +(async () => { + let wrapKey = await crypto.subtle.generateKey( + {name: "AES-GCM", length: 128}, true, ["wrapKey", "unwrapKey"]); + let key = await crypto.subtle.generateKey( + {name: "AES-GCM", length: 128}, true, ["encrypt", "decrypt"]); + let wrapAlgorithm = {name: "AES-GCM", iv: new Uint8Array(12)}; + closeChildOnAccess(wrapAlgorithm, "name"); + crypto.subtle.wrapKey("raw", key, wrapKey, wrapAlgorithm); +})();`; + runInChild(t, childScript); +}, "Context is discarded in wrapKey"); + +async_test((t) => { + const childScript = ` +(async () => { + let wrapKey = await crypto.subtle.generateKey( + {name: "AES-GCM", length: 128}, true, ["wrapKey", "unwrapKey"]); + let keyAlgorithm = {name: "AES-GCM", length: 128}; + let keyUsages = ["encrypt", "decrypt"]; + let key = await crypto.subtle.generateKey(keyAlgorithm, true, keyUsages); + let wrapAlgorithm = {name: "AES-GCM", iv: new Uint8Array(12)}; + let wrapped = await crypto.subtle.wrapKey("raw", key, wrapKey, wrapAlgorithm); + closeChildOnAccess(wrapAlgorithm, "name"); + crypto.subtle.unwrapKey( + "raw", wrapped, wrapKey, wrapAlgorithm, keyAlgorithm, true, keyUsages); +})();`; + runInChild(t, childScript); +}, "Context is discarded in unwrapKey"); + +async_test((t) => { + const childScript = ` +(async () => { + let wrapKey = await crypto.subtle.generateKey( + {name: "AES-GCM", length: 128}, true, ["wrapKey", "unwrapKey"]); + let keyAlgorithm = {name: "AES-GCM", length: 128}; + let keyUsages = ["encrypt", "decrypt"]; + let key = await crypto.subtle.generateKey(keyAlgorithm, true, keyUsages); + let wrapAlgorithm = {name: "AES-GCM", iv: new Uint8Array(12)}; + let wrapped = await crypto.subtle.wrapKey("raw", key, wrapKey, wrapAlgorithm); + closeChildOnAccess(keyAlgorithm, "name"); + crypto.subtle.unwrapKey( + "raw", wrapped, wrapKey, wrapAlgorithm, keyAlgorithm, true, keyUsages); +})();`; + runInChild(t, childScript); +}, "Context is discarded in unwrapKey (2)"); diff --git a/test/fixtures/wpt/WebCryptoAPI/derive_bits_keys/cfrg_curves_bits.https.any.js b/test/fixtures/wpt/WebCryptoAPI/derive_bits_keys/cfrg_curves_bits.https.any.js new file mode 100644 index 00000000000000..afa7db845faaee --- /dev/null +++ b/test/fixtures/wpt/WebCryptoAPI/derive_bits_keys/cfrg_curves_bits.https.any.js @@ -0,0 +1,9 @@ +// META: title=WebCryptoAPI: deriveBits() Using ECDH with CFRG Elliptic Curves +// META: script=cfrg_curves_bits.js + +// Define subtests from a `promise_test` to ensure the harness does not +// complete before the subtests are available. `explicit_done` cannot be used +// for this purpose because the global `done` function is automatically invoked +// by the WPT infrastructure in dedicated worker tests defined using the +// "multi-global" pattern. +promise_test(define_tests, 'setup - define tests'); diff --git a/test/fixtures/wpt/WebCryptoAPI/derive_bits_keys/cfrg_curves_bits.js b/test/fixtures/wpt/WebCryptoAPI/derive_bits_keys/cfrg_curves_bits.js new file mode 100644 index 00000000000000..f467bc858cbba9 --- /dev/null +++ b/test/fixtures/wpt/WebCryptoAPI/derive_bits_keys/cfrg_curves_bits.js @@ -0,0 +1,245 @@ + +function define_tests() { + // May want to test prefixed implementations. + var subtle = self.crypto.subtle; + + var pkcs8 = { + "X25519": new Uint8Array([48, 46, 2, 1, 0, 48, 5, 6, 3, 43, 101, 110, 4, 34, 4, 32, 200, 131, 142, 118, 208, 87, 223, 183, 216, 201, 90, 105, 225, 56, 22, 10, 221, 99, 115, 253, 113, 164, 210, 118, 187, 86, 227, 168, 27, 100, 255, 97]), + "X448": new Uint8Array([48, 70, 2, 1, 0, 48, 5, 6, 3, 43, 101, 111, 4, 58, 4, 56, 88, 199, 210, 154, 62, 181, 25, 178, 157, 0, 207, 177, 145, 187, 100, 252, 109, 138, 66, 216, 241, 113, 118, 39, 43, 137, 242, 39, 45, 24, 25, 41, 92, 101, 37, 192, 130, 150, 113, 176, 82, 239, 7, 39, 83, 15, 24, 142, 49, 208, 204, 83, 191, 38, 146, 158]) + }; + + var spki = { + "X25519": new Uint8Array([48, 42, 48, 5, 6, 3, 43, 101, 110, 3, 33, 0, 28, 242, 177, 230, 2, 46, 197, 55, 55, 30, 215, 245, 62, 84, 250, 17, 84, 216, 62, 152, 235, 100, 234, 81, 250, 229, 179, 48, 124, 254, 151, 6]), + "X448": new Uint8Array([48, 66, 48, 5, 6, 3, 43, 101, 111, 3, 57, 0, 182, 4, 161, 209, 165, 205, 29, 148, 38, 213, 97, 239, 99, 10, 158, 177, 108, 190, 105, 213, 185, 202, 97, 94, 220, 83, 99, 62, 251, 82, 234, 49, 230, 230, 160, 161, 219, 172, 198, 231, 108, 188, 230, 72, 45, 126, 75, 163, 213, 93, 158, 128, 39, 101, 206, 111]) + }; + + var sizes = { + "X25519": 32, + "X448": 56 + }; + + var derivations = { + "X25519": new Uint8Array([39, 104, 64, 157, 250, 185, 158, 194, 59, 140, 137, 185, 63, 245, 136, 2, 149, 247, 97, 118, 8, 143, 137, 228, 61, 254, 190, 126, 161, 149, 0, 8]), + "X448": new Uint8Array([240, 246, 197, 241, 127, 148, 244, 41, 30, 171, 113, 120, 134, 109, 55, 236, 137, 6, 221, 108, 81, 65, 67, 220, 133, 190, 124, 242, 141, 239, 243, 155, 114, 110, 15, 109, 207, 129, 14, 181, 148, 220, 169, 123, 72, 130, 189, 68, 196, 62, 167, 220, 103, 244, 154, 78]) + }; + + return importKeys(pkcs8, spki, sizes) + .then(function(results) { + publicKeys = results.publicKeys; + privateKeys = results.privateKeys; + noDeriveBitsKeys = results.noDeriveBitsKeys; + + Object.keys(sizes).forEach(function(algorithmName) { + // Basic success case + promise_test(function(test) { + return subtle.deriveBits({name: algorithmName, public: publicKeys[algorithmName]}, privateKeys[algorithmName], 8 * sizes[algorithmName]) + .then(function(derivation) { + assert_true(equalBuffers(derivation, derivations[algorithmName]), "Derived correct bits"); + }, function(err) { + assert_unreached("deriveBits failed with error " + err.name + ": " + err.message); + }); + }, algorithmName + " good parameters"); + + // Case insensitivity check + promise_test(function(test) { + return subtle.deriveBits({name: algorithmName.toLowerCase(), public: publicKeys[algorithmName]}, privateKeys[algorithmName], 8 * sizes[algorithmName]) + .then(function(derivation) { + assert_true(equalBuffers(derivation, derivations[algorithmName]), "Derived correct bits"); + }, function(err) { + assert_unreached("deriveBits failed with error " + err.name + ": " + err.message); + }); + }, algorithmName + " mixed case parameters"); + + // Null length + promise_test(function(test) { + return subtle.deriveBits({name: algorithmName, public: publicKeys[algorithmName]}, privateKeys[algorithmName], null) + .then(function(derivation) { + assert_true(equalBuffers(derivation, derivations[algorithmName]), "Derived correct bits"); + }, function(err) { + assert_unreached("deriveBits failed with error " + err.name + ": " + err.message); + }); + }, algorithmName + " with null length"); + + // Shorter than entire derivation per algorithm + promise_test(function(test) { + return subtle.deriveBits({name: algorithmName, public: publicKeys[algorithmName]}, privateKeys[algorithmName], 8 * sizes[algorithmName] - 32) + .then(function(derivation) { + assert_true(equalBuffers(derivation, derivations[algorithmName], 8 * sizes[algorithmName] - 32), "Derived correct bits"); + }, function(err) { + assert_unreached("deriveBits failed with error " + err.name + ": " + err.message); + }); + }, algorithmName + " short result"); + + // Non-multiple of 8 + promise_test(function(test) { + return subtle.deriveBits({name: algorithmName, public: publicKeys[algorithmName]}, privateKeys[algorithmName], 8 * sizes[algorithmName] - 11) + .then(function(derivation) { + assert_true(equalBuffers(derivation, derivations[algorithmName], 8 * sizes[algorithmName] - 11), "Derived correct bits"); + }, function(err) { + assert_unreached("deriveBits failed with error " + err.name + ": " + err.message); + }); + }, algorithmName + " non-multiple of 8 bits"); + + // Errors to test: + + // - missing public property TypeError + promise_test(function(test) { + return subtle.deriveBits({name: algorithmName}, privateKeys[algorithmName], 8 * sizes[algorithmName]) + .then(function(derivation) { + assert_unreached("deriveBits succeeded but should have failed with TypeError"); + }, function(err) { + assert_equals(err.name, "TypeError", "Should throw correct error, not " + err.name + ": " + err.message); + }); + }, algorithmName + " missing public property"); + + // - Non CryptoKey public property TypeError + promise_test(function(test) { + return subtle.deriveBits({name: algorithmName, public: {message: "Not a CryptoKey"}}, privateKeys[algorithmName], 8 * sizes[algorithmName]) + .then(function(derivation) { + assert_unreached("deriveBits succeeded but should have failed with TypeError"); + }, function(err) { + assert_equals(err.name, "TypeError", "Should throw correct error, not " + err.name + ": " + err.message); + }); + }, algorithmName + " public property of algorithm is not a CryptoKey"); + + // - wrong algorithm + promise_test(function(test) { + publicKey = publicKeys["X25519"]; + if (algorithmName === "X25519") { + publicKey = publicKeys["X448"]; + } + return subtle.deriveBits({name: algorithmName, public: publicKey}, privateKeys[algorithmName], 8 * sizes[algorithmName]) + .then(function(derivation) { + assert_unreached("deriveBits succeeded but should have failed with InvalidAccessError"); + }, function(err) { + assert_equals(err.name, "InvalidAccessError", "Should throw correct error, not " + err.name + ": " + err.message); + }); + }, algorithmName + " mismatched algorithms"); + + // - No deriveBits usage in baseKey InvalidAccessError + promise_test(function(test) { + return subtle.deriveBits({name: algorithmName, public: publicKeys[algorithmName]}, noDeriveBitsKeys[algorithmName], 8 * sizes[algorithmName]) + .then(function(derivation) { + assert_unreached("deriveBits succeeded but should have failed with InvalidAccessError"); + }, function(err) { + assert_equals(err.name, "InvalidAccessError", "Should throw correct error, not " + err.name + ": " + err.message); + }); + }, algorithmName + " no deriveBits usage for base key"); + + // - Use public key for baseKey InvalidAccessError + promise_test(function(test) { + return subtle.deriveBits({name: algorithmName, public: publicKeys[algorithmName]}, publicKeys[algorithmName], 8 * sizes[algorithmName]) + .then(function(derivation) { + assert_unreached("deriveBits succeeded but should have failed with InvalidAccessError"); + }, function(err) { + assert_equals(err.name, "InvalidAccessError", "Should throw correct error, not " + err.name + ": " + err.message); + }); + }, algorithmName + " base key is not a private key"); + + // - Use private key for public property InvalidAccessError + promise_test(function(test) { + return subtle.deriveBits({name: algorithmName, public: privateKeys[algorithmName]}, privateKeys[algorithmName], 8 * sizes[algorithmName]) + .then(function(derivation) { + assert_unreached("deriveBits succeeded but should have failed with InvalidAccessError"); + }, function(err) { + assert_equals(err.name, "InvalidAccessError", "Should throw correct error, not " + err.name + ": " + err.message); + }); + }, algorithmName + " public property value is a private key"); + + // - Use secret key for public property InvalidAccessError + promise_test(function(test) { + return subtle.generateKey({name: "AES-CBC", length: 128}, true, ["encrypt", "decrypt"]) + .then(function(secretKey) { + subtle.deriveBits({name: algorithmName, public: secretKey}, privateKeys[algorithmName], 8 * sizes[algorithmName]) + .then(function(derivation) { + assert_unreached("deriveBits succeeded but should have failed with InvalidAccessError"); + }, function(err) { + assert_equals(err.name, "InvalidAccessError", "Should throw correct error, not " + err.name + ": " + err.message); + }); + }); + }, algorithmName + " public property value is a secret key"); + + // - Length greater than possible for particular curves OperationError + promise_test(function(test) { + return subtle.deriveBits({name: algorithmName, public: publicKeys[algorithmName]}, privateKeys[algorithmName], 8 * sizes[algorithmName] + 8) + .then(function(derivation) { + assert_unreached("deriveBits succeeded but should have failed with OperationError"); + }, function(err) { + assert_equals(err.name, "OperationError", "Should throw correct error, not " + err.name + ": " + err.message); + }); + }, algorithmName + " asking for too many bits"); + }); + }); + + function importKeys(pkcs8, spki, sizes) { + var privateKeys = {}; + var publicKeys = {}; + var noDeriveBitsKeys = {}; + + var promises = []; + Object.keys(pkcs8).forEach(function(algorithmName) { + var operation = subtle.importKey("pkcs8", pkcs8[algorithmName], + {name: algorithmName}, + false, ["deriveBits", "deriveKey"]) + .then(function(key) { + privateKeys[algorithmName] = key; + }); + promises.push(operation); + }); + Object.keys(pkcs8).forEach(function(algorithmName) { + var operation = subtle.importKey("pkcs8", pkcs8[algorithmName], + {name: algorithmName}, + false, ["deriveKey"]) + .then(function(key) { + noDeriveBitsKeys[algorithmName] = key; + }); + promises.push(operation); + }); + Object.keys(spki).forEach(function(algorithmName) { + var operation = subtle.importKey("spki", spki[algorithmName], + {name: algorithmName}, + false, []) + .then(function(key) { + publicKeys[algorithmName] = key; + }); + promises.push(operation); + }); + + return Promise.all(promises) + .then(function(results) {return {privateKeys: privateKeys, publicKeys: publicKeys, noDeriveBitsKeys: noDeriveBitsKeys}}); + } + + // Compares two ArrayBuffer or ArrayBufferView objects. If bitCount is + // omitted, the two values must be the same length and have the same contents + // in every byte. If bitCount is included, only that leading number of bits + // have to match. + function equalBuffers(a, b, bitCount) { + var remainder; + + if (typeof bitCount === "undefined" && a.byteLength !== b.byteLength) { + return false; + } + + var aBytes = new Uint8Array(a); + var bBytes = new Uint8Array(b); + + var length = a.byteLength; + if (typeof bitCount !== "undefined") { + length = Math.floor(bitCount / 8); + } + + for (var i=0; i> (8 - remainder) === bBytes[length] >> (8 - remainder); + } + + return true; + } + +} diff --git a/test/fixtures/wpt/WebCryptoAPI/derive_bits_keys/cfrg_curves_keys.https.any.js b/test/fixtures/wpt/WebCryptoAPI/derive_bits_keys/cfrg_curves_keys.https.any.js new file mode 100644 index 00000000000000..71fe87d9a874b7 --- /dev/null +++ b/test/fixtures/wpt/WebCryptoAPI/derive_bits_keys/cfrg_curves_keys.https.any.js @@ -0,0 +1,9 @@ +// META: title=WebCryptoAPI: deriveKey() Using ECDH with CFRG Elliptic Curves +// META: script=cfrg_curves_keys.js + +// Define subtests from a `promise_test` to ensure the harness does not +// complete before the subtests are available. `explicit_done` cannot be used +// for this purpose because the global `done` function is automatically invoked +// by the WPT infrastructure in dedicated worker tests defined using the +// "multi-global" pattern. +promise_test(define_tests, 'setup - define tests'); diff --git a/test/fixtures/wpt/WebCryptoAPI/derive_bits_keys/cfrg_curves_keys.js b/test/fixtures/wpt/WebCryptoAPI/derive_bits_keys/cfrg_curves_keys.js new file mode 100644 index 00000000000000..7819ae0bf83b64 --- /dev/null +++ b/test/fixtures/wpt/WebCryptoAPI/derive_bits_keys/cfrg_curves_keys.js @@ -0,0 +1,213 @@ + +function define_tests() { + // May want to test prefixed implementations. + var subtle = self.crypto.subtle; + + var pkcs8 = { + "X25519": new Uint8Array([48, 46, 2, 1, 0, 48, 5, 6, 3, 43, 101, 110, 4, 34, 4, 32, 200, 131, 142, 118, 208, 87, 223, 183, 216, 201, 90, 105, 225, 56, 22, 10, 221, 99, 115, 253, 113, 164, 210, 118, 187, 86, 227, 168, 27, 100, 255, 97]), + "X448": new Uint8Array([48, 70, 2, 1, 0, 48, 5, 6, 3, 43, 101, 111, 4, 58, 4, 56, 88, 199, 210, 154, 62, 181, 25, 178, 157, 0, 207, 177, 145, 187, 100, 252, 109, 138, 66, 216, 241, 113, 118, 39, 43, 137, 242, 39, 45, 24, 25, 41, 92, 101, 37, 192, 130, 150, 113, 176, 82, 239, 7, 39, 83, 15, 24, 142, 49, 208, 204, 83, 191, 38, 146, 158]) + }; + + var spki = { + "X25519": new Uint8Array([48, 42, 48, 5, 6, 3, 43, 101, 110, 3, 33, 0, 28, 242, 177, 230, 2, 46, 197, 55, 55, 30, 215, 245, 62, 84, 250, 17, 84, 216, 62, 152, 235, 100, 234, 81, 250, 229, 179, 48, 124, 254, 151, 6]), + "X448": new Uint8Array([48, 66, 48, 5, 6, 3, 43, 101, 111, 3, 57, 0, 182, 4, 161, 209, 165, 205, 29, 148, 38, 213, 97, 239, 99, 10, 158, 177, 108, 190, 105, 213, 185, 202, 97, 94, 220, 83, 99, 62, 251, 82, 234, 49, 230, 230, 160, 161, 219, 172, 198, 231, 108, 188, 230, 72, 45, 126, 75, 163, 213, 93, 158, 128, 39, 101, 206, 111]) + }; + + var sizes = { + "X25519": 32, + "X448": 56 + }; + + var derivations = { + "X25519": new Uint8Array([39, 104, 64, 157, 250, 185, 158, 194, 59, 140, 137, 185, 63, 245, 136, 2, 149, 247, 97, 118, 8, 143, 137, 228, 61, 254, 190, 126, 161, 149, 0, 8]), + "X448": new Uint8Array([240, 246, 197, 241, 127, 148, 244, 41, 30, 171, 113, 120, 134, 109, 55, 236, 137, 6, 221, 108, 81, 65, 67, 220, 133, 190, 124, 242, 141, 239, 243, 155, 114, 110, 15, 109, 207, 129, 14, 181, 148, 220, 169, 123, 72, 130, 189, 68, 196, 62, 167, 220, 103, 244, 154, 78]) + }; + + return importKeys(pkcs8, spki, sizes) + .then(function(results) { + publicKeys = results.publicKeys; + privateKeys = results.privateKeys; + noDeriveKeyKeys = results.noDeriveKeyKeys; + + Object.keys(sizes).forEach(function(algorithmName) { + // Basic success case + promise_test(function(test) { + return subtle.deriveKey({name: algorithmName, public: publicKeys[algorithmName]}, privateKeys[algorithmName], {name: "HMAC", hash: "SHA-256", length: 256}, true, ["sign", "verify"]) + .then(function(key) {return crypto.subtle.exportKey("raw", key);}) + .then(function(exportedKey) { + assert_true(equalBuffers(exportedKey, derivations[algorithmName], 8 * exportedKey.length), "Derived correct key"); + }, function(err) { + assert_unreached("deriveKey failed with error " + err.name + ": " + err.message); + }); + }, algorithmName + " good parameters"); + + // Case insensitivity check + promise_test(function(test) { + return subtle.deriveKey({name: algorithmName.toLowerCase(), public: publicKeys[algorithmName]}, privateKeys[algorithmName], {name: "HMAC", hash: "SHA-256", length: 256}, true, ["sign", "verify"]) + .then(function(key) {return crypto.subtle.exportKey("raw", key);}) + .then(function(exportedKey) { + assert_true(equalBuffers(exportedKey, derivations[algorithmName], 8 * exportedKey.length), "Derived correct key"); + }, function(err) { + assert_unreached("deriveKey failed with error " + err.name + ": " + err.message); + }); + }, algorithmName + " mixed case parameters"); + // Errors to test: + + // - missing public property TypeError + promise_test(function(test) { + return subtle.deriveKey({name: algorithmName}, privateKeys[algorithmName], {name: "HMAC", hash: "SHA-256", length: 256}, true, ["sign", "verify"]) + .then(function(key) {return crypto.subtle.exportKey("raw", key);}) + .then(function(exportedKey) { + assert_unreached("deriveKey succeeded but should have failed with TypeError"); + }, function(err) { + assert_equals(err.name, "TypeError", "Should throw correct error, not " + err.name + ": " + err.message); + }); + }, algorithmName + " missing public property"); + + // - Non CryptoKey public property TypeError + promise_test(function(test) { + return subtle.deriveKey({name: algorithmName, public: {message: "Not a CryptoKey"}}, privateKeys[algorithmName], {name: "HMAC", hash: "SHA-256", length: 256}, true, ["sign", "verify"]) + .then(function(key) {return crypto.subtle.exportKey("raw", key);}) + .then(function(exportedKey) { + assert_unreached("deriveKey succeeded but should have failed with TypeError"); + }, function(err) { + assert_equals(err.name, "TypeError", "Should throw correct error, not " + err.name + ": " + err.message); + }); + }, algorithmName + " public property of algorithm is not a CryptoKey"); + + // - wrong algorithm + promise_test(function(test) { + publicKey = publicKeys["X25519"]; + if (algorithmName === "X25519") { + publicKey = publicKeys["X448"]; + } + return subtle.deriveKey({name: algorithmName, public: publicKey}, privateKeys[algorithmName], {name: "HMAC", hash: "SHA-256", length: 256}, true, ["sign", "verify"]) + .then(function(key) {return crypto.subtle.exportKey("raw", key);}) + .then(function(exportedKey) { + assert_unreached("deriveKey succeeded but should have failed with InvalidAccessError"); + }, function(err) { + assert_equals(err.name, "InvalidAccessError", "Should throw correct error, not " + err.name + ": " + err.message); + }); + }, algorithmName + " mismatched algorithms"); + + // - No deriveKey usage in baseKey InvalidAccessError + promise_test(function(test) { + return subtle.deriveKey({name: algorithmName, public: publicKeys[algorithmName]}, noDeriveKeyKeys[algorithmName], {name: "HMAC", hash: "SHA-256", length: 256}, true, ["sign", "verify"]) + .then(function(key) {return crypto.subtle.exportKey("raw", key);}) + .then(function(exportedKey) { + assert_unreached("deriveKey succeeded but should have failed with InvalidAccessError"); + }, function(err) { + assert_equals(err.name, "InvalidAccessError", "Should throw correct error, not " + err.name + ": " + err.message); + }); + }, algorithmName + " no deriveKey usage for base key"); + + // - Use public key for baseKey InvalidAccessError + promise_test(function(test) { + return subtle.deriveKey({name: algorithmName, public: publicKeys[algorithmName]}, publicKeys[algorithmName], {name: "HMAC", hash: "SHA-256", length: 256}, true, ["sign", "verify"]) + .then(function(key) {return crypto.subtle.exportKey("raw", key);}) + .then(function(exportedKey) { + assert_unreached("deriveKey succeeded but should have failed with InvalidAccessError"); + }, function(err) { + assert_equals(err.name, "InvalidAccessError", "Should throw correct error, not " + err.name + ": " + err.message); + }); + }, algorithmName + " base key is not a private key"); + + // - Use private key for public property InvalidAccessError + promise_test(function(test) { + return subtle.deriveKey({name: algorithmName, public: privateKeys[algorithmName]}, privateKeys[algorithmName], {name: "HMAC", hash: "SHA-256", length: 256}, true, ["sign", "verify"]) + .then(function(key) {return crypto.subtle.exportKey("raw", key);}) + .then(function(exportedKey) { + assert_unreached("deriveKey succeeded but should have failed with InvalidAccessError"); + }, function(err) { + assert_equals(err.name, "InvalidAccessError", "Should throw correct error, not " + err.name + ": " + err.message); + }); + }, algorithmName + " public property value is a private key"); + + // - Use secret key for public property InvalidAccessError + promise_test(function(test) { + return subtle.generateKey({name: "HMAC", hash: "SHA-256", length: 256}, true, ["sign", "verify"]) + .then(function(secretKey) { + subtle.deriveKey({name: algorithmName, public: secretKey}, privateKeys[algorithmName], {name: "AES-CBC", length: 256}, true, ["sign", "verify"]) + .then(function(key) {return crypto.subtle.exportKey("raw", key);}) + .then(function(exportedKey) { + assert_unreached("deriveKey succeeded but should have failed with InvalidAccessError"); + }, function(err) { + assert_equals(err.name, "InvalidAccessError", "Should throw correct error, not " + err.name + ": " + err.message); + }); + }); + }, algorithmName + " public property value is a secret key"); + }); + }); + + function importKeys(pkcs8, spki, sizes) { + var privateKeys = {}; + var publicKeys = {}; + var noDeriveKeyKeys = {}; + + var promises = []; + Object.keys(pkcs8).forEach(function(algorithmName) { + var operation = subtle.importKey("pkcs8", pkcs8[algorithmName], + {name: algorithmName}, + false, ["deriveBits", "deriveKey"]) + .then(function(key) { + privateKeys[algorithmName] = key; + }); + promises.push(operation); + }); + Object.keys(pkcs8).forEach(function(algorithmName) { + var operation = subtle.importKey("pkcs8", pkcs8[algorithmName], + {name: algorithmName}, + false, ["deriveBits"]) + .then(function(key) { + noDeriveKeyKeys[algorithmName] = key; + }); + promises.push(operation); + }); + Object.keys(spki).forEach(function(algorithmName) { + var operation = subtle.importKey("spki", spki[algorithmName], + {name: algorithmName}, + false, []) + .then(function(key) { + publicKeys[algorithmName] = key; + }); + promises.push(operation); + }); + + return Promise.all(promises) + .then(function(results) {return {privateKeys: privateKeys, publicKeys: publicKeys, noDeriveKeyKeys: noDeriveKeyKeys}}); + } + + // Compares two ArrayBuffer or ArrayBufferView objects. If bitCount is + // omitted, the two values must be the same length and have the same contents + // in every byte. If bitCount is included, only that leading number of bits + // have to match. + function equalBuffers(a, b, bitCount) { + var remainder; + + if (typeof bitCount === "undefined" && a.byteLength !== b.byteLength) { + return false; + } + + var aBytes = new Uint8Array(a); + var bBytes = new Uint8Array(b); + + var length = a.byteLength; + if (typeof bitCount !== "undefined") { + length = Math.floor(bitCount / 8); + } + + for (var i=0; i> (8 - remainder) === bBytes[length] >> (8 - remainder); + } + + return true; + } + +} diff --git a/test/fixtures/wpt/WebCryptoAPI/generateKey/failures.js b/test/fixtures/wpt/WebCryptoAPI/generateKey/failures.js index 23fb280182a040..c39e4d211cbdf4 100644 --- a/test/fixtures/wpt/WebCryptoAPI/generateKey/failures.js +++ b/test/fixtures/wpt/WebCryptoAPI/generateKey/failures.js @@ -31,7 +31,11 @@ function run_test(algorithmNames) { {name: "RSA-PSS", resultType: "CryptoKeyPair", usages: ["sign", "verify"], mandatoryUsages: ["sign"]}, {name: "RSA-OAEP", resultType: "CryptoKeyPair", usages: ["encrypt", "decrypt", "wrapKey", "unwrapKey"], mandatoryUsages: ["decrypt", "unwrapKey"]}, {name: "ECDSA", resultType: "CryptoKeyPair", usages: ["sign", "verify"], mandatoryUsages: ["sign"]}, - {name: "ECDH", resultType: "CryptoKeyPair", usages: ["deriveKey", "deriveBits"], mandatoryUsages: ["deriveKey", "deriveBits"]} + {name: "ECDH", resultType: "CryptoKeyPair", usages: ["deriveKey", "deriveBits"], mandatoryUsages: ["deriveKey", "deriveBits"]}, + {name: "Ed25519", resultType: "CryptoKeyPair", usages: ["sign", "verify"], mandatoryUsages: ["sign"]}, + {name: "Ed448", resultType: "CryptoKeyPair", usages: ["sign", "verify"], mandatoryUsages: ["sign"]}, + {name: "X25519", resultType: "CryptoKeyPair", usages: ["deriveKey", "deriveBits"], mandatoryUsages: ["deriveKey", "deriveBits"]}, + {name: "X448", resultType: "CryptoKeyPair", usages: ["deriveKey", "deriveBits"], mandatoryUsages: ["deriveKey", "deriveBits"]}, ]; var testVectors = []; diff --git a/test/fixtures/wpt/WebCryptoAPI/generateKey/failures_Ed25519.https.any.js b/test/fixtures/wpt/WebCryptoAPI/generateKey/failures_Ed25519.https.any.js new file mode 100644 index 00000000000000..8f18fb1efe0950 --- /dev/null +++ b/test/fixtures/wpt/WebCryptoAPI/generateKey/failures_Ed25519.https.any.js @@ -0,0 +1,5 @@ +// META: title=WebCryptoAPI: generateKey() for Failures +// META: timeout=long +// META: script=../util/helpers.js +// META: script=failures.js +run_test(["Ed25519"]); diff --git a/test/fixtures/wpt/WebCryptoAPI/generateKey/failures_Ed448.https.any.js b/test/fixtures/wpt/WebCryptoAPI/generateKey/failures_Ed448.https.any.js new file mode 100644 index 00000000000000..b25dcd14909410 --- /dev/null +++ b/test/fixtures/wpt/WebCryptoAPI/generateKey/failures_Ed448.https.any.js @@ -0,0 +1,5 @@ +// META: title=WebCryptoAPI: generateKey() for Failures +// META: timeout=long +// META: script=../util/helpers.js +// META: script=failures.js +run_test(["Ed448"]); diff --git a/test/fixtures/wpt/WebCryptoAPI/generateKey/failures_X25519.https.any.js b/test/fixtures/wpt/WebCryptoAPI/generateKey/failures_X25519.https.any.js new file mode 100644 index 00000000000000..2662d8697a9a6f --- /dev/null +++ b/test/fixtures/wpt/WebCryptoAPI/generateKey/failures_X25519.https.any.js @@ -0,0 +1,5 @@ +// META: title=WebCryptoAPI: generateKey() for Failures +// META: timeout=long +// META: script=../util/helpers.js +// META: script=failures.js +run_test(["X25519"]); diff --git a/test/fixtures/wpt/WebCryptoAPI/generateKey/failures_X448.https.any.js b/test/fixtures/wpt/WebCryptoAPI/generateKey/failures_X448.https.any.js new file mode 100644 index 00000000000000..455e260d1fe920 --- /dev/null +++ b/test/fixtures/wpt/WebCryptoAPI/generateKey/failures_X448.https.any.js @@ -0,0 +1,5 @@ +// META: title=WebCryptoAPI: generateKey() for Failures +// META: timeout=long +// META: script=../util/helpers.js +// META: script=failures.js +run_test(["X448"]); diff --git a/test/fixtures/wpt/WebCryptoAPI/generateKey/successes.js b/test/fixtures/wpt/WebCryptoAPI/generateKey/successes.js index b99e44d66d40f8..4a047aa0609d7a 100644 --- a/test/fixtures/wpt/WebCryptoAPI/generateKey/successes.js +++ b/test/fixtures/wpt/WebCryptoAPI/generateKey/successes.js @@ -26,7 +26,11 @@ function run_test(algorithmNames, slowTest) { {name: "RSA-PSS", resultType: "CryptoKeyPair", usages: ["sign", "verify"], mandatoryUsages: ["sign"]}, {name: "RSA-OAEP", resultType: "CryptoKeyPair", usages: ["encrypt", "decrypt", "wrapKey", "unwrapKey"], mandatoryUsages: ["decrypt", "unwrapKey"]}, {name: "ECDSA", resultType: "CryptoKeyPair", usages: ["sign", "verify"], mandatoryUsages: ["sign"]}, - {name: "ECDH", resultType: "CryptoKeyPair", usages: ["deriveKey", "deriveBits"], mandatoryUsages: ["deriveKey", "deriveBits"]} + {name: "ECDH", resultType: "CryptoKeyPair", usages: ["deriveKey", "deriveBits"], mandatoryUsages: ["deriveKey", "deriveBits"]}, + {name: "Ed25519", resultType: "CryptoKeyPair", usages: ["sign", "verify"], mandatoryUsages: ["sign"]}, + {name: "Ed448", resultType: "CryptoKeyPair", usages: ["sign", "verify"], mandatoryUsages: ["sign"]}, + {name: "X25519", resultType: "CryptoKeyPair", usages: ["deriveKey", "deriveBits"], mandatoryUsages: ["deriveKey", "deriveBits"]}, + {name: "X448", resultType: "CryptoKeyPair", usages: ["deriveKey", "deriveBits"], mandatoryUsages: ["deriveKey", "deriveBits"]}, ]; var testVectors = []; diff --git a/test/fixtures/wpt/WebCryptoAPI/generateKey/successes_Ed25519.https.any.js b/test/fixtures/wpt/WebCryptoAPI/generateKey/successes_Ed25519.https.any.js new file mode 100644 index 00000000000000..6b3bc460f60f2f --- /dev/null +++ b/test/fixtures/wpt/WebCryptoAPI/generateKey/successes_Ed25519.https.any.js @@ -0,0 +1,6 @@ +// META: title=WebCryptoAPI: generateKey() Successful Calls +// META: timeout=long +// META: script=../util/helpers.js +// META: script=/common/subset-tests.js +// META: script=successes.js +run_test(["Ed25519"]); diff --git a/test/fixtures/wpt/WebCryptoAPI/generateKey/successes_Ed448.https.any.js b/test/fixtures/wpt/WebCryptoAPI/generateKey/successes_Ed448.https.any.js new file mode 100644 index 00000000000000..8e37f57b244b58 --- /dev/null +++ b/test/fixtures/wpt/WebCryptoAPI/generateKey/successes_Ed448.https.any.js @@ -0,0 +1,6 @@ +// META: title=WebCryptoAPI: generateKey() Successful Calls +// META: timeout=long +// META: script=../util/helpers.js +// META: script=/common/subset-tests.js +// META: script=successes.js +run_test(["Ed448"]); diff --git a/test/fixtures/wpt/WebCryptoAPI/generateKey/successes_X25519.https.any.js b/test/fixtures/wpt/WebCryptoAPI/generateKey/successes_X25519.https.any.js new file mode 100644 index 00000000000000..0e87cf50108e69 --- /dev/null +++ b/test/fixtures/wpt/WebCryptoAPI/generateKey/successes_X25519.https.any.js @@ -0,0 +1,6 @@ +// META: title=WebCryptoAPI: generateKey() Successful Calls +// META: timeout=long +// META: script=../util/helpers.js +// META: script=/common/subset-tests.js +// META: script=successes.js +run_test(["X25519"]); diff --git a/test/fixtures/wpt/WebCryptoAPI/generateKey/successes_X448.https.any.js b/test/fixtures/wpt/WebCryptoAPI/generateKey/successes_X448.https.any.js new file mode 100644 index 00000000000000..e7dbe32696d8fe --- /dev/null +++ b/test/fixtures/wpt/WebCryptoAPI/generateKey/successes_X448.https.any.js @@ -0,0 +1,6 @@ +// META: title=WebCryptoAPI: generateKey() Successful Calls +// META: timeout=long +// META: script=../util/helpers.js +// META: script=/common/subset-tests.js +// META: script=successes.js +run_test(["X448"]); diff --git a/test/fixtures/wpt/WebCryptoAPI/getRandomValues.any.js b/test/fixtures/wpt/WebCryptoAPI/getRandomValues.any.js index a6762353798f43..1a3370ea13d2c0 100644 --- a/test/fixtures/wpt/WebCryptoAPI/getRandomValues.any.js +++ b/test/fixtures/wpt/WebCryptoAPI/getRandomValues.any.js @@ -8,12 +8,24 @@ test(function() { }, "Float64Array") assert_throws_dom("TypeMismatchError", function() { - self.crypto.getRandomValues(new Float32Array(65537)) + const len = 65536 / Float32Array.BYTES_PER_ELEMENT + 1; + self.crypto.getRandomValues(new Float32Array(len)); }, "Float32Array (too long)") assert_throws_dom("TypeMismatchError", function() { - self.crypto.getRandomValues(new Float64Array(65537)) + const len = 65536 / Float64Array.BYTES_PER_ELEMENT + 1; + self.crypto.getRandomValues(new Float64Array(len)) }, "Float64Array (too long)") -}, "Float arrays") +}, "Float arrays"); + +test(function() { + assert_throws_dom("TypeMismatchError", function() { + self.crypto.getRandomValues(new DataView(new ArrayBuffer(6))) + }, "DataView") + + assert_throws_dom("TypeMismatchError", function() { + self.crypto.getRandomValues(new DataView(new ArrayBuffer(65536 + 1))) + }, "DataView (too long)") +}, "DataView"); const arrays = [ 'Int8Array', diff --git a/test/fixtures/wpt/WebCryptoAPI/import_export/okp_importKey.https.any.js b/test/fixtures/wpt/WebCryptoAPI/import_export/okp_importKey.https.any.js new file mode 100644 index 00000000000000..836d6eaa305710 --- /dev/null +++ b/test/fixtures/wpt/WebCryptoAPI/import_export/okp_importKey.https.any.js @@ -0,0 +1,282 @@ +// META: title=WebCryptoAPI: importKey() for OKP keys +// META: timeout=long + +// Test importKey and exportKey for OKP algorithms. Only "happy paths" are +// currently tested - those where the operation should succeed. + + var subtle = crypto.subtle; + + var keyData = { + "Ed25519": { + spki: new Uint8Array([48, 42, 48, 5, 6, 3, 43, 101, 112, 3, 33, 0, 216, 225, 137, 99, 216, 9, 212, 135, 217, 84, 154, 204, 174, 198, 116, 46, 126, 235, 162, 77, 138, 13, 59, 20, 183, 227, 202, 234, 6, 137, 61, 204]), + pkcs8: new Uint8Array([48, 46, 2, 1, 0, 48, 5, 6, 3, 43, 101, 112, 4, 34, 4, 32, 243, 200, 244, 196, 141, 248, 120, 20, 110, 140, 211, 191, 109, 244, 229, 14, 56, 155, 167, 7, 78, 21, 194, 53, 45, 205, 93, 48, 141, 76, 168, 31]), + jwk: { + crv: "Ed25519", + d: "88j0xI34eBRujNO_bfTlDjibpwdOFcI1Lc1dMI1MqB8", + x: "2OGJY9gJ1IfZVJrMrsZ0Ln7rok2KDTsUt-PK6gaJPcw", + kty: "OKP" + } + }, + + "Ed448": { + spki: new Uint8Array([48, 67, 48, 5, 6, 3, 43, 101, 113, 3, 58, 0, 171, 75, 184, 133, 253, 125, 44, 90, 242, 78, 131, 113, 12, 255, 160, 199, 74, 87, 226, 116, 128, 29, 178, 5, 123, 11, 220, 94, 160, 50, 182, 254, 107, 199, 139, 128, 69, 54, 90, 235, 38, 232, 110, 31, 20, 253, 52, 157, 7, 196, 132, 149, 245, 164, 106, 90, 128]), + pkcs8: new Uint8Array([48, 71, 2, 1, 0, 48, 5, 6, 3, 43, 101, 113, 4, 59, 4, 57, 14, 255, 3, 69, 140, 40, 224, 23, 156, 82, 29, 227, 18, 201, 105, 183, 131, 67, 72, 236, 171, 153, 26, 96, 227, 178, 233, 167, 158, 76, 217, 228, 128, 239, 41, 23, 18, 210, 200, 61, 4, 114, 114, 213, 201, 244, 40, 102, 79, 105, 109, 38, 112, 69, 143, 29, 46]), + jwk: { + crv: "Ed448", + d: "Dv8DRYwo4BecUh3jEslpt4NDSOyrmRpg47Lpp55M2eSA7ykXEtLIPQRyctXJ9ChmT2ltJnBFjx0u", + x: "q0u4hf19LFryToNxDP-gx0pX4nSAHbIFewvcXqAytv5rx4uARTZa6ybobh8U_TSdB8SElfWkalqA", + kty: "OKP" + } + }, + + "X25519": { + spki: new Uint8Array([48, 42, 48, 5, 6, 3, 43, 101, 110, 3, 33, 0, 28, 242, 177, 230, 2, 46, 197, 55, 55, 30, 215, 245, 62, 84, 250, 17, 84, 216, 62, 152, 235, 100, 234, 81, 250, 229, 179, 48, 124, 254, 151, 6]), + pkcs8: new Uint8Array([48, 46, 2, 1, 0, 48, 5, 6, 3, 43, 101, 110, 4, 34, 4, 32, 200, 131, 142, 118, 208, 87, 223, 183, 216, 201, 90, 105, 225, 56, 22, 10, 221, 99, 115, 253, 113, 164, 210, 118, 187, 86, 227, 168, 27, 100, 255, 97]), + jwk: { + crv: "X25519", + d: "yIOOdtBX37fYyVpp4TgWCt1jc_1xpNJ2u1bjqBtk_2E", + x: "HPKx5gIuxTc3Htf1PlT6EVTYPpjrZOpR-uWzMHz-lwY", + kty: "OKP" + } + }, + + "X448": { + spki: new Uint8Array([48, 66, 48, 5, 6, 3, 43, 101, 111, 3, 57, 0, 182, 4, 161, 209, 165, 205, 29, 148, 38, 213, 97, 239, 99, 10, 158, 177, 108, 190, 105, 213, 185, 202, 97, 94, 220, 83, 99, 62, 251, 82, 234, 49, 230, 230, 160, 161, 219, 172, 198, 231, 108, 188, 230, 72, 45, 126, 75, 163, 213, 93, 158, 128, 39, 101, 206, 111]), + pkcs8: new Uint8Array([48, 70, 2, 1, 0, 48, 5, 6, 3, 43, 101, 111, 4, 58, 4, 56, 88, 199, 210, 154, 62, 181, 25, 178, 157, 0, 207, 177, 145, 187, 100, 252, 109, 138, 66, 216, 241, 113, 118, 39, 43, 137, 242, 39, 45, 24, 25, 41, 92, 101, 37, 192, 130, 150, 113, 176, 82, 239, 7, 39, 83, 15, 24, 142, 49, 208, 204, 83, 191, 38, 146, 158]), + jwk: { + crv: "X448", + d: "WMfSmj61GbKdAM-xkbtk_G2KQtjxcXYnK4nyJy0YGSlcZSXAgpZxsFLvBydTDxiOMdDMU78mkp4", + x: "tgSh0aXNHZQm1WHvYwqesWy-adW5ymFe3FNjPvtS6jHm5qCh26zG52y85kgtfkuj1V2egCdlzm8", + kty: "OKP" + } + }, + + }; + + // combinations to test + var testVectors = [ + {name: "Ed25519", privateUsages: ["sign"], publicUsages: ["verify"]}, + {name: "Ed448", privateUsages: ["sign"], publicUsages: ["verify"]}, + {name: "X25519", privateUsages: ["deriveKey", "deriveBits"], publicUsages: []}, + {name: "X448", privateUsages: ["deriveKey", "deriveBits"], publicUsages: []}, + ]; + + // TESTS ARE HERE: + // Test every test vector, along with all available key data + testVectors.forEach(function(vector) { + [true, false].forEach(function(extractable) { + + // Test public keys first + [[]].forEach(function(usages) { // Only valid usages argument is empty array + ['spki', 'jwk'].forEach(function(format) { + var algorithm = {name: vector.name}; + var data = keyData[vector.name]; + if (format === "jwk") { // Not all fields used for public keys + data = {jwk: {kty: keyData[vector.name].jwk.kty, crv: keyData[vector.name].jwk.crv, x: keyData[vector.name].jwk.x}}; + } + + testFormat(format, algorithm, data, vector.name, usages, extractable); + }); + + }); + + // Next, test private keys + allValidUsages(vector.privateUsages, []).forEach(function(usages) { + ['pkcs8', 'jwk'].forEach(function(format) { + var algorithm = {name: vector.name}; + var data = keyData[vector.name]; + + testFormat(format, algorithm, data, vector.name, usages, extractable); + }); + }); + }); + }); + + + // Test importKey with a given key format and other parameters. If + // extrable is true, export the key and verify that it matches the input. + function testFormat(format, algorithm, keyData, keySize, usages, extractable) { + promise_test(function(test) { + return subtle.importKey(format, keyData[format], algorithm, extractable, usages). + then(function(key) { + assert_equals(key.constructor, CryptoKey, "Imported a CryptoKey object"); + if (!extractable) { + return; + } + + return subtle.exportKey(format, key). + then(function(result) { + if (format !== "jwk") { + assert_true(equalBuffers(keyData[format], result), "Round trip works"); + } else { + assert_true(equalJwk(keyData[format], result), "Round trip works"); + } + }, function(err) { + assert_unreached("Threw an unexpected error: " + err.toString()); + }); + }, function(err) { + assert_unreached("Threw an unexpected error: " + err.toString()); + }); + }, "Good parameters: " + keySize.toString() + " bits " + parameterString(format, keyData[format], algorithm, extractable, usages)); + } + + + + // Helper methods follow: + + // Are two array buffers the same? + function equalBuffers(a, b) { + if (a.byteLength !== b.byteLength) { + return false; + } + + var aBytes = new Uint8Array(a); + var bBytes = new Uint8Array(b); + + for (var i=0; i 0) { + allNonemptySubsetsOf(remainingElements).forEach(function(combination) { + combination.push(firstElement); + results.push(combination); + }); + } + } + + return results; + } + + // Return a list of all valid usage combinations, given the possible ones + // and the ones that are required for a particular operation. + function allValidUsages(possibleUsages, requiredUsages) { + var allUsages = []; + + allNonemptySubsetsOf(possibleUsages).forEach(function(usage) { + for (var i=0; i { - createSecretKey(Buffer.alloc(0)); - }, { - name: 'RangeError', - code: 'ERR_OUT_OF_RANGE', - message: 'The value of "key.byteLength" is out of range. ' + - 'It must be > 0. Received 0' - }); -} - { // Attempting to create a key of a wrong type should throw const TYPE = 'wrong_type'; diff --git a/test/parallel/test-webcrypto-derivebits-hkdf.js b/test/parallel/test-webcrypto-derivebits-hkdf.js index 42d958e5f5f821..6c42c3b173123d 100644 --- a/test/parallel/test-webcrypto-derivebits-hkdf.js +++ b/test/parallel/test-webcrypto-derivebits-hkdf.js @@ -259,15 +259,18 @@ async function testDeriveBitsBadLengths( return Promise.all([ assert.rejects( subtle.deriveBits(algorithm, baseKeys[size], 0), { - message: /length cannot be zero/ + message: /length cannot be zero/, + name: 'OperationError', }), assert.rejects( subtle.deriveBits(algorithm, baseKeys[size], null), { - code: 'ERR_INVALID_ARG_TYPE' + message: 'length cannot be null', + name: 'OperationError', }), assert.rejects( subtle.deriveBits(algorithm, baseKeys[size], 15), { - message: /length must be a multiple of 8/ + message: /length must be a multiple of 8/, + name: 'OperationError', }), ]); } diff --git a/test/parallel/test-webcrypto-encrypt-decrypt-aes.js b/test/parallel/test-webcrypto-encrypt-decrypt-aes.js index 885cded906b079..db265d9cf8bdaa 100644 --- a/test/parallel/test-webcrypto-encrypt-decrypt-aes.js +++ b/test/parallel/test-webcrypto-encrypt-decrypt-aes.js @@ -119,7 +119,7 @@ async function testDecrypt({ keyBuffer, algorithm, result }) { decryptionFailing.forEach((vector) => { variations.push(assert.rejects(testDecrypt(vector), { - message: /bad decrypt/ + name: 'OperationError' })); }); diff --git a/test/parallel/test-webcrypto-encrypt-decrypt-rsa.js b/test/parallel/test-webcrypto-encrypt-decrypt-rsa.js index 151eebd36c9765..6af0fa727969d8 100644 --- a/test/parallel/test-webcrypto-encrypt-decrypt-rsa.js +++ b/test/parallel/test-webcrypto-encrypt-decrypt-rsa.js @@ -127,7 +127,7 @@ async function testEncryptionLongPlaintext({ algorithm, return assert.rejects( subtle.encrypt(algorithm, publicKey, newplaintext), { - message: /data too large/ + name: 'OperationError' }); } diff --git a/test/parallel/test-webcrypto-export-import.js b/test/parallel/test-webcrypto-export-import.js index 02e178f72628d5..8647192ed85e47 100644 --- a/test/parallel/test-webcrypto-export-import.js +++ b/test/parallel/test-webcrypto-export-import.js @@ -42,15 +42,6 @@ const { subtle } = webcrypto; name: 'SyntaxError', message: 'Unsupported key usage for an HMAC key' }); - await assert.rejects( - subtle.importKey('raw', keyData, { - name: 'HMAC', - hash: 'SHA-256', - length: 0 - }, false, ['sign', 'verify']), { - name: 'DataError', - message: 'Zero-length key is not supported' - }); await assert.rejects( subtle.importKey('raw', keyData, { name: 'HMAC', diff --git a/test/parallel/test-webcrypto-keygen.js b/test/parallel/test-webcrypto-keygen.js index e61d7c9b913abf..0e55a480b98e7d 100644 --- a/test/parallel/test-webcrypto-keygen.js +++ b/test/parallel/test-webcrypto-keygen.js @@ -452,7 +452,7 @@ const vectors = { [1, true, {}, [], undefined, null].forEach(async (namedCurve) => { await assert.rejects( subtle.generateKey({ name, namedCurve }, true, privateUsages), { - code: 'ERR_INVALID_ARG_TYPE' + name: 'NotSupportedError', }); }); } @@ -512,14 +512,14 @@ const vectors = { [1, 100, 257].forEach(async (length) => { await assert.rejects( subtle.generateKey({ name, length }, true, usages), { - code: 'ERR_INVALID_ARG_VALUE' + name: 'OperationError', }); }); ['', {}, [], false, null, undefined].forEach(async (length) => { await assert.rejects( subtle.generateKey({ name, length }, true, usages), { - code: 'ERR_INVALID_ARG_TYPE' + name: 'OperationError', }); }); } diff --git a/test/pummel/test-webcrypto-derivebits-pbkdf2.js b/test/pummel/test-webcrypto-derivebits-pbkdf2.js index e7ed4f6bd646dd..f3beffb096d28c 100644 --- a/test/pummel/test-webcrypto-derivebits-pbkdf2.js +++ b/test/pummel/test-webcrypto-derivebits-pbkdf2.js @@ -448,15 +448,18 @@ async function testDeriveBitsBadLengths( return Promise.all([ assert.rejects( subtle.deriveBits(algorithm, baseKeys[size], 0), { - message: /length cannot be zero/ + name: 'OperationError', + message: /length cannot be zero/, }), assert.rejects( subtle.deriveBits(algorithm, baseKeys[size], null), { - code: 'ERR_INVALID_ARG_TYPE' + name: 'OperationError', + message: 'length cannot be null', }), assert.rejects( subtle.deriveBits(algorithm, baseKeys[size], 15), { - message: /length must be a multiple of 8/ + name: 'OperationError', + message: /length must be a multiple of 8/, }), ]); } diff --git a/test/wpt/status/WebCryptoAPI.json b/test/wpt/status/WebCryptoAPI.json index 0f8489c871f037..9f101f6cdd92c9 100644 --- a/test/wpt/status/WebCryptoAPI.json +++ b/test/wpt/status/WebCryptoAPI.json @@ -1,90 +1,11 @@ { - "derive_bits_keys/ecdh_bits.https.any.js": { - "fail": "Derived correct bits expected true got false" - }, - "derive_bits_keys/hkdf.https.any.js": { - "fail": "location is not defined" - }, - "derive_bits_keys/pbkdf2.https.any.js": { - "fail": "location is not defined" - }, - "digest/digest.https.any.js": { - "fail": "Expected NotSupportedError but got TypeError" - }, - "encrypt_decrypt/aes_cbc.https.any.js": { - "fail": "Expected OperationError but got Error" - }, - "encrypt_decrypt/rsa_oaep.https.any.js": { - "fail": "Expected OperationError but got Error" - }, - "generateKey/failures_AES-CBC.https.any.js": { - "fail": "Expected OperationError but got TypeError" - }, - "generateKey/failures_AES-CTR.https.any.js": { - "fail": "Expected OperationError but got TypeError" - }, - "generateKey/failures_AES-GCM.https.any.js": { - "fail": "Expected OperationError but got TypeError" - }, - "generateKey/failures_AES-KW.https.any.js": { - "fail": "Expected OperationError but got TypeError" - }, - "generateKey/failures_ECDH.https.any.js": { - "fail": "Expected NotSupportedError but got TypeError" - }, - "generateKey/failures_ECDSA.https.any.js": { - "fail": "Expected NotSupportedError but got TypeError" - }, - "generateKey/failures_HMAC.https.any.js": { - "fail": "Operation succeeded, but should not have Reached unreachable code" - }, - "generateKey/failures_RSA-OAEP.https.any.js": { - "fail": "Operation succeeded, but should not have Reached unreachable code" - }, - "generateKey/failures_RSA-PSS.https.any.js": { - "fail": "Operation succeeded, but should not have Reached unreachable code" - }, - "generateKey/failures_RSASSA-PKCS1-v1_5.https.any.js": { - "fail": "Operation succeeded, but should not have Reached unreachable code" - }, - "generateKey/successes_AES-CBC.https.any.js": { - "fail": "location is not defined" - }, - "generateKey/successes_AES-CTR.https.any.js": { - "fail": "location is not defined" - }, - "generateKey/successes_AES-GCM.https.any.js": { - "fail": "location is not defined" - }, - "generateKey/successes_AES-KW.https.any.js": { - "fail": "location is not defined" - }, - "generateKey/successes_ECDH.https.any.js": { - "fail": "location is not defined" - }, - "generateKey/successes_ECDSA.https.any.js": { - "fail": "location is not defined" - }, - "generateKey/successes_HMAC.https.any.js": { - "fail": "location is not defined" - }, - "generateKey/successes_RSA-OAEP.https.any.js": { - "fail": "location is not defined" - }, - "generateKey/successes_RSA-PSS.https.any.js": { - "fail": "location is not defined" - }, - "generateKey/successes_RSASSA-PKCS1-v1_5.https.any.js": { - "fail": "location is not defined" - }, - "getRandomValues.any.js": { - "fail": "The data argument must be an integer-type TypedArray" + "algorithm-discards-context.https.window.js": { + "skip": "Not relevant in Node.js context" }, "historical.any.js": { - "fail": "expected (undefined) undefined but got..." + "skip": "Not relevant in Node.js context" }, "idlharness.https.any.js": { - "fail": "Various non-IDL-compliant things" + "skip": "Various non-IDL-compliant things" } } - diff --git a/test/wpt/test-webcrypto.js b/test/wpt/test-webcrypto.js index c1ee6402c5a75c..f90ffa9ad5316b 100644 --- a/test/wpt/test-webcrypto.js +++ b/test/wpt/test-webcrypto.js @@ -11,4 +11,8 @@ const runner = new WPTRunner('WebCryptoAPI'); // Set Node.js flags required for the tests. runner.setFlags(['--experimental-global-webcrypto']); +runner.setInitScript(` + global.location = {}; +`); + runner.runJsTests(); From 491ea8d100585d03a7b1b40d289dd513ad340944 Mon Sep 17 00:00:00 2001 From: Filip Skokan Date: Sun, 3 Jul 2022 09:26:40 +0200 Subject: [PATCH 02/17] address comments --- src/crypto/crypto_hkdf.cc | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/src/crypto/crypto_hkdf.cc b/src/crypto/crypto_hkdf.cc index 73936712115e11..6438172699261d 100644 --- a/src/crypto/crypto_hkdf.cc +++ b/src/crypto/crypto_hkdf.cc @@ -121,21 +121,31 @@ bool HKDFTraits::DeriveBits( return false; } } else { - unsigned int len = EVP_MD_size(params.digest); - uint8_t tempKey[len]; // NOLINT(runtime/arrays) + unsigned char temp_key[EVP_MAX_MD_SIZE]; + unsigned int len = sizeof(temp_key); if (params.salt.size()) { - HMAC(params.digest, + if (HMAC(params.digest, params.salt.data(), params.salt.size(), nullptr, 0, - tempKey, - &len); + temp_key, + &len) == nullptr) { + return false; + } } else { - HMAC(params.digest, new char[len]{}, len, nullptr, 0, tempKey, &len); + if (HMAC(params.digest, + new char[len]{}, + len, + nullptr, + 0, + temp_key, + &len) == nullptr) { + return false; + } } if (!EVP_PKEY_CTX_hkdf_mode(ctx.get(), EVP_PKEY_HKDEF_MODE_EXPAND_ONLY) || - !EVP_PKEY_CTX_set1_hkdf_key(ctx.get(), tempKey, len)) { + !EVP_PKEY_CTX_set1_hkdf_key(ctx.get(), temp_key, len)) { return false; } } From d9cd3853f9e4c89c6546aef8f8d818010f4c3b5c Mon Sep 17 00:00:00 2001 From: Filip Skokan Date: Sun, 3 Jul 2022 09:43:22 +0200 Subject: [PATCH 03/17] make format-cpp --- src/crypto/crypto_hkdf.cc | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/src/crypto/crypto_hkdf.cc b/src/crypto/crypto_hkdf.cc index 6438172699261d..c91001aed4c146 100644 --- a/src/crypto/crypto_hkdf.cc +++ b/src/crypto/crypto_hkdf.cc @@ -125,24 +125,24 @@ bool HKDFTraits::DeriveBits( unsigned int len = sizeof(temp_key); if (params.salt.size()) { if (HMAC(params.digest, - params.salt.data(), - params.salt.size(), - nullptr, - 0, - temp_key, - &len) == nullptr) { - return false; - } + params.salt.data(), + params.salt.size(), + nullptr, + 0, + temp_key, + &len) == nullptr) { + return false; + } } else { if (HMAC(params.digest, - new char[len]{}, - len, - nullptr, - 0, - temp_key, - &len) == nullptr) { - return false; - } + new char[len]{}, + len, + nullptr, + 0, + temp_key, + &len) == nullptr) { + return false; + } } if (!EVP_PKEY_CTX_hkdf_mode(ctx.get(), EVP_PKEY_HKDEF_MODE_EXPAND_ONLY) || !EVP_PKEY_CTX_set1_hkdf_key(ctx.get(), temp_key, len)) { From f0c0a9ccb7b4436712620f34d7fcd33a8e431a3e Mon Sep 17 00:00:00 2001 From: Filip Skokan Date: Sun, 3 Jul 2022 20:13:18 +0200 Subject: [PATCH 04/17] apply review feedback --- src/crypto/crypto_hkdf.cc | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/src/crypto/crypto_hkdf.cc b/src/crypto/crypto_hkdf.cc index c91001aed4c146..b590f5b8185379 100644 --- a/src/crypto/crypto_hkdf.cc +++ b/src/crypto/crypto_hkdf.cc @@ -96,6 +96,8 @@ Maybe HKDFTraits::AdditionalConfig( return Just(true); } +char salt[EVP_MAX_KEY_LENGTH]; + bool HKDFTraits::DeriveBits( Environment* env, const HKDFConfig& params, @@ -134,13 +136,8 @@ bool HKDFTraits::DeriveBits( return false; } } else { - if (HMAC(params.digest, - new char[len]{}, - len, - nullptr, - 0, - temp_key, - &len) == nullptr) { + if (HMAC(params.digest, salt, len, nullptr, 0, temp_key, &len) == + nullptr) { return false; } } From 37209740cb656d072acda0daf6b6cff7a590471c Mon Sep 17 00:00:00 2001 From: Filip Skokan Date: Sun, 3 Jul 2022 20:28:17 +0200 Subject: [PATCH 05/17] mark test-webcrypto SLOW --- test/wpt/wpt.status | 1 + 1 file changed, 1 insertion(+) diff --git a/test/wpt/wpt.status b/test/wpt/wpt.status index d20d108ac9906d..010c280866a996 100644 --- a/test/wpt/wpt.status +++ b/test/wpt/wpt.status @@ -7,6 +7,7 @@ prefix wpt [true] # This section applies to all platforms # https://github.com/nodejs/node/issues/40449 test-user-timing: PASS,FLAKY +test-webcrypto: SLOW [$system==win32] From 4fcfa501218239a0d3cd73ac4a82c9df3e5ed75d Mon Sep 17 00:00:00 2001 From: Filip Skokan Date: Sun, 3 Jul 2022 21:13:19 +0200 Subject: [PATCH 06/17] Revert "mark test-webcrypto SLOW" This reverts commit 0d3bf3da98f5143b275550ad2381ee5f4e3c072f. --- test/wpt/wpt.status | 1 - 1 file changed, 1 deletion(-) diff --git a/test/wpt/wpt.status b/test/wpt/wpt.status index 010c280866a996..d20d108ac9906d 100644 --- a/test/wpt/wpt.status +++ b/test/wpt/wpt.status @@ -7,7 +7,6 @@ prefix wpt [true] # This section applies to all platforms # https://github.com/nodejs/node/issues/40449 test-user-timing: PASS,FLAKY -test-webcrypto: SLOW [$system==win32] From e30587557f4697e0fa7c402907dd62e987eb442c Mon Sep 17 00:00:00 2001 From: Filip Skokan Date: Tue, 5 Jul 2022 12:24:07 +0200 Subject: [PATCH 07/17] apply review feedback --- src/crypto/crypto_hkdf.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/crypto/crypto_hkdf.cc b/src/crypto/crypto_hkdf.cc index b590f5b8185379..e6bc003692c53a 100644 --- a/src/crypto/crypto_hkdf.cc +++ b/src/crypto/crypto_hkdf.cc @@ -96,7 +96,7 @@ Maybe HKDFTraits::AdditionalConfig( return Just(true); } -char salt[EVP_MAX_KEY_LENGTH]; +char salt[EVP_MAX_KEY_LENGTH] = {0}; bool HKDFTraits::DeriveBits( Environment* env, @@ -136,7 +136,7 @@ bool HKDFTraits::DeriveBits( return false; } } else { - if (HMAC(params.digest, salt, len, nullptr, 0, temp_key, &len) == + if (HMAC(params.digest, salt, sizeof(salt), nullptr, 0, temp_key, &len) == nullptr) { return false; } From 7745cb0fbe7f2c32e7c46eae18b49bce1aea2ef9 Mon Sep 17 00:00:00 2001 From: Filip Skokan Date: Wed, 6 Jul 2022 09:22:47 +0200 Subject: [PATCH 08/17] apply review feedback --- src/crypto/crypto_hkdf.cc | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/crypto/crypto_hkdf.cc b/src/crypto/crypto_hkdf.cc index e6bc003692c53a..9c7cb29d215467 100644 --- a/src/crypto/crypto_hkdf.cc +++ b/src/crypto/crypto_hkdf.cc @@ -96,8 +96,6 @@ Maybe HKDFTraits::AdditionalConfig( return Just(true); } -char salt[EVP_MAX_KEY_LENGTH] = {0}; - bool HKDFTraits::DeriveBits( Environment* env, const HKDFConfig& params, @@ -136,6 +134,7 @@ bool HKDFTraits::DeriveBits( return false; } } else { + char salt[EVP_MAX_KEY_LENGTH] = {0}; if (HMAC(params.digest, salt, sizeof(salt), nullptr, 0, temp_key, &len) == nullptr) { return false; From 41d849d08c0ea04b815d1725ae5975aa94ff17cc Mon Sep 17 00:00:00 2001 From: Filip Skokan Date: Tue, 12 Jul 2022 18:05:13 +0200 Subject: [PATCH 09/17] see if increasing wpt timeout helps with ci --- tools/test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/test.py b/tools/test.py index 9a7de5ed24d706..850b0672b5d9bb 100755 --- a/tools/test.py +++ b/tools/test.py @@ -944,7 +944,7 @@ def GetVm(self, arch, mode): def GetTimeout(self, mode, section=''): timeout = self.timeout * TIMEOUT_SCALEFACTOR[ARCH_GUESS or 'ia32'][mode] - if section == 'pummel' or section == 'benchmark': + if section == 'pummel' or section == 'benchmark' or section == 'wpt': timeout = timeout * 6 return timeout From 07a86765431283daf6f61771d63d3d80c5693cc1 Mon Sep 17 00:00:00 2001 From: Filip Skokan Date: Wed, 13 Jul 2022 17:11:05 +0200 Subject: [PATCH 10/17] test if runing wpt js files one by one helps with test-webcrypto --- test/common/wpt.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/test/common/wpt.js b/test/common/wpt.js index ff969f4358c990..1630bdb124b65c 100644 --- a/test/common/wpt.js +++ b/test/common/wpt.js @@ -368,7 +368,7 @@ class WPTRunner { // TODO(joyeecheung): work with the upstream to port more tests in .html // to .js. - runJsTests() { + async runJsTests() { let queue = []; // If the tests are run as `node test/wpt/test-something.js subset.any.js`, @@ -459,6 +459,12 @@ class WPTRunner { ); this.inProgress.delete(testFileName); }); + + await new Promise((resolve) => { + worker.on('exit', () => { + resolve() + }) + }) } process.on('exit', () => { From fb5eb5416a75820846fda328e47536e43e70a7c2 Mon Sep 17 00:00:00 2001 From: Filip Skokan Date: Wed, 13 Jul 2022 17:12:19 +0200 Subject: [PATCH 11/17] fix lint --- test/common/wpt.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/common/wpt.js b/test/common/wpt.js index 1630bdb124b65c..2ccd5e0591ff59 100644 --- a/test/common/wpt.js +++ b/test/common/wpt.js @@ -462,9 +462,9 @@ class WPTRunner { await new Promise((resolve) => { worker.on('exit', () => { - resolve() - }) - }) + resolve(); + }); + }); } process.on('exit', () => { From 03275d045baee3ac30817d5e8c2345533074fc1e Mon Sep 17 00:00:00 2001 From: Filip Skokan Date: Sat, 23 Jul 2022 20:42:56 +0200 Subject: [PATCH 12/17] apply review feedback --- src/crypto/crypto_hkdf.cc | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/crypto/crypto_hkdf.cc b/src/crypto/crypto_hkdf.cc index 9c7cb29d215467..d4788d21b13065 100644 --- a/src/crypto/crypto_hkdf.cc +++ b/src/crypto/crypto_hkdf.cc @@ -123,7 +123,7 @@ bool HKDFTraits::DeriveBits( } else { unsigned char temp_key[EVP_MAX_MD_SIZE]; unsigned int len = sizeof(temp_key); - if (params.salt.size()) { + if (params.salt.size() != 0) { if (HMAC(params.digest, params.salt.data(), params.salt.size(), @@ -134,9 +134,14 @@ bool HKDFTraits::DeriveBits( return false; } } else { - char salt[EVP_MAX_KEY_LENGTH] = {0}; - if (HMAC(params.digest, salt, sizeof(salt), nullptr, 0, temp_key, &len) == - nullptr) { + char salt[EVP_MAX_MD_SIZE] = {0}; + if (HMAC(params.digest, + salt, + EVP_MD_size(params.digest), + nullptr, + 0, + temp_key, + &len) == nullptr) { return false; } } From 8c05855c6bfaef9f0d770b0559acfea350992365 Mon Sep 17 00:00:00 2001 From: Filip Skokan Date: Sat, 23 Jul 2022 21:03:35 +0200 Subject: [PATCH 13/17] update hkdf docs --- doc/api/crypto.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/api/crypto.md b/doc/api/crypto.md index 8d6a01c8f065b2..4e1839ffb34a4b 100644 --- a/doc/api/crypto.md +++ b/doc/api/crypto.md @@ -4217,7 +4217,7 @@ changes: * `digest` {string} The digest algorithm to use. * `ikm` {string|ArrayBuffer|Buffer|TypedArray|DataView|KeyObject} The input - keying material. It must be at least one byte in length. + keying material. Must be provided but can be zero-length. * `salt` {string|ArrayBuffer|Buffer|TypedArray|DataView} The salt value. Must be provided but can be zero-length. * `info` {string|ArrayBuffer|Buffer|TypedArray|DataView} Additional info value. @@ -4271,7 +4271,7 @@ added: v15.0.0 * `digest` {string} The digest algorithm to use. * `ikm` {string|ArrayBuffer|Buffer|TypedArray|DataView|KeyObject} The input - keying material. It must be at least one byte in length. + keying material. Must be provided but can be zero-length. * `salt` {string|ArrayBuffer|Buffer|TypedArray|DataView} The salt value. Must be provided but can be zero-length. * `info` {string|ArrayBuffer|Buffer|TypedArray|DataView} Additional info value. From 2561171c5f14c16743272883deed01b068a63934 Mon Sep 17 00:00:00 2001 From: Filip Skokan Date: Sat, 23 Jul 2022 21:03:49 +0200 Subject: [PATCH 14/17] add 0-length key hmac and equals tests --- test/parallel/test-crypto-hmac.js | 9 +++++++++ test/parallel/test-crypto-key-objects.js | 10 ++++++++++ 2 files changed, 19 insertions(+) diff --git a/test/parallel/test-crypto-hmac.js b/test/parallel/test-crypto-hmac.js index de0a59423d4b2a..0edea0dfb0fdcc 100644 --- a/test/parallel/test-crypto-hmac.js +++ b/test/parallel/test-crypto-hmac.js @@ -450,3 +450,12 @@ assert.strictEqual( () => crypto.createHmac('sha7', 'key'), /Invalid digest/); } + +{ + const buf = Buffer.alloc(0); + const keyObject = crypto.createSecretKey(Buffer.alloc(0)); + assert.deepStrictEqual( + crypto.createHmac('sha256', buf).update('foo').digest(), + crypto.createHmac('sha256', keyObject).update('foo').digest(), + ); +} diff --git a/test/parallel/test-crypto-key-objects.js b/test/parallel/test-crypto-key-objects.js index 1ed403f551ffd1..0d5dfb2ea77a77 100644 --- a/test/parallel/test-crypto-key-objects.js +++ b/test/parallel/test-crypto-key-objects.js @@ -858,3 +858,13 @@ const privateDsa = fixtures.readKey('dsa_private_encrypted_1025.pem', assert(!first.privateKey.equals(second.privateKey)); assert(!first.privateKey.equals(second.publicKey)); } + +{ + const first = createSecretKey(Buffer.alloc(0)); + const second = createSecretKey(Buffer.alloc(0)); + const third = createSecretKey(Buffer.alloc(1)); + assert(first.equals(first)); + assert(first.equals(second)); + assert(!first.equals(third)); + assert(!third.equals(first)); +} From 09a6e5011050e363039957f2a1abed225f572ea8 Mon Sep 17 00:00:00 2001 From: Filip Skokan Date: Sat, 23 Jul 2022 22:59:38 +0200 Subject: [PATCH 15/17] add yaml change entries --- doc/api/crypto.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/doc/api/crypto.md b/doc/api/crypto.md index 4e1839ffb34a4b..6b838eb449accf 100644 --- a/doc/api/crypto.md +++ b/doc/api/crypto.md @@ -3541,6 +3541,9 @@ and it will be impossible to extract the private key from the returned object. * `digest` {string} The digest algorithm to use. From d51c30c349b95ab0ed72a53cdbc6679c63f1c101 Mon Sep 17 00:00:00 2001 From: Filip Skokan Date: Sat, 23 Jul 2022 23:00:01 +0200 Subject: [PATCH 16/17] apply review feedback --- src/crypto/crypto_hkdf.cc | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/crypto/crypto_hkdf.cc b/src/crypto/crypto_hkdf.cc index d4788d21b13065..9007941e96e631 100644 --- a/src/crypto/crypto_hkdf.cc +++ b/src/crypto/crypto_hkdf.cc @@ -109,6 +109,9 @@ bool HKDFTraits::DeriveBits( return false; } + // TODO: Once support for OpenSSL 1.1.1 is dropped the whole + // of HKDFTraits::DeriveBits can be refactored to use + // EVP_KDF which does handle zero length key. if (params.key->GetSymmetricKeySize() != 0) { if (!EVP_PKEY_CTX_hkdf_mode(ctx.get(), EVP_PKEY_HKDEF_MODE_EXTRACT_AND_EXPAND) || @@ -121,6 +124,7 @@ bool HKDFTraits::DeriveBits( return false; } } else { + // Workaround for EVP_PKEY_derive HKDF not handling zero length keys. unsigned char temp_key[EVP_MAX_MD_SIZE]; unsigned int len = sizeof(temp_key); if (params.salt.size() != 0) { From c8d7b2cbb32459dc7cbfe1941fb5c878ab6f73b3 Mon Sep 17 00:00:00 2001 From: Filip Skokan Date: Sat, 23 Jul 2022 23:11:03 +0200 Subject: [PATCH 17/17] make lint happy --- src/crypto/crypto_hkdf.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/crypto/crypto_hkdf.cc b/src/crypto/crypto_hkdf.cc index 9007941e96e631..bff8cc60c4c13b 100644 --- a/src/crypto/crypto_hkdf.cc +++ b/src/crypto/crypto_hkdf.cc @@ -109,7 +109,7 @@ bool HKDFTraits::DeriveBits( return false; } - // TODO: Once support for OpenSSL 1.1.1 is dropped the whole + // TODO(panva): Once support for OpenSSL 1.1.1 is dropped the whole // of HKDFTraits::DeriveBits can be refactored to use // EVP_KDF which does handle zero length key. if (params.key->GetSymmetricKeySize() != 0) {