diff --git a/.devcontainer.json b/.devcontainer.json new file mode 100644 index 00000000000000..c0ae5292d6b572 --- /dev/null +++ b/.devcontainer.json @@ -0,0 +1,25 @@ +{ + "name": "Node.js Core Developer Environment", + "runArgs": [ + "--platform=linux/amd64" + ], + "customizations": { + "vscode": { + "extensions": [ + "github.vscode-pull-request-github", + "ms-vsliveshare.vsliveshare", + "vscode-icons-team.vscode-icons", + "visualstudioexptteam.vscodeintellicode" + ], + "settings": { + "terminal.integrated.profiles.linux": { + "zsh (login)": { + "path": "zsh", + "args": ["-l"] + } + } + } + } + }, + "image": "nodejs/devcontainer:nightly" +} diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json deleted file mode 100644 index 75fda6e5b319b9..00000000000000 --- a/.devcontainer/devcontainer.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "name": "Node.js Core Developer Environment", - "extensions": [ - "github.vscode-pull-request-github", - "ms-vsliveshare.vsliveshare", - "vscode-icons-team.vscode-icons", - "visualstudioexptteam.vscodeintellicode" - ], - "image": "nodejs/devcontainer:nightly", - "settings": { - "terminal.integrated.profiles.linux": { - "zsh (login)": { - "path": "zsh", - "args": ["-l"] - } - } - } -} diff --git a/.github/workflows/coverage-linux-without-intl.yml b/.github/workflows/coverage-linux-without-intl.yml index c30ae78e303e07..6c44a51d81798c 100644 --- a/.github/workflows/coverage-linux-without-intl.yml +++ b/.github/workflows/coverage-linux-without-intl.yml @@ -79,6 +79,6 @@ jobs: - name: Clean tmp run: rm -rf coverage/tmp && rm -rf out - name: Upload - uses: codecov/codecov-action@18283e04ce6e62d37312384ff67231eb8fd56d24 # v5.4.3 + uses: codecov/codecov-action@39a2af19d997be74586469d4062e173ecae614f6 # v5.4.3+ with: directory: ./coverage diff --git a/.github/workflows/coverage-linux.yml b/.github/workflows/coverage-linux.yml index 6d5afb489762e4..6c1ef24648a85b 100644 --- a/.github/workflows/coverage-linux.yml +++ b/.github/workflows/coverage-linux.yml @@ -79,6 +79,6 @@ jobs: - name: Clean tmp run: rm -rf coverage/tmp && rm -rf out - name: Upload - uses: codecov/codecov-action@18283e04ce6e62d37312384ff67231eb8fd56d24 # v5.4.3 + uses: codecov/codecov-action@39a2af19d997be74586469d4062e173ecae614f6 # v5.4.3+ with: directory: ./coverage diff --git a/.github/workflows/coverage-windows.yml b/.github/workflows/coverage-windows.yml index 0e68565e962741..52facb5778fba1 100644 --- a/.github/workflows/coverage-windows.yml +++ b/.github/workflows/coverage-windows.yml @@ -71,6 +71,6 @@ jobs: - name: Clean tmp run: npx rimraf ./coverage/tmp - name: Upload - uses: codecov/codecov-action@18283e04ce6e62d37312384ff67231eb8fd56d24 # v5.4.3 + uses: codecov/codecov-action@39a2af19d997be74586469d4062e173ecae614f6 # v5.4.3+ with: directory: ./coverage diff --git a/.gitignore b/.gitignore index 60a3ff8b4a4cd8..90ebce4cb674ac 100644 --- a/.gitignore +++ b/.gitignore @@ -7,8 +7,7 @@ .* # Exclude specific dotfiles that we want to track. !deps/**/.* -!.devcontainer/ -!.devcontainer/.devcontainer.json +!.devcontainer.json !test/fixtures/**/.* !.clang-format !.cpplint diff --git a/CHANGELOG.md b/CHANGELOG.md index f1983b2ddda791..843b312c289d76 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -40,7 +40,8 @@ release. -24.6.0
+24.7.0
+24.6.0
24.5.0
24.4.1
24.4.0
diff --git a/SECURITY.md b/SECURITY.md index d5cc79095371e3..9862585a92391c 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -102,6 +102,22 @@ vulnerability in the context of the Node.js threat model. In other words, it cannot assume that a trusted element (such as the operating system) has been compromised. +### Experimental platforms + +Node.js maintains a tier-based support system for operating systems and +hardware combinations (Tier 1, Tier 2, and Experimental). For platforms +classified as "Experimental" in the [supported platforms](BUILDING.md#supported-platforms) +documentation: + +* Security vulnerabilities that only affect experimental platforms will **not** be accepted as valid security issues. +* Any issues on experimental platforms will be treated as normal bugs. +* No CVEs will be issued for issues that only affect experimental platforms +* Bug bounty rewards are not available for experimental platform-specific issues + +This policy recognizes that experimental platforms may not compile, may not +pass the test suite, and do not have the same level of testing and support +infrastructure as Tier 1 and Tier 2 platforms. + Being able to cause the following through control of the elements that Node.js does not trust is considered a vulnerability: @@ -284,3 +300,8 @@ Security notifications will be distributed via the following methods. If you have suggestions on how this process could be improved, please visit the [nodejs/security-wg](https://github.com/nodejs/security-wg) repository. + +## Incident Response Plan + +In the event of a security incident, please refer to the +[Security Incident Response Plan](https://github.com/nodejs/security-wg/blob/main/INCIDENT_RESPONSE_PLAN.md). diff --git a/benchmark/common.js b/benchmark/common.js index 0d3dbef24d07ad..0bb3fa3dfae32f 100644 --- a/benchmark/common.js +++ b/benchmark/common.js @@ -81,6 +81,9 @@ class Benchmark { if (typeof value === 'number') { if (key === 'dur' || key === 'duration') { value = 0.05; + } else if (key === 'memory') { + // minimum Argon2 memcost with 1 lane is 8 + value = 8; } else if (value > 1) { value = 1; } diff --git a/benchmark/crypto/argon2.js b/benchmark/crypto/argon2.js new file mode 100644 index 00000000000000..ce6a824233e636 --- /dev/null +++ b/benchmark/crypto/argon2.js @@ -0,0 +1,53 @@ +'use strict'; + +const common = require('../common.js'); +const { hasOpenSSL } = require('../../test/common/crypto.js'); +const assert = require('node:assert'); +const { + argon2, + argon2Sync, + randomBytes, +} = require('node:crypto'); + +if (!hasOpenSSL(3, 2)) { + console.log('Skipping: Argon2 requires OpenSSL >= 3.2'); + process.exit(0); +} + +const bench = common.createBenchmark(main, { + mode: ['sync', 'async'], + algorithm: ['argon2d', 'argon2i', 'argon2id'], + passes: [1, 3], + parallelism: [2, 4, 8], + memory: [2 ** 11, 2 ** 16, 2 ** 21], + n: [50], +}); + +function measureSync(n, algorithm, message, nonce, options) { + bench.start(); + for (let i = 0; i < n; ++i) + argon2Sync(algorithm, { ...options, message, nonce, tagLength: 64 }); + bench.end(n); +} + +function measureAsync(n, algorithm, message, nonce, options) { + let remaining = n; + function done(err) { + assert.ifError(err); + if (--remaining === 0) + bench.end(n); + } + bench.start(); + for (let i = 0; i < n; ++i) + argon2(algorithm, { ...options, message, nonce, tagLength: 64 }, done); +} + +function main({ n, mode, algorithm, ...options }) { + // Message, nonce, secret, associated data & tag length do not affect performance + const message = randomBytes(32); + const nonce = randomBytes(16); + if (mode === 'sync') + measureSync(n, algorithm, message, nonce, options); + else + measureAsync(n, algorithm, message, nonce, options); +} diff --git a/benchmark/crypto/create-keyobject.js b/benchmark/crypto/create-keyobject.js index 75988031abf6cc..58b873cde7f27a 100644 --- a/benchmark/crypto/create-keyobject.js +++ b/benchmark/crypto/create-keyobject.js @@ -1,6 +1,7 @@ 'use strict'; const common = require('../common.js'); +const { hasOpenSSL } = require('../../test/common/crypto.js'); const crypto = require('crypto'); const fs = require('fs'); const path = require('path'); @@ -21,11 +22,14 @@ const keyFixtures = { 'ec': readKeyPair('ec_p256_public', 'ec_p256_private'), 'rsa': readKeyPair('rsa_public_2048', 'rsa_private_2048'), 'ed25519': readKeyPair('ed25519_public', 'ed25519_private'), - 'ml-dsa-44': readKeyPair('ml_dsa_44_public', 'ml_dsa_44_private'), }; +if (hasOpenSSL(3, 5)) { + keyFixtures['ml-dsa-44'] = readKeyPair('ml_dsa_44_public', 'ml_dsa_44_private'); +} + const bench = common.createBenchmark(main, { - keyType: ['rsa', 'ec', 'ed25519', 'ml-dsa-44'], + keyType: Object.keys(keyFixtures), keyFormat: ['pkcs8', 'spki', 'der-pkcs8', 'der-spki', 'jwk-public', 'jwk-private'], n: [1e3], }); diff --git a/benchmark/crypto/kem.js b/benchmark/crypto/kem.js new file mode 100644 index 00000000000000..c36e79957a115c --- /dev/null +++ b/benchmark/crypto/kem.js @@ -0,0 +1,140 @@ +'use strict'; + +const common = require('../common.js'); +const { hasOpenSSL } = require('../../test/common/crypto.js'); +const crypto = require('crypto'); +const fs = require('fs'); +const path = require('path'); +const fixtures_keydir = path.resolve(__dirname, '../../test/fixtures/keys/'); + +function readKey(name) { + return fs.readFileSync(`${fixtures_keydir}/${name}.pem`, 'utf8'); +} + +const keyFixtures = {}; + +if (hasOpenSSL(3, 5)) { + keyFixtures['ml-kem-512'] = readKey('ml_kem_512_private'); + keyFixtures['ml-kem-768'] = readKey('ml_kem_768_private'); + keyFixtures['ml-kem-1024'] = readKey('ml_kem_1024_private'); +} +if (hasOpenSSL(3, 2)) { + keyFixtures['p-256'] = readKey('ec_p256_private'); + keyFixtures['p-384'] = readKey('ec_p384_private'); + keyFixtures['p-521'] = readKey('ec_p521_private'); + keyFixtures.x25519 = readKey('x25519_private'); + keyFixtures.x448 = readKey('x448_private'); +} +if (hasOpenSSL(3, 0)) { + keyFixtures.rsa = readKey('rsa_private_2048'); +} + +if (Object.keys(keyFixtures).length === 0) { + console.log('no supported key types available for this OpenSSL version'); + process.exit(0); +} + +const bench = common.createBenchmark(main, { + keyType: Object.keys(keyFixtures), + mode: ['sync', 'async', 'async-parallel'], + keyFormat: ['keyObject', 'keyObject.unique'], + op: ['encapsulate', 'decapsulate'], + n: [1e3], +}, { + combinationFilter(p) { + // "keyObject.unique" allows to compare the result with "keyObject" to + // assess whether mutexes over the key material impact the operation + return p.keyFormat !== 'keyObject.unique' || + (p.keyFormat === 'keyObject.unique' && p.mode === 'async-parallel'); + }, +}); + +function measureSync(n, op, privateKey, keys, ciphertexts) { + bench.start(); + for (let i = 0; i < n; ++i) { + const key = privateKey || keys[i]; + if (op === 'encapsulate') { + crypto.encapsulate(key); + } else { + crypto.decapsulate(key, ciphertexts[i]); + } + } + bench.end(n); +} + +function measureAsync(n, op, privateKey, keys, ciphertexts) { + let remaining = n; + function done() { + if (--remaining === 0) + bench.end(n); + else + one(); + } + + function one() { + const key = privateKey || keys[n - remaining]; + if (op === 'encapsulate') { + crypto.encapsulate(key, done); + } else { + crypto.decapsulate(key, ciphertexts[n - remaining], done); + } + } + bench.start(); + one(); +} + +function measureAsyncParallel(n, op, privateKey, keys, ciphertexts) { + let remaining = n; + function done() { + if (--remaining === 0) + bench.end(n); + } + bench.start(); + for (let i = 0; i < n; ++i) { + const key = privateKey || keys[i]; + if (op === 'encapsulate') { + crypto.encapsulate(key, done); + } else { + crypto.decapsulate(key, ciphertexts[i], done); + } + } +} + +function main({ n, mode, keyFormat, keyType, op }) { + const pems = [...Buffer.alloc(n)].map(() => keyFixtures[keyType]); + const keyObjects = pems.map(crypto.createPrivateKey); + + let privateKey, keys, ciphertexts; + + switch (keyFormat) { + case 'keyObject': + privateKey = keyObjects[0]; + break; + case 'keyObject.unique': + keys = keyObjects; + break; + default: + throw new Error('not implemented'); + } + + // Pre-generate ciphertexts for decapsulate operations + if (op === 'decapsulate') { + if (privateKey) { + ciphertexts = [...Buffer.alloc(n)].map(() => crypto.encapsulate(privateKey).ciphertext); + } else { + ciphertexts = keys.map((key) => crypto.encapsulate(key).ciphertext); + } + } + + switch (mode) { + case 'sync': + measureSync(n, op, privateKey, keys, ciphertexts); + break; + case 'async': + measureAsync(n, op, privateKey, keys, ciphertexts); + break; + case 'async-parallel': + measureAsyncParallel(n, op, privateKey, keys, ciphertexts); + break; + } +} diff --git a/benchmark/crypto/oneshot-sign.js b/benchmark/crypto/oneshot-sign.js index 97372606a10ade..e1942c347d7508 100644 --- a/benchmark/crypto/oneshot-sign.js +++ b/benchmark/crypto/oneshot-sign.js @@ -1,6 +1,7 @@ 'use strict'; const common = require('../common.js'); +const { hasOpenSSL } = require('../../test/common/crypto.js'); const crypto = require('crypto'); const fs = require('fs'); const path = require('path'); @@ -14,16 +15,19 @@ const keyFixtures = { 'ec': readKey('ec_p256_private'), 'rsa': readKey('rsa_private_2048'), 'ed25519': readKey('ed25519_private'), - 'ml-dsa-44': readKey('ml_dsa_44_private'), }; +if (hasOpenSSL(3, 5)) { + keyFixtures['ml-dsa-44'] = readKey('ml_dsa_44_private'); +} + const data = crypto.randomBytes(256); let pems; let keyObjects; const bench = common.createBenchmark(main, { - keyType: ['rsa', 'ec', 'ed25519', 'ml-dsa-44'], + keyType: Object.keys(keyFixtures), mode: ['sync', 'async', 'async-parallel'], keyFormat: ['pem', 'der', 'jwk', 'keyObject', 'keyObject.unique'], n: [1e3], diff --git a/benchmark/crypto/oneshot-verify.js b/benchmark/crypto/oneshot-verify.js index 9569d5168f60ce..e0bbc0ce755f15 100644 --- a/benchmark/crypto/oneshot-verify.js +++ b/benchmark/crypto/oneshot-verify.js @@ -1,6 +1,7 @@ 'use strict'; const common = require('../common.js'); +const { hasOpenSSL } = require('../../test/common/crypto.js'); const crypto = require('crypto'); const fs = require('fs'); const path = require('path'); @@ -21,16 +22,19 @@ const keyFixtures = { 'ec': readKeyPair('ec_p256_public', 'ec_p256_private'), 'rsa': readKeyPair('rsa_public_2048', 'rsa_private_2048'), 'ed25519': readKeyPair('ed25519_public', 'ed25519_private'), - 'ml-dsa-44': readKeyPair('ml_dsa_44_public', 'ml_dsa_44_private'), }; +if (hasOpenSSL(3, 5)) { + keyFixtures['ml-dsa-44'] = readKeyPair('ml_dsa_44_public', 'ml_dsa_44_private'); +} + const data = crypto.randomBytes(256); let pems; let keyObjects; const bench = common.createBenchmark(main, { - keyType: ['rsa', 'ec', 'ed25519', 'ml-dsa-44'], + keyType: Object.keys(keyFixtures), mode: ['sync', 'async', 'async-parallel'], keyFormat: ['pem', 'der', 'jwk', 'keyObject', 'keyObject.unique'], n: [1e3], diff --git a/benchmark/fs/readfile-permission-enabled.js b/benchmark/fs/readfile-permission-enabled.js index c688e9eecb0e00..4d784ab343467a 100644 --- a/benchmark/fs/readfile-permission-enabled.js +++ b/benchmark/fs/readfile-permission-enabled.js @@ -12,8 +12,8 @@ const filename = tmpdir.resolve(`.removeme-benchmark-garbage-${process.pid}`); const bench = common.createBenchmark(main, { duration: [5], - encoding: ['', 'utf-8'], - len: [1024, 16 * 1024 * 1024], + encoding: ['utf-8'], + len: [1024], concurrent: [1, 10], }, { flags: [ diff --git a/benchmark/http/bench-parser.js b/benchmark/http/bench-parser.js index f2c120cd97f133..0a1e8f7b5e8a0f 100644 --- a/benchmark/http/bench-parser.js +++ b/benchmark/http/bench-parser.js @@ -16,7 +16,6 @@ function main({ len, n }) { const kOnHeadersComplete = HTTPParser.kOnHeadersComplete | 0; const kOnBody = HTTPParser.kOnBody | 0; const kOnMessageComplete = HTTPParser.kOnMessageComplete | 0; - const CRLF = '\r\n'; function processHeader(header, n) { const parser = newParser(REQUEST); @@ -43,12 +42,12 @@ function main({ len, n }) { return parser; } - let header = `GET /hello HTTP/1.1${CRLF}Content-Type: text/plain${CRLF}`; + let header = `GET /hello HTTP/1.1\r\nContent-Type: text/plain\r\n`; for (let i = 0; i < len; i++) { - header += `X-Filler${i}: ${Math.random().toString(36).substring(2)}${CRLF}`; + header += `X-Filler${i}: ${Math.random().toString(36).substring(2)}\r\n`; } - header += CRLF; + header += '\r\n'; processHeader(Buffer.from(header), n); } diff --git a/benchmark/util/diff.js b/benchmark/util/diff.js index 3ec024c24df6d0..7e40b3cd30f63b 100644 --- a/benchmark/util/diff.js +++ b/benchmark/util/diff.js @@ -5,7 +5,7 @@ const common = require('../common'); const bench = common.createBenchmark(main, { n: [1e3], - length: [1e3, 2e3], + length: [500, 1000], scenario: ['identical', 'small-diff', 'medium-diff', 'large-diff'], }); diff --git a/benchmark/v8/serialize.js b/benchmark/v8/serialize.js index c370da49ea7cca..1b9a240bd458e4 100644 --- a/benchmark/v8/serialize.js +++ b/benchmark/v8/serialize.js @@ -4,8 +4,8 @@ const common = require('../common.js'); const v8 = require('v8'); const bench = common.createBenchmark(main, { - len: [256, 1024 * 16, 1024 * 512], - n: [1e6], + len: [256, 1024 * 16], + n: [1e5], }); function main({ n, len }) { diff --git a/common.gypi b/common.gypi index 4a78c9029a4523..d4605a52de7db3 100644 --- a/common.gypi +++ b/common.gypi @@ -38,7 +38,7 @@ # Reset this number to 0 on major V8 upgrades. # Increment by one for each non-official patch applied to deps/v8. - 'v8_embedder_string': '-node.24', + 'v8_embedder_string': '-node.26', ##### V8 defaults for Node.js ##### @@ -538,7 +538,12 @@ 'ldflags': [ '-m64' ], }], [ 'host_arch=="ppc64" and OS not in "aix os400"', { - 'cflags': [ '-m64', '-mminimal-toc' ], + 'conditions': [ + [ 'clang==0', { + 'cflags': [ '-mminimal-toc' ], + }], + ], + 'cflags': [ '-m64' ], 'ldflags': [ '-m64' ], }], [ 'host_arch=="s390x" and OS=="linux"', { @@ -558,7 +563,12 @@ 'ldflags': [ '-m64' ], }], [ 'target_arch=="ppc64" and OS not in "aix os400"', { - 'cflags': [ '-m64', '-mminimal-toc' ], + 'conditions': [ + [ 'clang==0', { + 'cflags': [ '-mminimal-toc' ], + }], + ], + 'cflags': [ '-m64' ], 'ldflags': [ '-m64' ], }], [ 'target_arch=="s390x" and OS=="linux"', { diff --git a/configure.py b/configure.py index c6e5154fde671d..b54a2f5961f9bc 100755 --- a/configure.py +++ b/configure.py @@ -1113,7 +1113,7 @@ def try_check_compiler(cc, lang): proc = subprocess.Popen(shlex.split(cc) + ['-E', '-P', '-x', lang, '-'], stdin=subprocess.PIPE, stdout=subprocess.PIPE) except OSError: - return (False, False, '', '') + return (False, False, '', '', False) with proc: proc.stdin.write(b'__clang__ __GNUC__ __GNUC_MINOR__ __GNUC_PATCHLEVEL__ ' diff --git a/deps/ncrypto/ncrypto.cc b/deps/ncrypto/ncrypto.cc index a2bfe874650fd5..73af3b8c37c073 100644 --- a/deps/ncrypto/ncrypto.cc +++ b/deps/ncrypto/ncrypto.cc @@ -10,7 +10,28 @@ #include #include #if OPENSSL_VERSION_MAJOR >= 3 +#include +#include #include +#if OPENSSL_VERSION_NUMBER >= 0x30200000L +#include +#endif +#endif +#if OPENSSL_WITH_PQC +struct PQCMapping { + const char* name; + int nid; +}; + +constexpr static PQCMapping pqc_mappings[] = { + {"ML-DSA-44", EVP_PKEY_ML_DSA_44}, + {"ML-DSA-65", EVP_PKEY_ML_DSA_65}, + {"ML-DSA-87", EVP_PKEY_ML_DSA_87}, + {"ML-KEM-512", EVP_PKEY_ML_KEM_512}, + {"ML-KEM-768", EVP_PKEY_ML_KEM_768}, + {"ML-KEM-1024", EVP_PKEY_ML_KEM_1024}, +}; + #endif // EVP_PKEY_CTX_set_dsa_paramgen_q_bits was added in OpenSSL 1.1.1e. @@ -1852,6 +1873,102 @@ DataPointer pbkdf2(const Digest& md, return {}; } +#if OPENSSL_VERSION_NUMBER >= 0x30200000L +#ifndef OPENSSL_NO_ARGON2 +DataPointer argon2(const Buffer& pass, + const Buffer& salt, + uint32_t lanes, + size_t length, + uint32_t memcost, + uint32_t iter, + uint32_t version, + const Buffer& secret, + const Buffer& ad, + Argon2Type type) { + ClearErrorOnReturn clearErrorOnReturn; + + std::string_view algorithm; + switch (type) { + case Argon2Type::ARGON2I: + algorithm = "ARGON2I"; + break; + case Argon2Type::ARGON2D: + algorithm = "ARGON2D"; + break; + case Argon2Type::ARGON2ID: + algorithm = "ARGON2ID"; + break; + default: + // Invalid Argon2 type + return {}; + } + + // creates a new library context to avoid locking when running concurrently + auto ctx = DeleteFnPtr{OSSL_LIB_CTX_new()}; + if (!ctx) { + return {}; + } + + // required if threads > 1 + if (lanes > 1 && OSSL_set_max_threads(ctx.get(), lanes) != 1) { + return {}; + } + + auto kdf = DeleteFnPtr{ + EVP_KDF_fetch(ctx.get(), algorithm.data(), nullptr)}; + if (!kdf) { + return {}; + } + + auto kctx = + DeleteFnPtr{EVP_KDF_CTX_new(kdf.get())}; + if (!kctx) { + return {}; + } + + std::vector params; + params.reserve(9); + + params.push_back(OSSL_PARAM_construct_octet_string( + OSSL_KDF_PARAM_PASSWORD, + const_cast(pass.len > 0 ? pass.data : ""), + pass.len)); + params.push_back(OSSL_PARAM_construct_octet_string( + OSSL_KDF_PARAM_SALT, const_cast(salt.data), salt.len)); + params.push_back(OSSL_PARAM_construct_uint32(OSSL_KDF_PARAM_THREADS, &lanes)); + params.push_back( + OSSL_PARAM_construct_uint32(OSSL_KDF_PARAM_ARGON2_LANES, &lanes)); + params.push_back( + OSSL_PARAM_construct_uint32(OSSL_KDF_PARAM_ARGON2_MEMCOST, &memcost)); + params.push_back(OSSL_PARAM_construct_uint32(OSSL_KDF_PARAM_ITER, &iter)); + + if (ad.len != 0) { + params.push_back(OSSL_PARAM_construct_octet_string( + OSSL_KDF_PARAM_ARGON2_AD, const_cast(ad.data), ad.len)); + } + + if (secret.len != 0) { + params.push_back(OSSL_PARAM_construct_octet_string( + OSSL_KDF_PARAM_SECRET, + const_cast(secret.data), + secret.len)); + } + + params.push_back(OSSL_PARAM_construct_end()); + + auto dp = DataPointer::Alloc(length); + if (dp && EVP_KDF_derive(kctx.get(), + reinterpret_cast(dp.get()), + length, + params.data()) == 1) { + return dp; + } + + return {}; +} +#endif +#endif + // ============================================================================ EVPKeyPointer::PrivateKeyEncodingConfig::PrivateKeyEncodingConfig( @@ -1897,7 +2014,7 @@ EVPKeyPointer EVPKeyPointer::NewRawPrivate( EVP_PKEY_new_raw_private_key(id, nullptr, data.data, data.len)); } -#if OPENSSL_VERSION_MAJOR >= 3 && OPENSSL_VERSION_MINOR >= 5 +#if OPENSSL_WITH_PQC EVPKeyPointer EVPKeyPointer::NewRawSeed( int id, const Buffer& data) { if (id == 0) return {}; @@ -1968,12 +2085,22 @@ EVP_PKEY* EVPKeyPointer::release() { int EVPKeyPointer::id(const EVP_PKEY* key) { if (key == nullptr) return 0; int type = EVP_PKEY_id(key); -#if OPENSSL_VERSION_MAJOR >= 3 && OPENSSL_VERSION_MINOR >= 5 +#if OPENSSL_WITH_PQC + // EVP_PKEY_id returns -1 when EVP_PKEY_* is only implemented in a provider + // which is the case for all post-quantum NIST algorithms + // one suggested way would be to use a chain of `EVP_PKEY_is_a` // https://github.com/openssl/openssl/issues/27738#issuecomment-3013215870 + // or, this way there are less calls to the OpenSSL provider, just + // getting the name once if (type == -1) { - if (EVP_PKEY_is_a(key, "ML-DSA-44")) return EVP_PKEY_ML_DSA_44; - if (EVP_PKEY_is_a(key, "ML-DSA-65")) return EVP_PKEY_ML_DSA_65; - if (EVP_PKEY_is_a(key, "ML-DSA-87")) return EVP_PKEY_ML_DSA_87; + const char* type_name = EVP_PKEY_get0_type_name(key); + if (type_name == nullptr) return -1; + + for (const auto& mapping : pqc_mappings) { + if (strcmp(type_name, mapping.name) == 0) { + return mapping.nid; + } + } } #endif return type; @@ -2032,24 +2159,37 @@ DataPointer EVPKeyPointer::rawPublicKey() const { return {}; } -#if OPENSSL_VERSION_MAJOR >= 3 && OPENSSL_VERSION_MINOR >= 5 +#if OPENSSL_WITH_PQC DataPointer EVPKeyPointer::rawSeed() const { if (!pkey_) return {}; + + // Determine seed length and parameter name based on key type + size_t seed_len; + const char* param_name; + switch (id()) { case EVP_PKEY_ML_DSA_44: case EVP_PKEY_ML_DSA_65: case EVP_PKEY_ML_DSA_87: + seed_len = 32; // ML-DSA uses 32-byte seeds + param_name = OSSL_PKEY_PARAM_ML_DSA_SEED; + break; + case EVP_PKEY_ML_KEM_512: + case EVP_PKEY_ML_KEM_768: + case EVP_PKEY_ML_KEM_1024: + seed_len = 64; // ML-KEM uses 64-byte seeds + param_name = OSSL_PKEY_PARAM_ML_KEM_SEED; break; default: unreachable(); } - size_t seed_len = 32; if (auto data = DataPointer::Alloc(seed_len)) { const Buffer buf = data; size_t len = data.size(); + if (EVP_PKEY_get_octet_string_param( - get(), OSSL_PKEY_PARAM_ML_DSA_SEED, buf.data, len, &seed_len) != 1) + get(), param_name, buf.data, len, &seed_len) != 1) return {}; return data; } @@ -2515,7 +2655,7 @@ bool EVPKeyPointer::isOneShotVariant() const { switch (type) { case EVP_PKEY_ED25519: case EVP_PKEY_ED448: -#if OPENSSL_VERSION_MAJOR >= 3 && OPENSSL_VERSION_MINOR >= 5 +#if OPENSSL_WITH_PQC case EVP_PKEY_ML_DSA_44: case EVP_PKEY_ML_DSA_65: case EVP_PKEY_ML_DSA_87: @@ -2901,6 +3041,10 @@ const Cipher Cipher::AES_256_GCM = Cipher::FromNid(NID_aes_256_gcm); const Cipher Cipher::AES_128_KW = Cipher::FromNid(NID_id_aes128_wrap); const Cipher Cipher::AES_192_KW = Cipher::FromNid(NID_id_aes192_wrap); const Cipher Cipher::AES_256_KW = Cipher::FromNid(NID_id_aes256_wrap); +const Cipher Cipher::AES_128_OCB = Cipher::FromNid(NID_aes_128_ocb); +const Cipher Cipher::AES_192_OCB = Cipher::FromNid(NID_aes_192_ocb); +const Cipher Cipher::AES_256_OCB = Cipher::FromNid(NID_aes_256_ocb); +const Cipher Cipher::CHACHA20_POLY1305 = Cipher::FromNid(NID_chacha20_poly1305); bool Cipher::isGcmMode() const { if (!cipher_) return false; @@ -3102,6 +3246,11 @@ bool CipherCtxPointer::isGcmMode() const { return getMode() == EVP_CIPH_GCM_MODE; } +bool CipherCtxPointer::isOcbMode() const { + if (!ctx_) return false; + return getMode() == EVP_CIPH_OCB_MODE; +} + bool CipherCtxPointer::isCcmMode() const { if (!ctx_) return false; return getMode() == EVP_CIPH_CCM_MODE; @@ -3617,7 +3766,6 @@ EVPKeyPointer EVPKeyCtxPointer::paramgen() const { bool EVPKeyCtxPointer::publicCheck() const { if (!ctx_) return false; #ifndef OPENSSL_IS_BORINGSSL - return EVP_PKEY_public_check(ctx_.get()) == 1; #if OPENSSL_VERSION_MAJOR >= 3 return EVP_PKEY_public_check_quick(ctx_.get()) == 1; #else @@ -4383,4 +4531,125 @@ const Digest Digest::FromName(const char* name) { return ncrypto::getDigestByName(name); } +// ============================================================================ +// KEM Implementation +#if OPENSSL_VERSION_MAJOR >= 3 +#if !OPENSSL_VERSION_PREREQ(3, 5) +bool KEM::SetOperationParameter(EVP_PKEY_CTX* ctx, const EVPKeyPointer& key) { + const char* operation = nullptr; + + switch (EVP_PKEY_id(key.get())) { + case EVP_PKEY_RSA: + operation = OSSL_KEM_PARAM_OPERATION_RSASVE; + break; +#if OPENSSL_VERSION_PREREQ(3, 2) + case EVP_PKEY_EC: + case EVP_PKEY_X25519: + case EVP_PKEY_X448: + operation = OSSL_KEM_PARAM_OPERATION_DHKEM; + break; +#endif + default: + unreachable(); + } + + if (operation != nullptr) { + OSSL_PARAM params[] = { + OSSL_PARAM_utf8_string( + OSSL_KEM_PARAM_OPERATION, const_cast(operation), 0), + OSSL_PARAM_END}; + + if (EVP_PKEY_CTX_set_params(ctx, params) <= 0) { + return false; + } + } + + return true; +} +#endif + +std::optional KEM::Encapsulate( + const EVPKeyPointer& public_key) { + ClearErrorOnReturn clear_error_on_return; + + auto ctx = public_key.newCtx(); + if (!ctx) return std::nullopt; + + if (EVP_PKEY_encapsulate_init(ctx.get(), nullptr) <= 0) { + return std::nullopt; + } + +#if !OPENSSL_VERSION_PREREQ(3, 5) + if (!SetOperationParameter(ctx.get(), public_key)) { + return std::nullopt; + } +#endif + + // Determine output buffer sizes + size_t ciphertext_len = 0; + size_t shared_key_len = 0; + + if (EVP_PKEY_encapsulate( + ctx.get(), nullptr, &ciphertext_len, nullptr, &shared_key_len) <= 0) { + return std::nullopt; + } + + auto ciphertext = DataPointer::Alloc(ciphertext_len); + auto shared_key = DataPointer::Alloc(shared_key_len); + if (!ciphertext || !shared_key) return std::nullopt; + + if (EVP_PKEY_encapsulate(ctx.get(), + static_cast(ciphertext.get()), + &ciphertext_len, + static_cast(shared_key.get()), + &shared_key_len) <= 0) { + return std::nullopt; + } + + return EncapsulateResult(std::move(ciphertext), std::move(shared_key)); +} + +DataPointer KEM::Decapsulate(const EVPKeyPointer& private_key, + const Buffer& ciphertext) { + ClearErrorOnReturn clear_error_on_return; + + auto ctx = private_key.newCtx(); + if (!ctx) return {}; + + if (EVP_PKEY_decapsulate_init(ctx.get(), nullptr) <= 0) { + return {}; + } + +#if !OPENSSL_VERSION_PREREQ(3, 5) + if (!SetOperationParameter(ctx.get(), private_key)) { + return {}; + } +#endif + + // First pass: determine shared secret size + size_t shared_key_len = 0; + if (EVP_PKEY_decapsulate(ctx.get(), + nullptr, + &shared_key_len, + static_cast(ciphertext.data), + ciphertext.len) <= 0) { + return {}; + } + + auto shared_key = DataPointer::Alloc(shared_key_len); + if (!shared_key) return {}; + + if (EVP_PKEY_decapsulate(ctx.get(), + static_cast(shared_key.get()), + &shared_key_len, + static_cast(ciphertext.data), + ciphertext.len) <= 0) { + return {}; + } + + return shared_key; +} + +#endif // OPENSSL_VERSION_MAJOR >= 3 + } // namespace ncrypto diff --git a/deps/ncrypto/ncrypto.h b/deps/ncrypto/ncrypto.h index 82af70798f3171..6c62aa28a50e5a 100644 --- a/deps/ncrypto/ncrypto.h +++ b/deps/ncrypto/ncrypto.h @@ -28,11 +28,17 @@ #include #endif // OPENSSL_FIPS -#if OPENSSL_VERSION_MAJOR >= 3 -#define OSSL3_CONST const -#if OPENSSL_VERSION_MINOR >= 5 +// Define OPENSSL_WITH_PQC for post-quantum cryptography support +#if OPENSSL_VERSION_NUMBER >= 0x30500000L +#define OPENSSL_WITH_PQC 1 +#define EVP_PKEY_ML_KEM_512 NID_ML_KEM_512 +#define EVP_PKEY_ML_KEM_768 NID_ML_KEM_768 +#define EVP_PKEY_ML_KEM_1024 NID_ML_KEM_1024 #include #endif + +#if OPENSSL_VERSION_MAJOR >= 3 +#define OSSL3_CONST const #else #define OSSL3_CONST #endif @@ -367,6 +373,10 @@ class Cipher final { static const Cipher AES_128_KW; static const Cipher AES_192_KW; static const Cipher AES_256_KW; + static const Cipher AES_128_OCB; + static const Cipher AES_192_OCB; + static const Cipher AES_256_OCB; + static const Cipher CHACHA20_POLY1305; struct CipherParams { int padding; @@ -731,6 +741,7 @@ class CipherCtxPointer final { int getNid() const; bool isGcmMode() const; + bool isOcbMode() const; bool isCcmMode() const; bool isWrapMode() const; bool isChaCha20Poly1305() const; @@ -820,7 +831,7 @@ class EVPKeyPointer final { const Buffer& data); static EVPKeyPointer NewRawPrivate(int id, const Buffer& data); -#if OPENSSL_VERSION_MAJOR >= 3 && OPENSSL_VERSION_MINOR >= 5 +#if OPENSSL_WITH_PQC static EVPKeyPointer NewRawSeed(int id, const Buffer& data); #endif @@ -917,7 +928,7 @@ class EVPKeyPointer final { DataPointer rawPrivateKey() const; BIOPointer derPublicKey() const; -#if OPENSSL_VERSION_MAJOR >= 3 && OPENSSL_VERSION_MINOR >= 5 +#if OPENSSL_WITH_PQC DataPointer rawSeed() const; #endif @@ -1550,6 +1561,57 @@ DataPointer pbkdf2(const Digest& md, uint32_t iterations, size_t length); +#if OPENSSL_VERSION_NUMBER >= 0x30200000L +#ifndef OPENSSL_NO_ARGON2 +enum class Argon2Type { ARGON2D, ARGON2I, ARGON2ID }; + +DataPointer argon2(const Buffer& pass, + const Buffer& salt, + uint32_t lanes, + size_t length, + uint32_t memcost, + uint32_t iter, + uint32_t version, + const Buffer& secret, + const Buffer& ad, + Argon2Type type); +#endif +#endif + +// ============================================================================ +// KEM (Key Encapsulation Mechanism) +#if OPENSSL_VERSION_MAJOR >= 3 + +class KEM final { + public: + struct EncapsulateResult { + DataPointer ciphertext; + DataPointer shared_key; + + EncapsulateResult() = default; + EncapsulateResult(DataPointer ct, DataPointer sk) + : ciphertext(std::move(ct)), shared_key(std::move(sk)) {} + }; + + // Encapsulate a shared secret using KEM with a public key. + // Returns both the ciphertext and shared secret. + static std::optional Encapsulate( + const EVPKeyPointer& public_key); + + // Decapsulate a shared secret using KEM with a private key and ciphertext. + // Returns the shared secret. + static DataPointer Decapsulate(const EVPKeyPointer& private_key, + const Buffer& ciphertext); + + private: +#if !OPENSSL_VERSION_PREREQ(3, 5) + static bool SetOperationParameter(EVP_PKEY_CTX* ctx, + const EVPKeyPointer& key); +#endif +}; + +#endif // OPENSSL_VERSION_MAJOR >= 3 + // ============================================================================ // Version metadata #define NCRYPTO_VERSION "0.0.1" diff --git a/deps/undici/src/README.md b/deps/undici/src/README.md index 93008eb09918d9..eb69c0ca8f4a5a 100644 --- a/deps/undici/src/README.md +++ b/deps/undici/src/README.md @@ -622,11 +622,11 @@ and `undici.Agent`) which will enable the family autoselection algorithm when es Undici aligns with the Node.js LTS schedule. The following table shows the supported versions: -| Version | Node.js | End of Life | -|---------|-------------|-------------| -| 5.x | v18.x | 2024-04-30 | -| 6.x | v20.x v22.x | 2026-04-30 | -| 7.x | v24.x | 2027-04-30 | +| Undici Version | Bundled in Node.js | Node.js Versions Supported | End of Life | +|----------------|-------------------|----------------------------|-------------| +| 5.x | 18.x | ≥14.0 (tested: 14, 16, 18) | 2024-04-30 | +| 6.x | 20.x, 22.x | ≥18.17 (tested: 18, 20, 21, 22) | 2026-04-30 | +| 7.x | 24.x | ≥20.18.1 (tested: 20, 22, 24) | 2027-04-30 | ## License diff --git a/deps/undici/src/docs/docs/api/DiagnosticsChannel.md b/deps/undici/src/docs/docs/api/DiagnosticsChannel.md index dab13a7df6edb3..096bd58ce29882 100644 --- a/deps/undici/src/docs/docs/api/DiagnosticsChannel.md +++ b/deps/undici/src/docs/docs/api/DiagnosticsChannel.md @@ -169,14 +169,38 @@ This message is published after the client has successfully connected to a serve ```js import diagnosticsChannel from 'diagnostics_channel' -diagnosticsChannel.channel('undici:websocket:open').subscribe(({ address, protocol, extensions, websocket }) => { +diagnosticsChannel.channel('undici:websocket:open').subscribe(({ + address, // { address: string, family: string, port: number } + protocol, // string - negotiated subprotocol + extensions, // string - negotiated extensions + websocket, // WebSocket - the WebSocket instance + handshakeResponse // object - HTTP response that upgraded the connection +}) => { console.log(address) // address, family, and port console.log(protocol) // negotiated subprotocols console.log(extensions) // negotiated extensions console.log(websocket) // the WebSocket instance + + // Handshake response details + console.log(handshakeResponse.status) // 101 for successful WebSocket upgrade + console.log(handshakeResponse.statusText) // 'Switching Protocols' + console.log(handshakeResponse.headers) // Object containing response headers }) ``` +### Handshake Response Object + +The `handshakeResponse` object contains the HTTP response that upgraded the connection to WebSocket: + +- `status` (number): The HTTP status code (101 for successful WebSocket upgrade) +- `statusText` (string): The HTTP status message ('Switching Protocols' for successful upgrade) +- `headers` (object): The HTTP response headers from the server, including: + - `upgrade: 'websocket'` + - `connection: 'upgrade'` + - `sec-websocket-accept` and other WebSocket-related headers + +This information is particularly useful for debugging and monitoring WebSocket connections, as it provides access to the initial HTTP handshake response that established the WebSocket connection. + ## `undici:websocket:close` This message is published after the connection has closed. diff --git a/deps/undici/src/lib/dispatcher/proxy-agent.js b/deps/undici/src/lib/dispatcher/proxy-agent.js index 139ae6d17274d5..f0a71f7adbfb75 100644 --- a/deps/undici/src/lib/dispatcher/proxy-agent.js +++ b/deps/undici/src/lib/dispatcher/proxy-agent.js @@ -1,7 +1,6 @@ 'use strict' const { kProxy, kClose, kDestroy, kDispatch } = require('../core/symbols') -const { URL } = require('node:url') const Agent = require('./agent') const Pool = require('./pool') const DispatcherBase = require('./dispatcher-base') @@ -208,7 +207,7 @@ class ProxyAgent extends DispatcherBase { } /** - * @param {import('../types/proxy-agent').ProxyAgent.Options | string | URL} opts + * @param {import('../../types/proxy-agent').ProxyAgent.Options | string | URL} opts * @returns {URL} */ #getUrl (opts) { diff --git a/deps/undici/src/lib/handler/cache-handler.js b/deps/undici/src/lib/handler/cache-handler.js index 937790aca12292..c21a7206551660 100644 --- a/deps/undici/src/lib/handler/cache-handler.js +++ b/deps/undici/src/lib/handler/cache-handler.js @@ -15,6 +15,15 @@ const HEURISTICALLY_CACHEABLE_STATUS_CODES = [ 200, 203, 204, 206, 300, 301, 308, 404, 405, 410, 414, 501 ] +// Status codes which semantic is not handled by the cache +// https://datatracker.ietf.org/doc/html/rfc9111#section-3 +// This list should not grow beyond 206 and 304 unless the RFC is updated +// by a newer one including more. Please introduce another list if +// implementing caching of responses with the 'must-understand' directive. +const NOT_UNDERSTOOD_STATUS_CODES = [ + 206, 304 +] + const MAX_RESPONSE_AGE = 2147483647000 /** @@ -241,10 +250,19 @@ class CacheHandler { * @param {import('../../types/cache-interceptor.d.ts').default.CacheControlDirectives} cacheControlDirectives */ function canCacheResponse (cacheType, statusCode, resHeaders, cacheControlDirectives) { - // Allow caching for status codes 200 and 307 (original behavior) - // Also allow caching for other status codes that are heuristically cacheable - // when they have explicit cache directives - if (statusCode !== 200 && statusCode !== 307 && !HEURISTICALLY_CACHEABLE_STATUS_CODES.includes(statusCode)) { + // Status code must be final and understood. + if (statusCode < 200 || NOT_UNDERSTOOD_STATUS_CODES.includes(statusCode)) { + return false + } + // Responses with neither status codes that are heuristically cacheable, nor "explicit enough" caching + // directives, are not cacheable. "Explicit enough": see https://www.rfc-editor.org/rfc/rfc9111.html#section-3 + if (!HEURISTICALLY_CACHEABLE_STATUS_CODES.includes(statusCode) && !resHeaders['expires'] && + !cacheControlDirectives.public && + cacheControlDirectives['max-age'] === undefined && + // RFC 9111: a private response directive, if the cache is not shared + !(cacheControlDirectives.private && cacheType === 'private') && + !(cacheControlDirectives['s-maxage'] !== undefined && cacheType === 'shared') + ) { return false } diff --git a/deps/undici/src/lib/interceptor/cache.js b/deps/undici/src/lib/interceptor/cache.js index a2f235b8db34e4..6565baf0a51014 100644 --- a/deps/undici/src/lib/interceptor/cache.js +++ b/deps/undici/src/lib/interceptor/cache.js @@ -6,7 +6,7 @@ const util = require('../core/util') const CacheHandler = require('../handler/cache-handler') const MemoryCacheStore = require('../cache/memory-cache-store') const CacheRevalidationHandler = require('../handler/cache-revalidation-handler') -const { assertCacheStore, assertCacheMethods, makeCacheKey, normaliseHeaders, parseCacheControlHeader } = require('../util/cache.js') +const { assertCacheStore, assertCacheMethods, makeCacheKey, normalizeHeaders, parseCacheControlHeader } = require('../util/cache.js') const { AbortError } = require('../core/errors.js') /** @@ -326,7 +326,7 @@ module.exports = (opts = {}) => { opts = { ...opts, - headers: normaliseHeaders(opts) + headers: normalizeHeaders(opts) } const reqCacheControl = opts.headers?.['cache-control'] diff --git a/deps/undici/src/lib/llhttp/wasm_build_env.txt b/deps/undici/src/lib/llhttp/wasm_build_env.txt index 77e7ad40adc06a..ecb13a031dc5c8 100644 --- a/deps/undici/src/lib/llhttp/wasm_build_env.txt +++ b/deps/undici/src/lib/llhttp/wasm_build_env.txt @@ -1,5 +1,5 @@ -> undici@7.13.0 build:wasm +> undici@7.14.0 build:wasm > node build/wasm.js --docker > docker run --rm --platform=linux/x86_64 --user 1001:118 --mount type=bind,source=/home/runner/work/node/node/deps/undici/src/lib/llhttp,target=/home/node/build/lib/llhttp --mount type=bind,source=/home/runner/work/node/node/deps/undici/src/build,target=/home/node/build/build --mount type=bind,source=/home/runner/work/node/node/deps/undici/src/deps,target=/home/node/build/deps -t ghcr.io/nodejs/wasm-builder@sha256:975f391d907e42a75b8c72eb77c782181e941608687d4d8694c3e9df415a0970 node build/wasm.js diff --git a/deps/undici/src/lib/mock/snapshot-agent.js b/deps/undici/src/lib/mock/snapshot-agent.js index abdd8227e1e367..dbe53575f1d4e8 100644 --- a/deps/undici/src/lib/mock/snapshot-agent.js +++ b/deps/undici/src/lib/mock/snapshot-agent.js @@ -5,6 +5,7 @@ const MockAgent = require('./mock-agent') const { SnapshotRecorder } = require('./snapshot-recorder') const WrapHandler = require('../handler/wrap-handler') const { InvalidArgumentError, UndiciError } = require('../core/errors') +const { validateSnapshotMode } = require('./snapshot-utils') const kSnapshotRecorder = Symbol('kSnapshotRecorder') const kSnapshotMode = Symbol('kSnapshotMode') @@ -12,7 +13,7 @@ const kSnapshotPath = Symbol('kSnapshotPath') const kSnapshotLoaded = Symbol('kSnapshotLoaded') const kRealAgent = Symbol('kRealAgent') -// Static flag to ensure warning is only emitted once +// Static flag to ensure warning is only emitted once per process let warningEmitted = false class SnapshotAgent extends MockAgent { @@ -26,26 +27,24 @@ class SnapshotAgent extends MockAgent { warningEmitted = true } - const mockOptions = { ...opts } - delete mockOptions.mode - delete mockOptions.snapshotPath + const { + mode = 'record', + snapshotPath = null, + ...mockAgentOpts + } = opts - super(mockOptions) + super(mockAgentOpts) - // Validate mode option - const validModes = ['record', 'playback', 'update'] - const mode = opts.mode || 'record' - if (!validModes.includes(mode)) { - throw new InvalidArgumentError(`Invalid snapshot mode: ${mode}. Must be one of: ${validModes.join(', ')}`) - } + validateSnapshotMode(mode) // Validate snapshotPath is provided when required - if ((mode === 'playback' || mode === 'update') && !opts.snapshotPath) { + if ((mode === 'playback' || mode === 'update') && !snapshotPath) { throw new InvalidArgumentError(`snapshotPath is required when mode is '${mode}'`) } this[kSnapshotMode] = mode - this[kSnapshotPath] = opts.snapshotPath + this[kSnapshotPath] = snapshotPath + this[kSnapshotRecorder] = new SnapshotRecorder({ snapshotPath: this[kSnapshotPath], mode: this[kSnapshotMode], @@ -85,7 +84,7 @@ class SnapshotAgent extends MockAgent { // Ensure snapshots are loaded if (!this[kSnapshotLoaded]) { // Need to load asynchronously, delegate to async version - return this._asyncDispatch(opts, handler) + return this.#asyncDispatch(opts, handler) } // Try to find existing snapshot (synchronous) @@ -93,10 +92,10 @@ class SnapshotAgent extends MockAgent { if (snapshot) { // Use recorded response (synchronous) - return this._replaySnapshot(snapshot, handler) + return this.#replaySnapshot(snapshot, handler) } else if (mode === 'update') { // Make real request and record it (async required) - return this._recordAndReplay(opts, handler) + return this.#recordAndReplay(opts, handler) } else { // Playback mode but no snapshot found const error = new UndiciError(`No snapshot found for ${opts.method || 'GET'} ${opts.path}`) @@ -108,16 +107,14 @@ class SnapshotAgent extends MockAgent { } } else if (mode === 'record') { // Record mode - make real request and save response (async required) - return this._recordAndReplay(opts, handler) - } else { - throw new InvalidArgumentError(`Invalid snapshot mode: ${mode}. Must be 'record', 'playback', or 'update'`) + return this.#recordAndReplay(opts, handler) } } /** * Async version of dispatch for when we need to load snapshots first */ - async _asyncDispatch (opts, handler) { + async #asyncDispatch (opts, handler) { await this.loadSnapshots() return this.dispatch(opts, handler) } @@ -125,7 +122,7 @@ class SnapshotAgent extends MockAgent { /** * Records a real request and replays the response */ - _recordAndReplay (opts, handler) { + #recordAndReplay (opts, handler) { const responseData = { statusCode: null, headers: {}, @@ -180,45 +177,46 @@ class SnapshotAgent extends MockAgent { /** * Replays a recorded response + * + * @param {Object} snapshot - The recorded snapshot to replay. + * @param {Object} handler - The handler to call with the response data. + * @returns {void} */ - _replaySnapshot (snapshot, handler) { - return new Promise((resolve) => { - // Simulate the response - setImmediate(() => { - try { - const { response } = snapshot - - const controller = { - pause () {}, - resume () {}, - abort (reason) { - this.aborted = true - this.reason = reason - }, - - aborted: false, - paused: false - } - - handler.onRequestStart(controller) - - handler.onResponseStart(controller, response.statusCode, response.headers) - - // Body is always stored as base64 string - const body = Buffer.from(response.body, 'base64') - handler.onResponseData(controller, body) - - handler.onResponseEnd(controller, response.trailers) - resolve() - } catch (error) { - handler.onError?.(error) - } - }) - }) + #replaySnapshot (snapshot, handler) { + try { + const { response } = snapshot + + const controller = { + pause () { }, + resume () { }, + abort (reason) { + this.aborted = true + this.reason = reason + }, + + aborted: false, + paused: false + } + + handler.onRequestStart(controller) + + handler.onResponseStart(controller, response.statusCode, response.headers) + + // Body is always stored as base64 string + const body = Buffer.from(response.body, 'base64') + handler.onResponseData(controller, body) + + handler.onResponseEnd(controller, response.trailers) + } catch (error) { + handler.onError?.(error) + } } /** * Loads snapshots from file + * + * @param {string} [filePath] - Optional file path to load snapshots from. + * @returns {Promise} - Resolves when snapshots are loaded. */ async loadSnapshots (filePath) { await this[kSnapshotRecorder].loadSnapshots(filePath || this[kSnapshotPath]) @@ -226,12 +224,15 @@ class SnapshotAgent extends MockAgent { // In playback mode, set up MockAgent interceptors for all snapshots if (this[kSnapshotMode] === 'playback') { - this._setupMockInterceptors() + this.#setupMockInterceptors() } } /** * Saves snapshots to file + * + * @param {string} [filePath] - Optional file path to save snapshots to. + * @returns {Promise} - Resolves when snapshots are saved. */ async saveSnapshots (filePath) { return this[kSnapshotRecorder].saveSnapshots(filePath || this[kSnapshotPath]) @@ -248,9 +249,9 @@ class SnapshotAgent extends MockAgent { * * Called automatically when loading snapshots in playback mode. * - * @private + * @returns {void} */ - _setupMockInterceptors () { + #setupMockInterceptors () { for (const snapshot of this[kSnapshotRecorder].getSnapshots()) { const { request, responses, response } = snapshot const url = new URL(request.url) @@ -275,6 +276,7 @@ class SnapshotAgent extends MockAgent { /** * Gets the snapshot recorder + * @return {SnapshotRecorder} - The snapshot recorder instance */ getRecorder () { return this[kSnapshotRecorder] @@ -282,6 +284,7 @@ class SnapshotAgent extends MockAgent { /** * Gets the current mode + * @return {import('./snapshot-utils').SnapshotMode} - The current snapshot mode */ getMode () { return this[kSnapshotMode] @@ -289,6 +292,7 @@ class SnapshotAgent extends MockAgent { /** * Clears all snapshots + * @returns {void} */ clearSnapshots () { this[kSnapshotRecorder].clear() @@ -296,6 +300,7 @@ class SnapshotAgent extends MockAgent { /** * Resets call counts for all snapshots (useful for test cleanup) + * @returns {void} */ resetCallCounts () { this[kSnapshotRecorder].resetCallCounts() @@ -303,6 +308,8 @@ class SnapshotAgent extends MockAgent { /** * Deletes a specific snapshot by request options + * @param {import('./snapshot-recorder').SnapshotRequestOptions} requestOpts - Request options to identify the snapshot + * @return {Promise} - Returns true if the snapshot was deleted, false if not found */ deleteSnapshot (requestOpts) { return this[kSnapshotRecorder].deleteSnapshot(requestOpts) @@ -310,6 +317,7 @@ class SnapshotAgent extends MockAgent { /** * Gets information about a specific snapshot + * @returns {import('./snapshot-recorder').SnapshotInfo|null} - Snapshot information or null if not found */ getSnapshotInfo (requestOpts) { return this[kSnapshotRecorder].getSnapshotInfo(requestOpts) @@ -317,13 +325,19 @@ class SnapshotAgent extends MockAgent { /** * Replaces all snapshots with new data (full replacement) + * @param {Array<{hash: string; snapshot: import('./snapshot-recorder').SnapshotEntryshotEntry}>|Record} snapshotData - New snapshot data to replace existing snapshots + * @returns {void} */ replaceSnapshots (snapshotData) { this[kSnapshotRecorder].replaceSnapshots(snapshotData) } + /** + * Closes the agent, saving snapshots and cleaning up resources. + * + * @returns {Promise} + */ async close () { - // Close recorder (saves snapshots and cleans up timers) await this[kSnapshotRecorder].close() await this[kRealAgent]?.close() await super.close() diff --git a/deps/undici/src/lib/mock/snapshot-recorder.js b/deps/undici/src/lib/mock/snapshot-recorder.js index 7482b5c1914a56..e810fe795072a7 100644 --- a/deps/undici/src/lib/mock/snapshot-recorder.js +++ b/deps/undici/src/lib/mock/snapshot-recorder.js @@ -2,13 +2,93 @@ const { writeFile, readFile, mkdir } = require('node:fs/promises') const { dirname, resolve } = require('node:path') +const { setTimeout, clearTimeout } = require('node:timers') const { InvalidArgumentError, UndiciError } = require('../core/errors') +const { hashId, isUrlExcludedFactory, normalizeHeaders, createHeaderFilters } = require('./snapshot-utils') + +/** + * @typedef {Object} SnapshotRequestOptions + * @property {string} method - HTTP method (e.g. 'GET', 'POST', etc.) + * @property {string} path - Request path + * @property {string} origin - Request origin (base URL) + * @property {import('./snapshot-utils').Headers|import('./snapshot-utils').UndiciHeaders} headers - Request headers + * @property {import('./snapshot-utils').NormalizedHeaders} _normalizedHeaders - Request headers as a lowercase object + * @property {string|Buffer} [body] - Request body (optional) + */ + +/** + * @typedef {Object} SnapshotEntryRequest + * @property {string} method - HTTP method (e.g. 'GET', 'POST', etc.) + * @property {string} url - Full URL of the request + * @property {import('./snapshot-utils').NormalizedHeaders} headers - Normalized headers as a lowercase object + * @property {string|Buffer} [body] - Request body (optional) + */ + +/** + * @typedef {Object} SnapshotEntryResponse + * @property {number} statusCode - HTTP status code of the response + * @property {import('./snapshot-utils').NormalizedHeaders} headers - Normalized response headers as a lowercase object + * @property {string} body - Response body as a base64url encoded string + * @property {Object} [trailers] - Optional response trailers + */ + +/** + * @typedef {Object} SnapshotEntry + * @property {SnapshotEntryRequest} request - The request object + * @property {Array} responses - Array of response objects + * @property {number} callCount - Number of times this snapshot has been called + * @property {string} timestamp - ISO timestamp of when the snapshot was created + */ + +/** + * @typedef {Object} SnapshotRecorderMatchOptions + * @property {Array} [matchHeaders=[]] - Headers to match (empty array means match all headers) + * @property {Array} [ignoreHeaders=[]] - Headers to ignore for matching + * @property {Array} [excludeHeaders=[]] - Headers to exclude from matching + * @property {boolean} [matchBody=true] - Whether to match request body + * @property {boolean} [matchQuery=true] - Whether to match query properties + * @property {boolean} [caseSensitive=false] - Whether header matching is case-sensitive + */ + +/** + * @typedef {Object} SnapshotRecorderOptions + * @property {string} [snapshotPath] - Path to save/load snapshots + * @property {import('./snapshot-utils').SnapshotMode} [mode='record'] - Mode: 'record' or 'playback' + * @property {number} [maxSnapshots=Infinity] - Maximum number of snapshots to keep + * @property {boolean} [autoFlush=false] - Whether to automatically flush snapshots to disk + * @property {number} [flushInterval=30000] - Auto-flush interval in milliseconds (default: 30 seconds) + * @property {Array} [excludeUrls=[]] - URLs to exclude from recording + * @property {function} [shouldRecord=null] - Function to filter requests for recording + * @property {function} [shouldPlayback=null] - Function to filter requests + */ + +/** + * @typedef {Object} SnapshotFormattedRequest + * @property {string} method - HTTP method (e.g. 'GET', 'POST', etc.) + * @property {string} url - Full URL of the request (with query parameters if matchQuery is true) + * @property {import('./snapshot-utils').NormalizedHeaders} headers - Normalized headers as a lowercase object + * @property {string} body - Request body (optional, only if matchBody is true) + */ + +/** + * @typedef {Object} SnapshotInfo + * @property {string} hash - Hash key for the snapshot + * @property {SnapshotEntryRequest} request - The request object + * @property {number} responseCount - Number of responses recorded for this request + * @property {number} callCount - Number of times this snapshot has been called + * @property {string} timestamp - ISO timestamp of when the snapshot was created + */ /** * Formats a request for consistent snapshot storage * Caches normalized headers to avoid repeated processing + * + * @param {SnapshotRequestOptions} opts - Request options + * @param {import('./snapshot-utils').HeaderFilters} headerFilters - Cached header sets for performance + * @param {SnapshotRecorderMatchOptions} [matchOptions] - Matching options for headers and body + * @returns {SnapshotFormattedRequest} - Formatted request object */ -function formatRequestKey (opts, cachedSets, matchOptions = {}) { +function formatRequestKey (opts, headerFilters, matchOptions = {}) { const url = new URL(opts.path, opts.origin) // Cache normalized headers if not already done @@ -20,37 +100,40 @@ function formatRequestKey (opts, cachedSets, matchOptions = {}) { return { method: opts.method || 'GET', url: matchOptions.matchQuery !== false ? url.toString() : `${url.origin}${url.pathname}`, - headers: filterHeadersForMatching(normalized, cachedSets, matchOptions), - body: matchOptions.matchBody !== false && opts.body ? String(opts.body) : undefined + headers: filterHeadersForMatching(normalized, headerFilters, matchOptions), + body: matchOptions.matchBody !== false && opts.body ? String(opts.body) : '' } } /** * Filters headers based on matching configuration + * + * @param {import('./snapshot-utils').Headers} headers - Headers to filter + * @param {import('./snapshot-utils').HeaderFilters} headerFilters - Cached sets for ignore, exclude, and match headers + * @param {SnapshotRecorderMatchOptions} [matchOptions] - Matching options for headers */ -function filterHeadersForMatching (headers, cachedSets, matchOptions = {}) { +function filterHeadersForMatching (headers, headerFilters, matchOptions = {}) { if (!headers || typeof headers !== 'object') return {} const { - matchHeaders = null, caseSensitive = false } = matchOptions const filtered = {} - const { ignoreSet, excludeSet, matchSet } = cachedSets + const { ignore, exclude, match } = headerFilters for (const [key, value] of Object.entries(headers)) { const headerKey = caseSensitive ? key : key.toLowerCase() // Skip if in exclude list (for security) - if (excludeSet.has(headerKey)) continue + if (exclude.has(headerKey)) continue // Skip if in ignore list (for matching) - if (ignoreSet.has(headerKey)) continue + if (ignore.has(headerKey)) continue // If matchHeaders is specified, only include those headers - if (matchHeaders && Array.isArray(matchHeaders)) { - if (!matchSet.has(headerKey)) continue + if (match.size !== 0) { + if (!match.has(headerKey)) continue } filtered[headerKey] = value @@ -61,17 +144,20 @@ function filterHeadersForMatching (headers, cachedSets, matchOptions = {}) { /** * Filters headers for storage (only excludes sensitive headers) + * + * @param {import('./snapshot-utils').Headers} headers - Headers to filter + * @param {import('./snapshot-utils').HeaderFilters} headerFilters - Cached sets for ignore, exclude, and match headers + * @param {SnapshotRecorderMatchOptions} [matchOptions] - Matching options for headers */ -function filterHeadersForStorage (headers, matchOptions = {}) { +function filterHeadersForStorage (headers, headerFilters, matchOptions = {}) { if (!headers || typeof headers !== 'object') return {} const { - excludeHeaders = [], caseSensitive = false } = matchOptions const filtered = {} - const excludeSet = new Set(excludeHeaders.map(h => caseSensitive ? h : h.toLowerCase())) + const { exclude: excludeSet } = headerFilters for (const [key, value] of Object.entries(headers)) { const headerKey = caseSensitive ? key : key.toLowerCase() @@ -86,106 +172,81 @@ function filterHeadersForStorage (headers, matchOptions = {}) { } /** - * Creates cached header sets for performance + * Creates a hash key for request matching + * Properly orders headers to avoid conflicts and uses crypto hashing when available + * + * @param {SnapshotFormattedRequest} formattedRequest - Request object + * @returns {string} - Base64url encoded hash of the request */ -function createHeaderSetsCache (matchOptions = {}) { - const { ignoreHeaders = [], excludeHeaders = [], matchHeaders = null, caseSensitive = false } = matchOptions +function createRequestHash (formattedRequest) { + const parts = [ + formattedRequest.method, + formattedRequest.url + ] - return { - ignoreSet: new Set(ignoreHeaders.map(h => caseSensitive ? h : h.toLowerCase())), - excludeSet: new Set(excludeHeaders.map(h => caseSensitive ? h : h.toLowerCase())), - matchSet: matchHeaders && Array.isArray(matchHeaders) - ? new Set(matchHeaders.map(h => caseSensitive ? h : h.toLowerCase())) - : null - } -} + // Process headers in a deterministic way to avoid conflicts + if (formattedRequest.headers && typeof formattedRequest.headers === 'object') { + const headerKeys = Object.keys(formattedRequest.headers).sort() + for (const key of headerKeys) { + const values = Array.isArray(formattedRequest.headers[key]) + ? formattedRequest.headers[key] + : [formattedRequest.headers[key]] -/** - * Normalizes headers for consistent comparison - */ -function normalizeHeaders (headers) { - if (!headers) return {} - - const normalized = {} - - // Handle array format (undici internal format: [name, value, name, value, ...]) - if (Array.isArray(headers)) { - for (let i = 0; i < headers.length; i += 2) { - const key = headers[i] - const value = headers[i + 1] - if (key && value !== undefined) { - // Convert Buffers to strings if needed - const keyStr = Buffer.isBuffer(key) ? key.toString() : String(key) - const valueStr = Buffer.isBuffer(value) ? value.toString() : String(value) - normalized[keyStr.toLowerCase()] = valueStr - } - } - return normalized - } + // Add header name + parts.push(key) - // Handle object format - if (headers && typeof headers === 'object') { - for (const [key, value] of Object.entries(headers)) { - if (key && typeof key === 'string') { - normalized[key.toLowerCase()] = Array.isArray(value) ? value.join(', ') : String(value) + // Add all values for this header, sorted for consistency + for (const value of values.sort()) { + parts.push(String(value)) } } } - return normalized -} + // Add body + parts.push(formattedRequest.body) -/** - * Creates a hash key for request matching - */ -function createRequestHash (request) { - const parts = [ - request.method, - request.url, - JSON.stringify(request.headers, Object.keys(request.headers).sort()), - request.body || '' - ] - return Buffer.from(parts.join('|')).toString('base64url') -} + const content = parts.join('|') -/** - * Checks if a URL matches any of the exclude patterns - */ -function isUrlExcluded (url, excludePatterns = []) { - if (!excludePatterns.length) return false - - for (const pattern of excludePatterns) { - if (typeof pattern === 'string') { - // Simple string match (case-insensitive) - if (url.toLowerCase().includes(pattern.toLowerCase())) { - return true - } - } else if (pattern instanceof RegExp) { - // Regex pattern match - if (pattern.test(url)) { - return true - } - } - } - - return false + return hashId(content) } class SnapshotRecorder { + /** @type {NodeJS.Timeout | null} */ + #flushTimeout + + /** @type {import('./snapshot-utils').IsUrlExcluded} */ + #isUrlExcluded + + /** @type {Map} */ + #snapshots = new Map() + + /** @type {string|undefined} */ + #snapshotPath + + /** @type {number} */ + #maxSnapshots = Infinity + + /** @type {boolean} */ + #autoFlush = false + + /** @type {import('./snapshot-utils').HeaderFilters} */ + #headerFilters + + /** + * Creates a new SnapshotRecorder instance + * @param {SnapshotRecorderOptions&SnapshotRecorderMatchOptions} [options={}] - Configuration options for the recorder + */ constructor (options = {}) { - this.snapshots = new Map() - this.snapshotPath = options.snapshotPath - this.mode = options.mode || 'record' - this.loaded = false - this.maxSnapshots = options.maxSnapshots || Infinity - this.autoFlush = options.autoFlush || false + this.#snapshotPath = options.snapshotPath + this.#maxSnapshots = options.maxSnapshots || Infinity + this.#autoFlush = options.autoFlush || false this.flushInterval = options.flushInterval || 30000 // 30 seconds default this._flushTimer = null - this._flushTimeout = null // Matching configuration + /** @type {Required} */ this.matchOptions = { - matchHeaders: options.matchHeaders || null, // null means match all headers + matchHeaders: options.matchHeaders || [], // empty means match all headers ignoreHeaders: options.ignoreHeaders || [], excludeHeaders: options.excludeHeaders || [], matchBody: options.matchBody !== false, // default: true @@ -194,46 +255,49 @@ class SnapshotRecorder { } // Cache processed header sets to avoid recreating them on every request - this._headerSetsCache = createHeaderSetsCache(this.matchOptions) + this.#headerFilters = createHeaderFilters(this.matchOptions) // Request filtering callbacks - this.shouldRecord = options.shouldRecord || null // function(requestOpts) -> boolean - this.shouldPlayback = options.shouldPlayback || null // function(requestOpts) -> boolean + this.shouldRecord = options.shouldRecord || (() => true) // function(requestOpts) -> boolean + this.shouldPlayback = options.shouldPlayback || (() => true) // function(requestOpts) -> boolean // URL pattern filtering - this.excludeUrls = options.excludeUrls || [] // Array of regex patterns or strings + this.#isUrlExcluded = isUrlExcludedFactory(options.excludeUrls) // Array of regex patterns or strings // Start auto-flush timer if enabled - if (this.autoFlush && this.snapshotPath) { - this._startAutoFlush() + if (this.#autoFlush && this.#snapshotPath) { + this.#startAutoFlush() } } /** * Records a request-response interaction + * @param {SnapshotRequestOptions} requestOpts - Request options + * @param {SnapshotEntryResponse} response - Response data to record + * @return {Promise} - Resolves when the recording is complete */ async record (requestOpts, response) { // Check if recording should be filtered out - if (this.shouldRecord && typeof this.shouldRecord === 'function') { - if (!this.shouldRecord(requestOpts)) { - return // Skip recording - } + if (!this.shouldRecord(requestOpts)) { + return // Skip recording } // Check URL exclusion patterns const url = new URL(requestOpts.path, requestOpts.origin).toString() - if (isUrlExcluded(url, this.excludeUrls)) { + if (this.#isUrlExcluded(url)) { return // Skip recording } - const request = formatRequestKey(requestOpts, this._headerSetsCache, this.matchOptions) + const request = formatRequestKey(requestOpts, this.#headerFilters, this.matchOptions) const hash = createRequestHash(request) // Extract response data - always store body as base64 const normalizedHeaders = normalizeHeaders(response.headers) + + /** @type {SnapshotEntryResponse} */ const responseData = { statusCode: response.statusCode, - headers: filterHeadersForStorage(normalizedHeaders, this.matchOptions), + headers: filterHeadersForStorage(normalizedHeaders, this.#headerFilters, this.matchOptions), body: Buffer.isBuffer(response.body) ? response.body.toString('base64') : Buffer.from(String(response.body || '')).toString('base64'), @@ -241,18 +305,18 @@ class SnapshotRecorder { } // Remove oldest snapshot if we exceed maxSnapshots limit - if (this.snapshots.size >= this.maxSnapshots && !this.snapshots.has(hash)) { - const oldestKey = this.snapshots.keys().next().value - this.snapshots.delete(oldestKey) + if (this.#snapshots.size >= this.#maxSnapshots && !this.#snapshots.has(hash)) { + const oldestKey = this.#snapshots.keys().next().value + this.#snapshots.delete(oldestKey) } // Support sequential responses - if snapshot exists, add to responses array - const existingSnapshot = this.snapshots.get(hash) + const existingSnapshot = this.#snapshots.get(hash) if (existingSnapshot && existingSnapshot.responses) { existingSnapshot.responses.push(responseData) existingSnapshot.timestamp = new Date().toISOString() } else { - this.snapshots.set(hash, { + this.#snapshots.set(hash, { request, responses: [responseData], // Always store as array for consistency callCount: 0, @@ -261,67 +325,54 @@ class SnapshotRecorder { } // Auto-flush if enabled - if (this.autoFlush && this.snapshotPath) { - this._scheduleFlush() + if (this.#autoFlush && this.#snapshotPath) { + this.#scheduleFlush() } } /** * Finds a matching snapshot for the given request * Returns the appropriate response based on call count for sequential responses + * + * @param {SnapshotRequestOptions} requestOpts - Request options to match + * @returns {SnapshotEntry&Record<'response', SnapshotEntryResponse>|undefined} - Matching snapshot response or undefined if not found */ findSnapshot (requestOpts) { // Check if playback should be filtered out - if (this.shouldPlayback && typeof this.shouldPlayback === 'function') { - if (!this.shouldPlayback(requestOpts)) { - return undefined // Skip playback - } + if (!this.shouldPlayback(requestOpts)) { + return undefined // Skip playback } // Check URL exclusion patterns const url = new URL(requestOpts.path, requestOpts.origin).toString() - if (isUrlExcluded(url, this.excludeUrls)) { + if (this.#isUrlExcluded(url)) { return undefined // Skip playback } - const request = formatRequestKey(requestOpts, this._headerSetsCache, this.matchOptions) + const request = formatRequestKey(requestOpts, this.#headerFilters, this.matchOptions) const hash = createRequestHash(request) - const snapshot = this.snapshots.get(hash) + const snapshot = this.#snapshots.get(hash) if (!snapshot) return undefined // Handle sequential responses - if (snapshot.responses && Array.isArray(snapshot.responses)) { - const currentCallCount = snapshot.callCount || 0 - const responseIndex = Math.min(currentCallCount, snapshot.responses.length - 1) - snapshot.callCount = currentCallCount + 1 - - return { - ...snapshot, - response: snapshot.responses[responseIndex] - } - } + const currentCallCount = snapshot.callCount || 0 + const responseIndex = Math.min(currentCallCount, snapshot.responses.length - 1) + snapshot.callCount = currentCallCount + 1 - // Legacy format compatibility - convert single response to array format - if (snapshot.response && !snapshot.responses) { - snapshot.responses = [snapshot.response] - snapshot.callCount = 1 - delete snapshot.response - - return { - ...snapshot, - response: snapshot.responses[0] - } + return { + ...snapshot, + response: snapshot.responses[responseIndex] } - - return snapshot } /** * Loads snapshots from file + * @param {string} [filePath] - Optional file path to load snapshots from + * @return {Promise} - Resolves when snapshots are loaded */ async loadSnapshots (filePath) { - const path = filePath || this.snapshotPath + const path = filePath || this.#snapshotPath if (!path) { throw new InvalidArgumentError('Snapshot path is required') } @@ -332,21 +383,18 @@ class SnapshotRecorder { // Convert array format back to Map if (Array.isArray(parsed)) { - this.snapshots.clear() + this.#snapshots.clear() for (const { hash, snapshot } of parsed) { - this.snapshots.set(hash, snapshot) + this.#snapshots.set(hash, snapshot) } } else { // Legacy object format - this.snapshots = new Map(Object.entries(parsed)) + this.#snapshots = new Map(Object.entries(parsed)) } - - this.loaded = true } catch (error) { if (error.code === 'ENOENT') { // File doesn't exist yet - that's ok for recording mode - this.snapshots.clear() - this.loaded = true + this.#snapshots.clear() } else { throw new UndiciError(`Failed to load snapshots from ${path}`, { cause: error }) } @@ -355,9 +403,12 @@ class SnapshotRecorder { /** * Saves snapshots to file + * + * @param {string} [filePath] - Optional file path to save snapshots + * @returns {Promise} - Resolves when snapshots are saved */ async saveSnapshots (filePath) { - const path = filePath || this.snapshotPath + const path = filePath || this.#snapshotPath if (!path) { throw new InvalidArgumentError('Snapshot path is required') } @@ -368,67 +419,75 @@ class SnapshotRecorder { await mkdir(dirname(resolvedPath), { recursive: true }) // Convert Map to serializable format - const data = Array.from(this.snapshots.entries()).map(([hash, snapshot]) => ({ + const data = Array.from(this.#snapshots.entries()).map(([hash, snapshot]) => ({ hash, snapshot })) - await writeFile(resolvedPath, JSON.stringify(data, null, 2), 'utf8', { flush: true }) + await writeFile(resolvedPath, JSON.stringify(data, null, 2), { flush: true }) } /** * Clears all recorded snapshots + * @returns {void} */ clear () { - this.snapshots.clear() + this.#snapshots.clear() } /** * Gets all recorded snapshots + * @return {Array} - Array of all recorded snapshots */ getSnapshots () { - return Array.from(this.snapshots.values()) + return Array.from(this.#snapshots.values()) } /** * Gets snapshot count + * @return {number} - Number of recorded snapshots */ size () { - return this.snapshots.size + return this.#snapshots.size } /** * Resets call counts for all snapshots (useful for test cleanup) + * @returns {void} */ resetCallCounts () { - for (const snapshot of this.snapshots.values()) { + for (const snapshot of this.#snapshots.values()) { snapshot.callCount = 0 } } /** * Deletes a specific snapshot by request options + * @param {SnapshotRequestOptions} requestOpts - Request options to match + * @returns {boolean} - True if snapshot was deleted, false if not found */ deleteSnapshot (requestOpts) { - const request = formatRequestKey(requestOpts, this._headerSetsCache, this.matchOptions) + const request = formatRequestKey(requestOpts, this.#headerFilters, this.matchOptions) const hash = createRequestHash(request) - return this.snapshots.delete(hash) + return this.#snapshots.delete(hash) } /** * Gets information about a specific snapshot + * @param {SnapshotRequestOptions} requestOpts - Request options to match + * @returns {SnapshotInfo|null} - Snapshot information or null if not found */ getSnapshotInfo (requestOpts) { - const request = formatRequestKey(requestOpts, this._headerSetsCache, this.matchOptions) + const request = formatRequestKey(requestOpts, this.#headerFilters, this.matchOptions) const hash = createRequestHash(request) - const snapshot = this.snapshots.get(hash) + const snapshot = this.#snapshots.get(hash) if (!snapshot) return null return { hash, request: snapshot.request, - responseCount: snapshot.responses ? snapshot.responses.length : (snapshot.response ? 1 : 0), + responseCount: snapshot.responses ? snapshot.responses.length : (snapshot.response ? 1 : 0), // .response for legacy snapshots callCount: snapshot.callCount || 0, timestamp: snapshot.timestamp } @@ -436,76 +495,80 @@ class SnapshotRecorder { /** * Replaces all snapshots with new data (full replacement) + * @param {Array<{hash: string; snapshot: SnapshotEntry}>|Record} snapshotData - New snapshot data to replace existing ones + * @returns {void} */ replaceSnapshots (snapshotData) { - this.snapshots.clear() + this.#snapshots.clear() if (Array.isArray(snapshotData)) { for (const { hash, snapshot } of snapshotData) { - this.snapshots.set(hash, snapshot) + this.#snapshots.set(hash, snapshot) } } else if (snapshotData && typeof snapshotData === 'object') { // Legacy object format - this.snapshots = new Map(Object.entries(snapshotData)) + this.#snapshots = new Map(Object.entries(snapshotData)) } } /** * Starts the auto-flush timer + * @returns {void} */ - _startAutoFlush () { - if (!this._flushTimer) { - this._flushTimer = setInterval(() => { - this.saveSnapshots().catch(() => { - // Ignore flush errors - they shouldn't interrupt normal operation - }) - }, this.flushInterval) - } + #startAutoFlush () { + return this.#scheduleFlush() } /** * Stops the auto-flush timer + * @returns {void} */ - _stopAutoFlush () { - if (this._flushTimer) { - clearInterval(this._flushTimer) - this._flushTimer = null + #stopAutoFlush () { + if (this.#flushTimeout) { + clearTimeout(this.#flushTimeout) + // Ensure any pending flush is completed + this.saveSnapshots().catch(() => { + // Ignore flush errors + }) + this.#flushTimeout = null } } /** * Schedules a flush (debounced to avoid excessive writes) */ - _scheduleFlush () { - // Simple debouncing - clear existing timeout and set new one - if (this._flushTimeout) { - clearTimeout(this._flushTimeout) - } - this._flushTimeout = setTimeout(() => { + #scheduleFlush () { + this.#flushTimeout = setTimeout(() => { this.saveSnapshots().catch(() => { // Ignore flush errors }) - this._flushTimeout = null + if (this.#autoFlush) { + this.#flushTimeout?.refresh() + } else { + this.#flushTimeout = null + } }, 1000) // 1 second debounce } /** * Cleanup method to stop timers + * @returns {void} */ destroy () { - this._stopAutoFlush() - if (this._flushTimeout) { - clearTimeout(this._flushTimeout) - this._flushTimeout = null + this.#stopAutoFlush() + if (this.#flushTimeout) { + clearTimeout(this.#flushTimeout) + this.#flushTimeout = null } } /** * Async close method that saves all recordings and performs cleanup + * @returns {Promise} */ async close () { // Save any pending recordings if we have a snapshot path - if (this.snapshotPath && this.snapshots.size > 0) { + if (this.#snapshotPath && this.#snapshots.size !== 0) { await this.saveSnapshots() } @@ -514,4 +577,4 @@ class SnapshotRecorder { } } -module.exports = { SnapshotRecorder, formatRequestKey, createRequestHash, filterHeadersForMatching, filterHeadersForStorage, isUrlExcluded, createHeaderSetsCache } +module.exports = { SnapshotRecorder, formatRequestKey, createRequestHash, filterHeadersForMatching, filterHeadersForStorage, createHeaderFilters } diff --git a/deps/undici/src/lib/mock/snapshot-utils.js b/deps/undici/src/lib/mock/snapshot-utils.js new file mode 100644 index 00000000000000..ebad12e888ff29 --- /dev/null +++ b/deps/undici/src/lib/mock/snapshot-utils.js @@ -0,0 +1,158 @@ +'use strict' + +const { InvalidArgumentError } = require('../core/errors') + +/** + * @typedef {Object} HeaderFilters + * @property {Set} ignore - Set of headers to ignore for matching + * @property {Set} exclude - Set of headers to exclude from matching + * @property {Set} match - Set of headers to match (empty means match + */ + +/** + * Creates cached header sets for performance + * + * @param {import('./snapshot-recorder').SnapshotRecorderMatchOptions} matchOptions - Matching options for headers + * @returns {HeaderFilters} - Cached sets for ignore, exclude, and match headers + */ +function createHeaderFilters (matchOptions = {}) { + const { ignoreHeaders = [], excludeHeaders = [], matchHeaders = [], caseSensitive = false } = matchOptions + + return { + ignore: new Set(ignoreHeaders.map(header => caseSensitive ? header : header.toLowerCase())), + exclude: new Set(excludeHeaders.map(header => caseSensitive ? header : header.toLowerCase())), + match: new Set(matchHeaders.map(header => caseSensitive ? header : header.toLowerCase())) + } +} + +let crypto +try { + crypto = require('node:crypto') +} catch { /* Fallback if crypto is not available */ } + +/** + * @callback HashIdFunction + * @param {string} value - The value to hash + * @returns {string} - The base64url encoded hash of the value + */ + +/** + * Generates a hash for a given value + * @type {HashIdFunction} + */ +const hashId = crypto?.hash + ? (value) => crypto.hash('sha256', value, 'base64url') + : (value) => Buffer.from(value).toString('base64url') + +/** + * @typedef {(url: string) => boolean} IsUrlExcluded Checks if a URL matches any of the exclude patterns + */ + +/** @typedef {{[key: Lowercase]: string}} NormalizedHeaders */ +/** @typedef {Array} UndiciHeaders */ +/** @typedef {Record} Headers */ + +/** + * @param {*} headers + * @returns {headers is UndiciHeaders} + */ +function isUndiciHeaders (headers) { + return Array.isArray(headers) && (headers.length & 1) === 0 +} + +/** + * Factory function to create a URL exclusion checker + * @param {Array} [excludePatterns=[]] - Array of patterns to exclude + * @returns {IsUrlExcluded} - A function that checks if a URL matches any of the exclude patterns + */ +function isUrlExcludedFactory (excludePatterns = []) { + if (excludePatterns.length === 0) { + return () => false + } + + return function isUrlExcluded (url) { + let urlLowerCased + + for (const pattern of excludePatterns) { + if (typeof pattern === 'string') { + if (!urlLowerCased) { + // Convert URL to lowercase only once + urlLowerCased = url.toLowerCase() + } + // Simple string match (case-insensitive) + if (urlLowerCased.includes(pattern.toLowerCase())) { + return true + } + } else if (pattern instanceof RegExp) { + // Regex pattern match + if (pattern.test(url)) { + return true + } + } + } + + return false + } +} + +/** + * Normalizes headers for consistent comparison + * + * @param {Object|UndiciHeaders} headers - Headers to normalize + * @returns {NormalizedHeaders} - Normalized headers as a lowercase object + */ +function normalizeHeaders (headers) { + /** @type {NormalizedHeaders} */ + const normalizedHeaders = {} + + if (!headers) return normalizedHeaders + + // Handle array format (undici internal format: [name, value, name, value, ...]) + if (isUndiciHeaders(headers)) { + for (let i = 0; i < headers.length; i += 2) { + const key = headers[i] + const value = headers[i + 1] + if (key && value !== undefined) { + // Convert Buffers to strings if needed + const keyStr = Buffer.isBuffer(key) ? key.toString() : key + const valueStr = Buffer.isBuffer(value) ? value.toString() : value + normalizedHeaders[keyStr.toLowerCase()] = valueStr + } + } + return normalizedHeaders + } + + // Handle object format + if (headers && typeof headers === 'object') { + for (const [key, value] of Object.entries(headers)) { + if (key && typeof key === 'string') { + normalizedHeaders[key.toLowerCase()] = Array.isArray(value) ? value.join(', ') : String(value) + } + } + } + + return normalizedHeaders +} + +const validSnapshotModes = /** @type {const} */ (['record', 'playback', 'update']) + +/** @typedef {typeof validSnapshotModes[number]} SnapshotMode */ + +/** + * @param {*} mode - The snapshot mode to validate + * @returns {asserts mode is SnapshotMode} + */ +function validateSnapshotMode (mode) { + if (!validSnapshotModes.includes(mode)) { + throw new InvalidArgumentError(`Invalid snapshot mode: ${mode}. Must be one of: ${validSnapshotModes.join(', ')}`) + } +} + +module.exports = { + createHeaderFilters, + hashId, + isUndiciHeaders, + normalizeHeaders, + isUrlExcludedFactory, + validateSnapshotMode +} diff --git a/deps/undici/src/lib/util/cache.js b/deps/undici/src/lib/util/cache.js index 53851df1d1bbd9..3c2eb0dbd29d6a 100644 --- a/deps/undici/src/lib/util/cache.js +++ b/deps/undici/src/lib/util/cache.js @@ -34,7 +34,7 @@ function makeCacheKey (opts) { * @param {Record} * @returns {Record} */ -function normaliseHeaders (opts) { +function normalizeHeaders (opts) { let headers if (opts.headers == null) { headers = {} @@ -234,7 +234,7 @@ function parseCacheControlHeader (header) { } } } else { - // Something like `no-cache=some-header` + // Something like `no-cache="some-header"` if (key in output) { output[key] = output[key].concat(value) } else { @@ -367,7 +367,7 @@ function assertCacheMethods (methods, name = 'CacheMethods') { module.exports = { makeCacheKey, - normaliseHeaders, + normalizeHeaders, assertCacheKey, assertCacheValue, parseCacheControlHeader, diff --git a/deps/undici/src/lib/web/cache/cache.js b/deps/undici/src/lib/web/cache/cache.js index dd9e2f8163ad3a..70a3787a71d415 100644 --- a/deps/undici/src/lib/web/cache/cache.js +++ b/deps/undici/src/lib/web/cache/cache.js @@ -18,7 +18,7 @@ const { createDeferredPromise } = require('../../util/promise') * @property {'delete' | 'put'} type * @property {any} request * @property {any} response - * @property {import('../../types/cache').CacheQueryOptions} options + * @property {import('../../../types/cache').CacheQueryOptions} options */ /** @@ -452,7 +452,7 @@ class Cache { /** * @see https://w3c.github.io/ServiceWorker/#dom-cache-keys * @param {any} request - * @param {import('../../types/cache').CacheQueryOptions} options + * @param {import('../../../types/cache').CacheQueryOptions} options * @returns {Promise} */ async keys (request = undefined, options = {}) { @@ -670,7 +670,7 @@ class Cache { /** * @see https://w3c.github.io/ServiceWorker/#query-cache * @param {any} requestQuery - * @param {import('../../types/cache').CacheQueryOptions} options + * @param {import('../../../types/cache').CacheQueryOptions} options * @param {requestResponseList} targetStorage * @returns {requestResponseList} */ @@ -695,7 +695,7 @@ class Cache { * @param {any} requestQuery * @param {any} request * @param {any | null} response - * @param {import('../../types/cache').CacheQueryOptions | undefined} options + * @param {import('../../../types/cache').CacheQueryOptions | undefined} options * @returns {boolean} */ #requestMatchesCachedItem (requestQuery, request, response = null, options) { diff --git a/deps/undici/src/lib/web/eventsource/eventsource.js b/deps/undici/src/lib/web/eventsource/eventsource.js index 746400b8ea97a5..1ff4e36ca2a071 100644 --- a/deps/undici/src/lib/web/eventsource/eventsource.js +++ b/deps/undici/src/lib/web/eventsource/eventsource.js @@ -124,10 +124,10 @@ class EventSource extends EventTarget { url = webidl.converters.USVString(url) eventSourceInitDict = webidl.converters.EventSourceInitDict(eventSourceInitDict, prefix, 'eventSourceInitDict') - this.#dispatcher = eventSourceInitDict.dispatcher + this.#dispatcher = eventSourceInitDict.node.dispatcher || eventSourceInitDict.dispatcher this.#state = { lastEventId: '', - reconnectionTime: defaultReconnectionTime + reconnectionTime: eventSourceInitDict.node.reconnectionTime } // 2. Let settings be ev's relevant settings object. @@ -472,6 +472,21 @@ webidl.converters.EventSourceInitDict = webidl.dictionaryConverter([ { key: 'dispatcher', // undici only converter: webidl.converters.any + }, + { + key: 'node', // undici only + converter: webidl.dictionaryConverter([ + { + key: 'reconnectionTime', + converter: webidl.converters['unsigned long'], + defaultValue: () => defaultReconnectionTime + }, + { + key: 'dispatcher', + converter: webidl.converters.any + } + ]), + defaultValue: () => ({}) } ]) diff --git a/deps/undici/src/lib/web/fetch/formdata.js b/deps/undici/src/lib/web/fetch/formdata.js index e21ee3f553e96e..c21fb06a3eeb62 100644 --- a/deps/undici/src/lib/web/fetch/formdata.js +++ b/deps/undici/src/lib/web/fetch/formdata.js @@ -9,7 +9,7 @@ const nodeUtil = require('node:util') class FormData { #state = [] - constructor (form) { + constructor (form = undefined) { webidl.util.markAsUncloneable(this) if (form !== undefined) { diff --git a/deps/undici/src/lib/web/fetch/response.js b/deps/undici/src/lib/web/fetch/response.js index fba44ef6dcacf0..5f11f449477f8b 100644 --- a/deps/undici/src/lib/web/fetch/response.js +++ b/deps/undici/src/lib/web/fetch/response.js @@ -22,7 +22,8 @@ const { webidl } = require('../webidl') const { URLSerializer } = require('./data-url') const { kConstruct } = require('../../core/symbols') const assert = require('node:assert') -const { types } = require('node:util') + +const { isArrayBuffer } = nodeUtil.types const textEncoder = new TextEncoder('utf-8') @@ -243,6 +244,11 @@ class Response { // 2. Let clonedResponse be the result of cloning this’s response. const clonedResponse = cloneResponse(this.#state) + // Note: To re-register because of a new stream. + if (this.#state.body?.stream) { + streamRegistry.register(this, new WeakRef(this.#state.body.stream)) + } + // 3. Return the result of creating a Response object, given // clonedResponse, this’s headers’s guard, and this’s relevant Realm. return fromInnerResponse(clonedResponse, getHeadersGuard(this.#headers)) @@ -353,8 +359,6 @@ function cloneResponse (response) { // result of cloning response’s body. if (response.body != null) { newResponse.body = cloneBody(response.body) - - streamRegistry.register(newResponse, new WeakRef(response.body.stream)) } // 4. Return newResponse. @@ -576,7 +580,7 @@ webidl.converters.XMLHttpRequestBodyInit = function (V, prefix, name) { return V } - if (ArrayBuffer.isView(V) || types.isArrayBuffer(V)) { + if (ArrayBuffer.isView(V) || isArrayBuffer(V)) { return V } diff --git a/deps/undici/src/lib/web/websocket/stream/websocketstream.js b/deps/undici/src/lib/web/websocket/stream/websocketstream.js index dc364a00e66160..e7a8bce614a11d 100644 --- a/deps/undici/src/lib/web/websocket/stream/websocketstream.js +++ b/deps/undici/src/lib/web/websocket/stream/websocketstream.js @@ -6,7 +6,7 @@ const { states, opcodes, sentCloseFrameState } = require('../constants') const { webidl } = require('../../webidl') const { getURLRecord, isValidSubprotocol, isEstablished, utf8Decode } = require('../util') const { establishWebSocketConnection, failWebsocketConnection, closeWebSocketConnection } = require('../connection') -const { types } = require('node:util') +const { isArrayBuffer } = require('node:util/types') const { channels } = require('../../../core/diagnostics') const { WebsocketFrameSend } = require('../frame') const { ByteParser } = require('../receiver') @@ -210,7 +210,7 @@ class WebSocketStream { let opcode = null // 4. If chunk is a BufferSource , - if (ArrayBuffer.isView(chunk) || types.isArrayBuffer(chunk)) { + if (ArrayBuffer.isView(chunk) || isArrayBuffer(chunk)) { // 4.1. Set data to a copy of the bytes given chunk . data = new Uint8Array(ArrayBuffer.isView(chunk) ? new Uint8Array(chunk.buffer, chunk.byteOffset, chunk.byteLength) : chunk) diff --git a/deps/undici/src/lib/web/websocket/websocket.js b/deps/undici/src/lib/web/websocket/websocket.js index 5688c8ad8a0563..1f10cb0a73a7ed 100644 --- a/deps/undici/src/lib/web/websocket/websocket.js +++ b/deps/undici/src/lib/web/websocket/websocket.js @@ -1,5 +1,6 @@ 'use strict' +const { isArrayBuffer } = require('node:util/types') const { webidl } = require('../webidl') const { URLSerializer } = require('../fetch/data-url') const { environmentSettingsObject } = require('../fetch/util') @@ -19,7 +20,6 @@ const { establishWebSocketConnection, closeWebSocketConnection, failWebsocketCon const { ByteParser } = require('./receiver') const { kEnumerableProperty } = require('../../core/util') const { getGlobalDispatcher } = require('../../global') -const { types } = require('node:util') const { ErrorEvent, CloseEvent, createFastMessageEvent } = require('./events') const { SendQueue } = require('./sender') const { WebsocketFrameSend } = require('./frame') @@ -257,7 +257,7 @@ class WebSocket extends EventTarget { this.#sendQueue.add(buffer, () => { this.#bufferedAmount -= buffer.byteLength }, sendHints.text) - } else if (types.isArrayBuffer(data)) { + } else if (isArrayBuffer(data)) { // If the WebSocket connection is established, and the WebSocket // closing handshake has not yet started, then the user agent must // send a WebSocket Message comprised of data using a binary frame @@ -482,11 +482,18 @@ class WebSocket extends EventTarget { fireEvent('open', this) if (channels.open.hasSubscribers) { + // Convert headers to a plain object for the event + const headers = response.headersList.entries channels.open.publish({ address: response.socket.address(), protocol: this.#protocol, extensions: this.#extensions, - websocket: this + websocket: this, + handshakeResponse: { + status: response.status, + statusText: response.statusText, + headers + } }) } } @@ -728,7 +735,7 @@ webidl.converters.WebSocketSendData = function (V) { return V } - if (ArrayBuffer.isView(V) || types.isArrayBuffer(V)) { + if (ArrayBuffer.isView(V) || isArrayBuffer(V)) { return V } } diff --git a/deps/undici/src/package-lock.json b/deps/undici/src/package-lock.json index 57498279f5040c..80ef7918ad3918 100644 --- a/deps/undici/src/package-lock.json +++ b/deps/undici/src/package-lock.json @@ -1,33 +1,33 @@ { "name": "undici", - "version": "7.13.0", + "version": "7.14.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "undici", - "version": "7.13.0", + "version": "7.14.0", "license": "MIT", "devDependencies": { "@fastify/busboy": "3.1.1", "@matteo.collina/tspl": "^0.2.0", + "@metcoder95/https-pem": "^1.0.0", "@sinonjs/fake-timers": "^12.0.0", "@types/node": "^18.19.50", "abort-controller": "^3.0.0", "borp": "^0.20.0", "c8": "^10.0.0", - "cross-env": "^7.0.3", + "cross-env": "^10.0.0", "dns-packet": "^5.4.0", "esbuild": "^0.25.2", "eslint": "^9.9.0", "fast-check": "^4.1.1", - "https-pem": "^3.0.0", "husky": "^9.0.7", - "jest": "^29.0.2", + "jest": "^30.0.5", "neostandard": "^0.12.0", "node-forge": "^1.3.1", "proxy": "^2.1.1", - "tsd": "^0.32.0", + "tsd": "^0.33.0", "typescript": "^5.6.2", "ws": "^8.11.0" }, @@ -114,22 +114,22 @@ } }, "node_modules/@babel/core": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.0.tgz", - "integrity": "sha512-UlLAnTPrFdNGoFtbSXwcGFQBtQZJCNjaN6hQNP3UPvuNXT1i82N26KL3dZeIpNalWywr9IuQuncaAfUaS1g6sQ==", + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.3.tgz", + "integrity": "sha512-yDBHV9kQNcr2/sUr9jghVyz9C3Y5G2zUM2H2lo+9mKv4sFgbA8s8Z9t8D1jiTkGoO/NoIfKMyKWr4s6CN23ZwQ==", "dev": true, "license": "MIT", "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.27.1", - "@babel/generator": "^7.28.0", + "@babel/generator": "^7.28.3", "@babel/helper-compilation-targets": "^7.27.2", - "@babel/helper-module-transforms": "^7.27.3", - "@babel/helpers": "^7.27.6", - "@babel/parser": "^7.28.0", + "@babel/helper-module-transforms": "^7.28.3", + "@babel/helpers": "^7.28.3", + "@babel/parser": "^7.28.3", "@babel/template": "^7.27.2", - "@babel/traverse": "^7.28.0", - "@babel/types": "^7.28.0", + "@babel/traverse": "^7.28.3", + "@babel/types": "^7.28.2", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", @@ -145,14 +145,14 @@ } }, "node_modules/@babel/generator": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.0.tgz", - "integrity": "sha512-lJjzvrbEeWrhB4P3QBsH7tey117PjLZnDbLiQEKjQ/fNJTjuq4HSqgFA+UNSwZT8D7dxxbnuSBMsa1lrWzKlQg==", + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.3.tgz", + "integrity": "sha512-3lSpxGgvnmZznmBkCRnVREPUFJv2wrv9iAoFDvADJc0ypmdOxdUtcLeBgBJ6zE0PMeTKnxeQzyk0xTBq4Ep7zw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/parser": "^7.28.0", - "@babel/types": "^7.28.0", + "@babel/parser": "^7.28.3", + "@babel/types": "^7.28.2", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" @@ -203,15 +203,15 @@ } }, "node_modules/@babel/helper-module-transforms": { - "version": "7.27.3", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.27.3.tgz", - "integrity": "sha512-dSOvYwvyLsWBeIRyOeHXp5vPj5l1I011r52FM1+r1jCERv+aFXYk4whgQccYEGYxK2H3ZAIA8nuPkQ0HaUo3qg==", + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.3.tgz", + "integrity": "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==", "dev": true, "license": "MIT", "dependencies": { "@babel/helper-module-imports": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1", - "@babel/traverse": "^7.27.3" + "@babel/traverse": "^7.28.3" }, "engines": { "node": ">=6.9.0" @@ -261,9 +261,9 @@ } }, "node_modules/@babel/helpers": { - "version": "7.28.2", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.2.tgz", - "integrity": "sha512-/V9771t+EgXz62aCcyofnQhGM8DQACbRhvzKFsXKC9QM+5MadF8ZmIm0crDMaz3+o0h0zXfJnd4EhbYbxsrcFw==", + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.3.tgz", + "integrity": "sha512-PTNtvUQihsAsDHMOP5pfobP8C6CM4JWXmP8DrEIt46c3r2bf87Ua1zoqevsMo9g+tWDwgWrFP5EIxuBx5RudAw==", "dev": true, "license": "MIT", "dependencies": { @@ -275,13 +275,13 @@ } }, "node_modules/@babel/parser": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.0.tgz", - "integrity": "sha512-jVZGvOxOuNSsuQuLRTh13nU0AogFlw32w/MT+LV6D3sP5WdbW61E77RnkbaO2dUvmPAYrBDJXGn5gGS6tH4j8g==", + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.3.tgz", + "integrity": "sha512-7+Ey1mAgYqFAx2h0RuoxcQT5+MlG3GTV0TQrgr7/ZliKsm/MNDxVVutlWaziMq7wJNAz8MTqz55XLpWvva6StA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/types": "^7.28.0" + "@babel/types": "^7.28.2" }, "bin": { "parser": "bin/babel-parser.js" @@ -545,18 +545,18 @@ } }, "node_modules/@babel/traverse": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.0.tgz", - "integrity": "sha512-mGe7UK5wWyh0bKRfupsUchrQGqvDbZDbKJw+kcRGSmdHVYrv+ltd0pnpDTVpiTqnaBru9iEvA8pz8W46v0Amwg==", + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.3.tgz", + "integrity": "sha512-7w4kZYHneL3A6NP2nxzHvT3HCZ7puDZZjFMqDpBPECub79sTtSO5CGXDkKrTQq8ksAwfD/XI2MRFX23njdDaIQ==", "dev": true, "license": "MIT", "dependencies": { "@babel/code-frame": "^7.27.1", - "@babel/generator": "^7.28.0", + "@babel/generator": "^7.28.3", "@babel/helper-globals": "^7.28.0", - "@babel/parser": "^7.28.0", + "@babel/parser": "^7.28.3", "@babel/template": "^7.27.2", - "@babel/types": "^7.28.0", + "@babel/types": "^7.28.2", "debug": "^4.3.1" }, "engines": { @@ -621,10 +621,17 @@ "tslib": "^2.4.0" } }, + "node_modules/@epic-web/invariant": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@epic-web/invariant/-/invariant-1.0.0.tgz", + "integrity": "sha512-lrTPqgvfFQtR/eY/qkIzp98OGdNJu0m5ji3q/nJI8v3SXkRKEnWiOxMmbvcSoAIzv/cGiuvRy57k4suKQSAdwA==", + "dev": true, + "license": "MIT" + }, "node_modules/@esbuild/aix-ppc64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.8.tgz", - "integrity": "sha512-urAvrUedIqEiFR3FYSLTWQgLu5tb+m0qZw0NBEasUeo6wuqatkMDaRT+1uABiGXEu5vqgPd7FGE1BhsAIy9QVA==", + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.9.tgz", + "integrity": "sha512-OaGtL73Jck6pBKjNIe24BnFE6agGl+6KxDtTfHhy1HmhthfKouEcOhqpSL64K4/0WCtbKFLOdzD/44cJ4k9opA==", "cpu": [ "ppc64" ], @@ -639,9 +646,9 @@ } }, "node_modules/@esbuild/android-arm": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.8.tgz", - "integrity": "sha512-RONsAvGCz5oWyePVnLdZY/HHwA++nxYWIX1atInlaW6SEkwq6XkP3+cb825EUcRs5Vss/lGh/2YxAb5xqc07Uw==", + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.9.tgz", + "integrity": "sha512-5WNI1DaMtxQ7t7B6xa572XMXpHAaI/9Hnhk8lcxF4zVN4xstUgTlvuGDorBguKEnZO70qwEcLpfifMLoxiPqHQ==", "cpu": [ "arm" ], @@ -656,9 +663,9 @@ } }, "node_modules/@esbuild/android-arm64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.8.tgz", - "integrity": "sha512-OD3p7LYzWpLhZEyATcTSJ67qB5D+20vbtr6vHlHWSQYhKtzUYrETuWThmzFpZtFsBIxRvhO07+UgVA9m0i/O1w==", + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.9.tgz", + "integrity": "sha512-IDrddSmpSv51ftWslJMvl3Q2ZT98fUSL2/rlUXuVqRXHCs5EUF1/f+jbjF5+NG9UffUDMCiTyh8iec7u8RlTLg==", "cpu": [ "arm64" ], @@ -673,9 +680,9 @@ } }, "node_modules/@esbuild/android-x64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.8.tgz", - "integrity": "sha512-yJAVPklM5+4+9dTeKwHOaA+LQkmrKFX96BM0A/2zQrbS6ENCmxc4OVoBs5dPkCCak2roAD+jKCdnmOqKszPkjA==", + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.9.tgz", + "integrity": "sha512-I853iMZ1hWZdNllhVZKm34f4wErd4lMyeV7BLzEExGEIZYsOzqDWDf+y082izYUE8gtJnYHdeDpN/6tUdwvfiw==", "cpu": [ "x64" ], @@ -690,9 +697,9 @@ } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.8.tgz", - "integrity": "sha512-Jw0mxgIaYX6R8ODrdkLLPwBqHTtYHJSmzzd+QeytSugzQ0Vg4c5rDky5VgkoowbZQahCbsv1rT1KW72MPIkevw==", + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.9.tgz", + "integrity": "sha512-XIpIDMAjOELi/9PB30vEbVMs3GV1v2zkkPnuyRRURbhqjyzIINwj+nbQATh4H9GxUgH1kFsEyQMxwiLFKUS6Rg==", "cpu": [ "arm64" ], @@ -707,9 +714,9 @@ } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.8.tgz", - "integrity": "sha512-Vh2gLxxHnuoQ+GjPNvDSDRpoBCUzY4Pu0kBqMBDlK4fuWbKgGtmDIeEC081xi26PPjn+1tct+Bh8FjyLlw1Zlg==", + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.9.tgz", + "integrity": "sha512-jhHfBzjYTA1IQu8VyrjCX4ApJDnH+ez+IYVEoJHeqJm9VhG9Dh2BYaJritkYK3vMaXrf7Ogr/0MQ8/MeIefsPQ==", "cpu": [ "x64" ], @@ -724,9 +731,9 @@ } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.8.tgz", - "integrity": "sha512-YPJ7hDQ9DnNe5vxOm6jaie9QsTwcKedPvizTVlqWG9GBSq+BuyWEDazlGaDTC5NGU4QJd666V0yqCBL2oWKPfA==", + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.9.tgz", + "integrity": "sha512-z93DmbnY6fX9+KdD4Ue/H6sYs+bhFQJNCPZsi4XWJoYblUqT06MQUdBCpcSfuiN72AbqeBFu5LVQTjfXDE2A6Q==", "cpu": [ "arm64" ], @@ -741,9 +748,9 @@ } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.8.tgz", - "integrity": "sha512-MmaEXxQRdXNFsRN/KcIimLnSJrk2r5H8v+WVafRWz5xdSVmWLoITZQXcgehI2ZE6gioE6HirAEToM/RvFBeuhw==", + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.9.tgz", + "integrity": "sha512-mrKX6H/vOyo5v71YfXWJxLVxgy1kyt1MQaD8wZJgJfG4gq4DpQGpgTB74e5yBeQdyMTbgxp0YtNj7NuHN0PoZg==", "cpu": [ "x64" ], @@ -758,9 +765,9 @@ } }, "node_modules/@esbuild/linux-arm": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.8.tgz", - "integrity": "sha512-FuzEP9BixzZohl1kLf76KEVOsxtIBFwCaLupVuk4eFVnOZfU+Wsn+x5Ryam7nILV2pkq2TqQM9EZPsOBuMC+kg==", + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.9.tgz", + "integrity": "sha512-HBU2Xv78SMgaydBmdor38lg8YDnFKSARg1Q6AT0/y2ezUAKiZvc211RDFHlEZRFNRVhcMamiToo7bDx3VEOYQw==", "cpu": [ "arm" ], @@ -775,9 +782,9 @@ } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.8.tgz", - "integrity": "sha512-WIgg00ARWv/uYLU7lsuDK00d/hHSfES5BzdWAdAig1ioV5kaFNrtK8EqGcUBJhYqotlUByUKz5Qo6u8tt7iD/w==", + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.9.tgz", + "integrity": "sha512-BlB7bIcLT3G26urh5Dmse7fiLmLXnRlopw4s8DalgZ8ef79Jj4aUcYbk90g8iCa2467HX8SAIidbL7gsqXHdRw==", "cpu": [ "arm64" ], @@ -792,9 +799,9 @@ } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.8.tgz", - "integrity": "sha512-A1D9YzRX1i+1AJZuFFUMP1E9fMaYY+GnSQil9Tlw05utlE86EKTUA7RjwHDkEitmLYiFsRd9HwKBPEftNdBfjg==", + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.9.tgz", + "integrity": "sha512-e7S3MOJPZGp2QW6AK6+Ly81rC7oOSerQ+P8L0ta4FhVi+/j/v2yZzx5CqqDaWjtPFfYz21Vi1S0auHrap3Ma3A==", "cpu": [ "ia32" ], @@ -809,9 +816,9 @@ } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.8.tgz", - "integrity": "sha512-O7k1J/dwHkY1RMVvglFHl1HzutGEFFZ3kNiDMSOyUrB7WcoHGf96Sh+64nTRT26l3GMbCW01Ekh/ThKM5iI7hQ==", + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.9.tgz", + "integrity": "sha512-Sbe10Bnn0oUAB2AalYztvGcK+o6YFFA/9829PhOCUS9vkJElXGdphz0A3DbMdP8gmKkqPmPcMJmJOrI3VYB1JQ==", "cpu": [ "loong64" ], @@ -826,9 +833,9 @@ } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.8.tgz", - "integrity": "sha512-uv+dqfRazte3BzfMp8PAQXmdGHQt2oC/y2ovwpTteqrMx2lwaksiFZ/bdkXJC19ttTvNXBuWH53zy/aTj1FgGw==", + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.9.tgz", + "integrity": "sha512-YcM5br0mVyZw2jcQeLIkhWtKPeVfAerES5PvOzaDxVtIyZ2NUBZKNLjC5z3/fUlDgT6w89VsxP2qzNipOaaDyA==", "cpu": [ "mips64el" ], @@ -843,9 +850,9 @@ } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.8.tgz", - "integrity": "sha512-GyG0KcMi1GBavP5JgAkkstMGyMholMDybAf8wF5A70CALlDM2p/f7YFE7H92eDeH/VBtFJA5MT4nRPDGg4JuzQ==", + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.9.tgz", + "integrity": "sha512-++0HQvasdo20JytyDpFvQtNrEsAgNG2CY1CLMwGXfFTKGBGQT3bOeLSYE2l1fYdvML5KUuwn9Z8L1EWe2tzs1w==", "cpu": [ "ppc64" ], @@ -860,9 +867,9 @@ } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.8.tgz", - "integrity": "sha512-rAqDYFv3yzMrq7GIcen3XP7TUEG/4LK86LUPMIz6RT8A6pRIDn0sDcvjudVZBiiTcZCY9y2SgYX2lgK3AF+1eg==", + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.9.tgz", + "integrity": "sha512-uNIBa279Y3fkjV+2cUjx36xkx7eSjb8IvnL01eXUKXez/CBHNRw5ekCGMPM0BcmqBxBcdgUWuUXmVWwm4CH9kg==", "cpu": [ "riscv64" ], @@ -877,9 +884,9 @@ } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.8.tgz", - "integrity": "sha512-Xutvh6VjlbcHpsIIbwY8GVRbwoviWT19tFhgdA7DlenLGC/mbc3lBoVb7jxj9Z+eyGqvcnSyIltYUrkKzWqSvg==", + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.9.tgz", + "integrity": "sha512-Mfiphvp3MjC/lctb+7D287Xw1DGzqJPb/J2aHHcHxflUo+8tmN/6d4k6I2yFR7BVo5/g7x2Monq4+Yew0EHRIA==", "cpu": [ "s390x" ], @@ -894,9 +901,9 @@ } }, "node_modules/@esbuild/linux-x64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.8.tgz", - "integrity": "sha512-ASFQhgY4ElXh3nDcOMTkQero4b1lgubskNlhIfJrsH5OKZXDpUAKBlNS0Kx81jwOBp+HCeZqmoJuihTv57/jvQ==", + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.9.tgz", + "integrity": "sha512-iSwByxzRe48YVkmpbgoxVzn76BXjlYFXC7NvLYq+b+kDjyyk30J0JY47DIn8z1MO3K0oSl9fZoRmZPQI4Hklzg==", "cpu": [ "x64" ], @@ -911,9 +918,9 @@ } }, "node_modules/@esbuild/netbsd-arm64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.8.tgz", - "integrity": "sha512-d1KfruIeohqAi6SA+gENMuObDbEjn22olAR7egqnkCD9DGBG0wsEARotkLgXDu6c4ncgWTZJtN5vcgxzWRMzcw==", + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.9.tgz", + "integrity": "sha512-9jNJl6FqaUG+COdQMjSCGW4QiMHH88xWbvZ+kRVblZsWrkXlABuGdFJ1E9L7HK+T0Yqd4akKNa/lO0+jDxQD4Q==", "cpu": [ "arm64" ], @@ -928,9 +935,9 @@ } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.8.tgz", - "integrity": "sha512-nVDCkrvx2ua+XQNyfrujIG38+YGyuy2Ru9kKVNyh5jAys6n+l44tTtToqHjino2My8VAY6Lw9H7RI73XFi66Cg==", + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.9.tgz", + "integrity": "sha512-RLLdkflmqRG8KanPGOU7Rpg829ZHu8nFy5Pqdi9U01VYtG9Y0zOG6Vr2z4/S+/3zIyOxiK6cCeYNWOFR9QP87g==", "cpu": [ "x64" ], @@ -945,9 +952,9 @@ } }, "node_modules/@esbuild/openbsd-arm64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.8.tgz", - "integrity": "sha512-j8HgrDuSJFAujkivSMSfPQSAa5Fxbvk4rgNAS5i3K+r8s1X0p1uOO2Hl2xNsGFppOeHOLAVgYwDVlmxhq5h+SQ==", + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.9.tgz", + "integrity": "sha512-YaFBlPGeDasft5IIM+CQAhJAqS3St3nJzDEgsgFixcfZeyGPCd6eJBWzke5piZuZ7CtL656eOSYKk4Ls2C0FRQ==", "cpu": [ "arm64" ], @@ -962,9 +969,9 @@ } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.8.tgz", - "integrity": "sha512-1h8MUAwa0VhNCDp6Af0HToI2TJFAn1uqT9Al6DJVzdIBAd21m/G0Yfc77KDM3uF3T/YaOgQq3qTJHPbTOInaIQ==", + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.9.tgz", + "integrity": "sha512-1MkgTCuvMGWuqVtAvkpkXFmtL8XhWy+j4jaSO2wxfJtilVCi0ZE37b8uOdMItIHz4I6z1bWWtEX4CJwcKYLcuA==", "cpu": [ "x64" ], @@ -979,9 +986,9 @@ } }, "node_modules/@esbuild/openharmony-arm64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.8.tgz", - "integrity": "sha512-r2nVa5SIK9tSWd0kJd9HCffnDHKchTGikb//9c7HX+r+wHYCpQrSgxhlY6KWV1nFo1l4KFbsMlHk+L6fekLsUg==", + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.9.tgz", + "integrity": "sha512-4Xd0xNiMVXKh6Fa7HEJQbrpP3m3DDn43jKxMjxLLRjWnRsfxjORYJlXPO4JNcXtOyfajXorRKY9NkOpTHptErg==", "cpu": [ "arm64" ], @@ -996,9 +1003,9 @@ } }, "node_modules/@esbuild/sunos-x64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.8.tgz", - "integrity": "sha512-zUlaP2S12YhQ2UzUfcCuMDHQFJyKABkAjvO5YSndMiIkMimPmxA+BYSBikWgsRpvyxuRnow4nS5NPnf9fpv41w==", + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.9.tgz", + "integrity": "sha512-WjH4s6hzo00nNezhp3wFIAfmGZ8U7KtrJNlFMRKxiI9mxEK1scOMAaa9i4crUtu+tBr+0IN6JCuAcSBJZfnphw==", "cpu": [ "x64" ], @@ -1013,9 +1020,9 @@ } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.8.tgz", - "integrity": "sha512-YEGFFWESlPva8hGL+zvj2z/SaK+pH0SwOM0Nc/d+rVnW7GSTFlLBGzZkuSU9kFIGIo8q9X3ucpZhu8PDN5A2sQ==", + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.9.tgz", + "integrity": "sha512-mGFrVJHmZiRqmP8xFOc6b84/7xa5y5YvR1x8djzXpJBSv/UsNK6aqec+6JDjConTgvvQefdGhFDAs2DLAds6gQ==", "cpu": [ "arm64" ], @@ -1030,9 +1037,9 @@ } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.8.tgz", - "integrity": "sha512-hiGgGC6KZ5LZz58OL/+qVVoZiuZlUYlYHNAmczOm7bs2oE1XriPFi5ZHHrS8ACpV5EjySrnoCKmcbQMN+ojnHg==", + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.9.tgz", + "integrity": "sha512-b33gLVU2k11nVx1OhX3C8QQP6UHQK4ZtN56oFWvVXvz2VkDoe6fbG8TOgHFxEvqeqohmRnIHe5A1+HADk4OQww==", "cpu": [ "ia32" ], @@ -1047,9 +1054,9 @@ } }, "node_modules/@esbuild/win32-x64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.8.tgz", - "integrity": "sha512-cn3Yr7+OaaZq1c+2pe+8yxC8E144SReCQjN6/2ynubzYjvyqZjTXfQJpAcQpsdJq3My7XADANiYGHoFC69pLQw==", + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.9.tgz", + "integrity": "sha512-PPOl1mi6lpLNQxnGoyAfschAodRFYXJ+9fs6WHXz7CSWKbOqiMZsubC+BQsVKuul+3vKLuwTHsS2c2y9EoKwxQ==", "cpu": [ "x64" ], @@ -1121,9 +1128,9 @@ } }, "node_modules/@eslint/config-helpers": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.3.0.tgz", - "integrity": "sha512-ViuymvFmcJi04qdZeDc2whTHryouGcDlaxPqarTD0ZE10ISpxGUVZGZDx4w01upyIynL3iu6IXH2bS1NhclQMw==", + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.3.1.tgz", + "integrity": "sha512-xR93k9WhrDYpXHORXpxVL5oHj3Era7wo6k/Wd8/IsQNnZUTzkGS29lyn3nAT05v6ltUuTFVCCYDEGfy2Or/sPA==", "dev": true, "license": "Apache-2.0", "engines": { @@ -1131,9 +1138,9 @@ } }, "node_modules/@eslint/core": { - "version": "0.15.1", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.15.1.tgz", - "integrity": "sha512-bkOp+iumZCCbt1K1CmWf0R9pM5yKpDv+ZXtvSyQpudrI9kuFLp+bM2WOPXImuD/ceQuaa8f5pj93Y7zyECIGNA==", + "version": "0.15.2", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.15.2.tgz", + "integrity": "sha512-78Md3/Rrxh83gCxoUc0EiciuOHsIITzLy53m3d9UyiW8y9Dj2D29FeETqyKA+BRK76tnTp6RXWb3pCay8Oyomg==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -1168,9 +1175,9 @@ } }, "node_modules/@eslint/js": { - "version": "9.32.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.32.0.tgz", - "integrity": "sha512-BBpRFZK3eX6uMLKz8WxFOBIFFcGFJ/g8XuwjTHCqHROSIsopI+ddn/d5Cfh36+7+e5edVS8dbSHnBNhrLEX0zg==", + "version": "9.33.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.33.0.tgz", + "integrity": "sha512-5K1/mKhWaMfreBGJTwval43JJmkip0RmM+3+IuqupeSKNC/Th2Kc7ucaq5ovTSra/OOKB9c58CGSz3QMVbWt0A==", "dev": true, "license": "MIT", "engines": { @@ -1191,13 +1198,13 @@ } }, "node_modules/@eslint/plugin-kit": { - "version": "0.3.4", - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.3.4.tgz", - "integrity": "sha512-Ul5l+lHEcw3L5+k8POx6r74mxEYKG5kOb6Xpy2gCRW6zweT6TEhAf8vhxGgjhqrd/VO/Dirhsb+1hNpD1ue9hw==", + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.3.5.tgz", + "integrity": "sha512-Z5kJ+wU3oA7MMIqVR9tyZRtjYPr4OC004Q4Rw7pgOKUOKkJfZ3O24nz3WYfGRpMDNmcOi3TwQOmgm7B7Tpii0w==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@eslint/core": "^0.15.1", + "@eslint/core": "^0.15.2", "levn": "^0.4.1" }, "engines": { @@ -1457,61 +1464,61 @@ } }, "node_modules/@jest/console": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.7.0.tgz", - "integrity": "sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg==", + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-30.0.5.tgz", + "integrity": "sha512-xY6b0XiL0Nav3ReresUarwl2oIz1gTnxGbGpho9/rbUWsLH0f1OD/VT84xs8c7VmH7MChnLb0pag6PhZhAdDiA==", "dev": true, "license": "MIT", "dependencies": { - "@jest/types": "^29.6.3", + "@jest/types": "30.0.5", "@types/node": "*", - "chalk": "^4.0.0", - "jest-message-util": "^29.7.0", - "jest-util": "^29.7.0", + "chalk": "^4.1.2", + "jest-message-util": "30.0.5", + "jest-util": "30.0.5", "slash": "^3.0.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/@jest/core": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.7.0.tgz", - "integrity": "sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg==", + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-30.0.5.tgz", + "integrity": "sha512-fKD0OulvRsXF1hmaFgHhVJzczWzA1RXMMo9LTPuFXo9q/alDbME3JIyWYqovWsUBWSoBcsHaGPSLF9rz4l9Qeg==", "dev": true, "license": "MIT", "dependencies": { - "@jest/console": "^29.7.0", - "@jest/reporters": "^29.7.0", - "@jest/test-result": "^29.7.0", - "@jest/transform": "^29.7.0", - "@jest/types": "^29.6.3", + "@jest/console": "30.0.5", + "@jest/pattern": "30.0.1", + "@jest/reporters": "30.0.5", + "@jest/test-result": "30.0.5", + "@jest/transform": "30.0.5", + "@jest/types": "30.0.5", "@types/node": "*", - "ansi-escapes": "^4.2.1", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "exit": "^0.1.2", - "graceful-fs": "^4.2.9", - "jest-changed-files": "^29.7.0", - "jest-config": "^29.7.0", - "jest-haste-map": "^29.7.0", - "jest-message-util": "^29.7.0", - "jest-regex-util": "^29.6.3", - "jest-resolve": "^29.7.0", - "jest-resolve-dependencies": "^29.7.0", - "jest-runner": "^29.7.0", - "jest-runtime": "^29.7.0", - "jest-snapshot": "^29.7.0", - "jest-util": "^29.7.0", - "jest-validate": "^29.7.0", - "jest-watcher": "^29.7.0", - "micromatch": "^4.0.4", - "pretty-format": "^29.7.0", - "slash": "^3.0.0", - "strip-ansi": "^6.0.0" + "ansi-escapes": "^4.3.2", + "chalk": "^4.1.2", + "ci-info": "^4.2.0", + "exit-x": "^0.2.2", + "graceful-fs": "^4.2.11", + "jest-changed-files": "30.0.5", + "jest-config": "30.0.5", + "jest-haste-map": "30.0.5", + "jest-message-util": "30.0.5", + "jest-regex-util": "30.0.1", + "jest-resolve": "30.0.5", + "jest-resolve-dependencies": "30.0.5", + "jest-runner": "30.0.5", + "jest-runtime": "30.0.5", + "jest-snapshot": "30.0.5", + "jest-util": "30.0.5", + "jest-validate": "30.0.5", + "jest-watcher": "30.0.5", + "micromatch": "^4.0.8", + "pretty-format": "30.0.5", + "slash": "^3.0.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" }, "peerDependencies": { "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" @@ -1522,150 +1529,160 @@ } } }, - "node_modules/@jest/core/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/core/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "node_modules/@jest/diff-sequences": { + "version": "30.0.1", + "resolved": "https://registry.npmjs.org/@jest/diff-sequences/-/diff-sequences-30.0.1.tgz", + "integrity": "sha512-n5H8QLDJ47QqbCNn5SuFjCRDrOLEZ0h8vAHCK5RL9Ls7Xa8AQLa/YxAc9UjFqoEDM48muwtBGjtMY5cr0PLDCw==", "dev": true, "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, "engines": { - "node": ">=8" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/@jest/environment": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.7.0.tgz", - "integrity": "sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==", + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-30.0.5.tgz", + "integrity": "sha512-aRX7WoaWx1oaOkDQvCWImVQ8XNtdv5sEWgk4gxR6NXb7WBUnL5sRak4WRzIQRZ1VTWPvV4VI4mgGjNL9TeKMYA==", "dev": true, "license": "MIT", "dependencies": { - "@jest/fake-timers": "^29.7.0", - "@jest/types": "^29.6.3", + "@jest/fake-timers": "30.0.5", + "@jest/types": "30.0.5", "@types/node": "*", - "jest-mock": "^29.7.0" + "jest-mock": "30.0.5" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/@jest/expect": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-29.7.0.tgz", - "integrity": "sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ==", + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-30.0.5.tgz", + "integrity": "sha512-6udac8KKrtTtC+AXZ2iUN/R7dp7Ydry+Fo6FPFnDG54wjVMnb6vW/XNlf7Xj8UDjAE3aAVAsR4KFyKk3TCXmTA==", "dev": true, "license": "MIT", "dependencies": { - "expect": "^29.7.0", - "jest-snapshot": "^29.7.0" + "expect": "30.0.5", + "jest-snapshot": "30.0.5" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/@jest/expect-utils": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.7.0.tgz", - "integrity": "sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==", + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-30.0.5.tgz", + "integrity": "sha512-F3lmTT7CXWYywoVUGTCmom0vXq3HTTkaZyTAzIy+bXSBizB7o5qzlC9VCtq0arOa8GqmNsbg/cE9C6HLn7Szew==", "dev": true, "license": "MIT", "dependencies": { - "jest-get-type": "^29.6.3" + "@jest/get-type": "30.0.1" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/@jest/fake-timers": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.7.0.tgz", - "integrity": "sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==", + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-30.0.5.tgz", + "integrity": "sha512-ZO5DHfNV+kgEAeP3gK3XlpJLL4U3Sz6ebl/n68Uwt64qFFs5bv4bfEEjyRGK5uM0C90ewooNgFuKMdkbEoMEXw==", "dev": true, "license": "MIT", "dependencies": { - "@jest/types": "^29.6.3", - "@sinonjs/fake-timers": "^10.0.2", + "@jest/types": "30.0.5", + "@sinonjs/fake-timers": "^13.0.0", "@types/node": "*", - "jest-message-util": "^29.7.0", - "jest-mock": "^29.7.0", - "jest-util": "^29.7.0" + "jest-message-util": "30.0.5", + "jest-mock": "30.0.5", + "jest-util": "30.0.5" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/@jest/fake-timers/node_modules/@sinonjs/fake-timers": { - "version": "10.3.0", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz", - "integrity": "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==", + "version": "13.0.5", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-13.0.5.tgz", + "integrity": "sha512-36/hTbH2uaWuGVERyC6da9YwGWnzUZXuPro/F2LfsdOsLnCojz/iSH8MxUt/FD2S5XBSVPhmArFUXcpCQ2Hkiw==", "dev": true, "license": "BSD-3-Clause", "dependencies": { - "@sinonjs/commons": "^3.0.0" + "@sinonjs/commons": "^3.0.1" + } + }, + "node_modules/@jest/get-type": { + "version": "30.0.1", + "resolved": "https://registry.npmjs.org/@jest/get-type/-/get-type-30.0.1.tgz", + "integrity": "sha512-AyYdemXCptSRFirI5EPazNxyPwAL0jXt3zceFjaj8NFiKP9pOi0bfXonf6qkf82z2t3QWPeLCWWw4stPBzctLw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/@jest/globals": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.7.0.tgz", - "integrity": "sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ==", + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-30.0.5.tgz", + "integrity": "sha512-7oEJT19WW4oe6HR7oLRvHxwlJk2gev0U9px3ufs8sX9PoD1Eza68KF0/tlN7X0dq/WVsBScXQGgCldA1V9Y/jA==", "dev": true, "license": "MIT", "dependencies": { - "@jest/environment": "^29.7.0", - "@jest/expect": "^29.7.0", - "@jest/types": "^29.6.3", - "jest-mock": "^29.7.0" + "@jest/environment": "30.0.5", + "@jest/expect": "30.0.5", + "@jest/types": "30.0.5", + "jest-mock": "30.0.5" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/pattern": { + "version": "30.0.1", + "resolved": "https://registry.npmjs.org/@jest/pattern/-/pattern-30.0.1.tgz", + "integrity": "sha512-gWp7NfQW27LaBQz3TITS8L7ZCQ0TLvtmI//4OwlQRx4rnWxcPNIYjxZpDcN4+UlGxgm3jS5QPz8IPTCkb59wZA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "jest-regex-util": "30.0.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/@jest/reporters": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.7.0.tgz", - "integrity": "sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg==", + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-30.0.5.tgz", + "integrity": "sha512-mafft7VBX4jzED1FwGC1o/9QUM2xebzavImZMeqnsklgcyxBto8mV4HzNSzUrryJ+8R9MFOM3HgYuDradWR+4g==", "dev": true, "license": "MIT", "dependencies": { "@bcoe/v8-coverage": "^0.2.3", - "@jest/console": "^29.7.0", - "@jest/test-result": "^29.7.0", - "@jest/transform": "^29.7.0", - "@jest/types": "^29.6.3", - "@jridgewell/trace-mapping": "^0.3.18", + "@jest/console": "30.0.5", + "@jest/test-result": "30.0.5", + "@jest/transform": "30.0.5", + "@jest/types": "30.0.5", + "@jridgewell/trace-mapping": "^0.3.25", "@types/node": "*", - "chalk": "^4.0.0", - "collect-v8-coverage": "^1.0.0", - "exit": "^0.1.2", - "glob": "^7.1.3", - "graceful-fs": "^4.2.9", + "chalk": "^4.1.2", + "collect-v8-coverage": "^1.0.2", + "exit-x": "^0.2.2", + "glob": "^10.3.10", + "graceful-fs": "^4.2.11", "istanbul-lib-coverage": "^3.0.0", "istanbul-lib-instrument": "^6.0.0", "istanbul-lib-report": "^3.0.0", - "istanbul-lib-source-maps": "^4.0.0", + "istanbul-lib-source-maps": "^5.0.0", "istanbul-reports": "^3.1.3", - "jest-message-util": "^29.7.0", - "jest-util": "^29.7.0", - "jest-worker": "^29.7.0", + "jest-message-util": "30.0.5", + "jest-util": "30.0.5", + "jest-worker": "30.0.5", "slash": "^3.0.0", - "string-length": "^4.0.1", - "strip-ansi": "^6.0.0", + "string-length": "^4.0.2", "v8-to-istanbul": "^9.0.1" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" }, "peerDependencies": { "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" @@ -1683,160 +1700,132 @@ "dev": true, "license": "MIT" }, - "node_modules/@jest/reporters/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/reporters/node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "deprecated": "Glob versions prior to v9 are no longer supported", - "dev": true, - "license": "ISC", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/@jest/reporters/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "node_modules/@jest/schemas": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz", + "integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==", "dev": true, "license": "MIT", "dependencies": { - "ansi-regex": "^5.0.1" + "@sinclair/typebox": "^0.34.0" }, "engines": { - "node": ">=8" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/@jest/schemas": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", - "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", + "node_modules/@jest/snapshot-utils": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/snapshot-utils/-/snapshot-utils-30.0.5.tgz", + "integrity": "sha512-XcCQ5qWHLvi29UUrowgDFvV4t7ETxX91CbDczMnoqXPOIcZOxyNdSjm6kV5XMc8+HkxfRegU/MUmnTbJRzGrUQ==", "dev": true, "license": "MIT", "dependencies": { - "@sinclair/typebox": "^0.27.8" + "@jest/types": "30.0.5", + "chalk": "^4.1.2", + "graceful-fs": "^4.2.11", + "natural-compare": "^1.4.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/@jest/source-map": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-29.6.3.tgz", - "integrity": "sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw==", + "version": "30.0.1", + "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-30.0.1.tgz", + "integrity": "sha512-MIRWMUUR3sdbP36oyNyhbThLHyJ2eEDClPCiHVbrYAe5g3CHRArIVpBw7cdSB5fr+ofSfIb2Tnsw8iEHL0PYQg==", "dev": true, "license": "MIT", "dependencies": { - "@jridgewell/trace-mapping": "^0.3.18", - "callsites": "^3.0.0", - "graceful-fs": "^4.2.9" + "@jridgewell/trace-mapping": "^0.3.25", + "callsites": "^3.1.0", + "graceful-fs": "^4.2.11" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/@jest/test-result": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-29.7.0.tgz", - "integrity": "sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA==", + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-30.0.5.tgz", + "integrity": "sha512-wPyztnK0gbDMQAJZ43tdMro+qblDHH1Ru/ylzUo21TBKqt88ZqnKKK2m30LKmLLoKtR2lxdpCC/P3g1vfKcawQ==", "dev": true, "license": "MIT", "dependencies": { - "@jest/console": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "collect-v8-coverage": "^1.0.0" + "@jest/console": "30.0.5", + "@jest/types": "30.0.5", + "@types/istanbul-lib-coverage": "^2.0.6", + "collect-v8-coverage": "^1.0.2" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/@jest/test-sequencer": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.7.0.tgz", - "integrity": "sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw==", + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-30.0.5.tgz", + "integrity": "sha512-Aea/G1egWoIIozmDD7PBXUOxkekXl7ueGzrsGGi1SbeKgQqCYCIf+wfbflEbf2LiPxL8j2JZGLyrzZagjvW4YQ==", "dev": true, "license": "MIT", "dependencies": { - "@jest/test-result": "^29.7.0", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.7.0", + "@jest/test-result": "30.0.5", + "graceful-fs": "^4.2.11", + "jest-haste-map": "30.0.5", "slash": "^3.0.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/@jest/transform": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz", - "integrity": "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==", + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-30.0.5.tgz", + "integrity": "sha512-Vk8amLQCmuZyy6GbBht1Jfo9RSdBtg7Lks+B0PecnjI8J+PCLQPGh7uI8Q/2wwpW2gLdiAfiHNsmekKlywULqg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/core": "^7.11.6", - "@jest/types": "^29.6.3", - "@jridgewell/trace-mapping": "^0.3.18", - "babel-plugin-istanbul": "^6.1.1", - "chalk": "^4.0.0", + "@babel/core": "^7.27.4", + "@jest/types": "30.0.5", + "@jridgewell/trace-mapping": "^0.3.25", + "babel-plugin-istanbul": "^7.0.0", + "chalk": "^4.1.2", "convert-source-map": "^2.0.0", "fast-json-stable-stringify": "^2.1.0", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.7.0", - "jest-regex-util": "^29.6.3", - "jest-util": "^29.7.0", - "micromatch": "^4.0.4", - "pirates": "^4.0.4", + "graceful-fs": "^4.2.11", + "jest-haste-map": "30.0.5", + "jest-regex-util": "30.0.1", + "jest-util": "30.0.5", + "micromatch": "^4.0.8", + "pirates": "^4.0.7", "slash": "^3.0.0", - "write-file-atomic": "^4.0.2" + "write-file-atomic": "^5.0.1" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/@jest/types": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", - "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.0.5.tgz", + "integrity": "sha512-aREYa3aku9SSnea4aX6bhKn4bgv3AXkgijoQgbYV3yvbiGt6z+MQ85+6mIhx9DsKW2BuB/cLR/A+tcMThx+KLQ==", "dev": true, "license": "MIT", "dependencies": { - "@jest/schemas": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", + "@jest/pattern": "30.0.1", + "@jest/schemas": "30.0.5", + "@types/istanbul-lib-coverage": "^2.0.6", + "@types/istanbul-reports": "^3.0.4", "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" + "@types/yargs": "^17.0.33", + "chalk": "^4.1.2" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.12", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.12.tgz", - "integrity": "sha512-OuLGC46TjB5BbN1dH8JULVVZY4WTdkF7tV9Ys6wLL1rubZnCMstOhNHueU5bLCrnRuDhKPDM4g6sw4Bel5Gzqg==", + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", "dev": true, "license": "MIT", "dependencies": { @@ -1855,16 +1844,16 @@ } }, "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.4", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.4.tgz", - "integrity": "sha512-VT2+G1VQs/9oz078bLrYbecdZKs912zQlkelYpuf+SXF+QvZDYJlbx/LSx+meSAwdDFnF8FVXW92AVjjkVmgFw==", + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", "dev": true, "license": "MIT" }, "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.29", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.29.tgz", - "integrity": "sha512-uw6guiW/gcAGPDhLmd77/6lW8QLeiV5RUTsAX46Db6oLhGaVj4lhnPwb184s1bkc8kdVg/+h988dro8GRDpmYQ==", + "version": "0.3.30", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.30.tgz", + "integrity": "sha512-GQ7Nw5G2lTu/BtHTKfXhKHok2WGetd4XYcVKGx00SjAk8GMwgJM3zr6zORiPGuOE+/vkc90KtTosSSvaCjKb2Q==", "dev": true, "license": "MIT", "dependencies": { @@ -1886,6 +1875,20 @@ "dev": true, "license": "MIT" }, + "node_modules/@metcoder95/https-pem": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@metcoder95/https-pem/-/https-pem-1.0.0.tgz", + "integrity": "sha512-v+nv59Y6SqdtbXBmAlVx++Vrc2DkBt4F7AVOmbNMB+pjD8QLCoReRdsTCydJC/wY4iZX8O4Cm0F1DhgDgwhIsg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "selfsigned": "^3.0.1" + }, + "engines": { + "node": ">=20" + } + }, "node_modules/@napi-rs/wasm-runtime": { "version": "0.2.12", "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.12.tgz", @@ -1958,10 +1961,23 @@ "node": ">=14" } }, + "node_modules/@pkgr/core": { + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.2.9.tgz", + "integrity": "sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/pkgr" + } + }, "node_modules/@reporters/github": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/@reporters/github/-/github-1.8.0.tgz", - "integrity": "sha512-EJNbv7qvqbICrVbyaPLKWT/mGzdkkdskKuPg1hG0tVKeAEtH6D1gCZwZ84N/26CQ8FBsyfiUyVjwtgYEByGKWQ==", + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/@reporters/github/-/github-1.9.1.tgz", + "integrity": "sha512-MIuO3Ci0vCKV5CkueQOieiYKPqJvHkHJxYsk+raotCHcB4yPU4ns6yfy3tcXDsUFCkj+vbB+XXucdac139J6uA==", "dev": true, "license": "MIT", "dependencies": { @@ -1977,9 +1993,9 @@ "license": "MIT" }, "node_modules/@sinclair/typebox": { - "version": "0.27.8", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", - "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", + "version": "0.34.40", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.40.tgz", + "integrity": "sha512-gwBNIP8ZAYev/ORDWW0QvxdwPXwxBtLsdsJgSc7eDIRt8ubP+rxUBzPsrwnu16fgEF8Bx4lh/+mvQvJzcTM6Kw==", "dev": true, "license": "MIT" }, @@ -2050,9 +2066,9 @@ } }, "node_modules/@tsd/typescript": { - "version": "5.8.3", - "resolved": "https://registry.npmjs.org/@tsd/typescript/-/typescript-5.8.3.tgz", - "integrity": "sha512-oKarNCN1QUhG148M88mtZdOlBZWWGcInquef+U8QL7gwJkRuNo5WS45Fjsd+3hM9cDJWGpqSZ4Oo097KDx4IWA==", + "version": "5.9.2", + "resolved": "https://registry.npmjs.org/@tsd/typescript/-/typescript-5.9.2.tgz", + "integrity": "sha512-mSMM0QtEPdMd+rdMDd17yCUYD4yI3pKHap89+jEZrZ3KIO5PhDofBjER0OtgHdvOXF74KMLO3fyD6k3Hz0v03A==", "dev": true, "license": "MIT", "engines": { @@ -2133,16 +2149,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@types/graceful-fs": { - "version": "4.1.9", - "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.9.tgz", - "integrity": "sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, "node_modules/@types/istanbul-lib-coverage": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", @@ -2185,25 +2191,15 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "18.19.121", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.121.tgz", - "integrity": "sha512-bHOrbyztmyYIi4f1R0s17QsPs1uyyYnGcXeZoGEd227oZjry0q6XQBQxd82X1I57zEfwO8h9Xo+Kl5gX1d9MwQ==", + "version": "18.19.123", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.123.tgz", + "integrity": "sha512-K7DIaHnh0mzVxreCR9qwgNxp3MH9dltPNIEddW9MYUlcKAzm+3grKNSTe2vCJHI1FaLpvpL5JGJrz1UZDKYvDg==", "dev": true, "license": "MIT", "dependencies": { "undici-types": "~5.26.4" } }, - "node_modules/@types/node-forge": { - "version": "1.3.13", - "resolved": "https://registry.npmjs.org/@types/node-forge/-/node-forge-1.3.13.tgz", - "integrity": "sha512-zePQJSW5QkwSHKRApqWCVKeKoSOt4xvEnLENZPjyvm9Ezdf/EyDeJM7jqLzOwjVICQQzvLZ63T55MKdJB5H6ww==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, "node_modules/@types/normalize-package-data": { "version": "2.4.4", "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.4.tgz", @@ -2236,17 +2232,17 @@ "license": "MIT" }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.38.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.38.0.tgz", - "integrity": "sha512-CPoznzpuAnIOl4nhj4tRr4gIPj5AfKgkiJmGQDaq+fQnRJTYlcBjbX3wbciGmpoPf8DREufuPRe1tNMZnGdanA==", + "version": "8.39.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.39.1.tgz", + "integrity": "sha512-yYegZ5n3Yr6eOcqgj2nJH8cH/ZZgF+l0YIdKILSDjYFRjgYQMgv/lRjV5Z7Up04b9VYUondt8EPMqg7kTWgJ2g==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.38.0", - "@typescript-eslint/type-utils": "8.38.0", - "@typescript-eslint/utils": "8.38.0", - "@typescript-eslint/visitor-keys": "8.38.0", + "@typescript-eslint/scope-manager": "8.39.1", + "@typescript-eslint/type-utils": "8.39.1", + "@typescript-eslint/utils": "8.39.1", + "@typescript-eslint/visitor-keys": "8.39.1", "graphemer": "^1.4.0", "ignore": "^7.0.0", "natural-compare": "^1.4.0", @@ -2260,9 +2256,9 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/parser": "^8.38.0", + "@typescript-eslint/parser": "^8.39.1", "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <5.9.0" + "typescript": ">=4.8.4 <6.0.0" } }, "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { @@ -2276,16 +2272,16 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "8.38.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.38.0.tgz", - "integrity": "sha512-Zhy8HCvBUEfBECzIl1PKqF4p11+d0aUJS1GeUiuqK9WmOug8YCmC4h4bjyBvMyAMI9sbRczmrYL5lKg/YMbrcQ==", + "version": "8.39.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.39.1.tgz", + "integrity": "sha512-pUXGCuHnnKw6PyYq93lLRiZm3vjuslIy7tus1lIQTYVK9bL8XBgJnCWm8a0KcTtHC84Yya1Q6rtll+duSMj0dg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/scope-manager": "8.38.0", - "@typescript-eslint/types": "8.38.0", - "@typescript-eslint/typescript-estree": "8.38.0", - "@typescript-eslint/visitor-keys": "8.38.0", + "@typescript-eslint/scope-manager": "8.39.1", + "@typescript-eslint/types": "8.39.1", + "@typescript-eslint/typescript-estree": "8.39.1", + "@typescript-eslint/visitor-keys": "8.39.1", "debug": "^4.3.4" }, "engines": { @@ -2297,18 +2293,18 @@ }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <5.9.0" + "typescript": ">=4.8.4 <6.0.0" } }, "node_modules/@typescript-eslint/project-service": { - "version": "8.38.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.38.0.tgz", - "integrity": "sha512-dbK7Jvqcb8c9QfH01YB6pORpqX1mn5gDZc9n63Ak/+jD67oWXn3Gs0M6vddAN+eDXBCS5EmNWzbSxsn9SzFWWg==", + "version": "8.39.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.39.1.tgz", + "integrity": "sha512-8fZxek3ONTwBu9ptw5nCKqZOSkXshZB7uAxuFF0J/wTMkKydjXCzqqga7MlFMpHi9DoG4BadhmTkITBcg8Aybw==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.38.0", - "@typescript-eslint/types": "^8.38.0", + "@typescript-eslint/tsconfig-utils": "^8.39.1", + "@typescript-eslint/types": "^8.39.1", "debug": "^4.3.4" }, "engines": { @@ -2319,18 +2315,18 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "typescript": ">=4.8.4 <5.9.0" + "typescript": ">=4.8.4 <6.0.0" } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.38.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.38.0.tgz", - "integrity": "sha512-WJw3AVlFFcdT9Ri1xs/lg8LwDqgekWXWhH3iAF+1ZM+QPd7oxQ6jvtW/JPwzAScxitILUIFs0/AnQ/UWHzbATQ==", + "version": "8.39.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.39.1.tgz", + "integrity": "sha512-RkBKGBrjgskFGWuyUGz/EtD8AF/GW49S21J8dvMzpJitOF1slLEbbHnNEtAHtnDAnx8qDEdRrULRnWVx27wGBw==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.38.0", - "@typescript-eslint/visitor-keys": "8.38.0" + "@typescript-eslint/types": "8.39.1", + "@typescript-eslint/visitor-keys": "8.39.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2341,9 +2337,9 @@ } }, "node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.38.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.38.0.tgz", - "integrity": "sha512-Lum9RtSE3EroKk/bYns+sPOodqb2Fv50XOl/gMviMKNvanETUuUcC9ObRbzrJ4VSd2JalPqgSAavwrPiPvnAiQ==", + "version": "8.39.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.39.1.tgz", + "integrity": "sha512-ePUPGVtTMR8XMU2Hee8kD0Pu4NDE1CN9Q1sxGSGd/mbOtGZDM7pnhXNJnzW63zk/q+Z54zVzj44HtwXln5CvHA==", "dev": true, "license": "MIT", "engines": { @@ -2354,19 +2350,19 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "typescript": ">=4.8.4 <5.9.0" + "typescript": ">=4.8.4 <6.0.0" } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.38.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.38.0.tgz", - "integrity": "sha512-c7jAvGEZVf0ao2z+nnz8BUaHZD09Agbh+DY7qvBQqLiz8uJzRgVPj5YvOh8I8uEiH8oIUGIfHzMwUcGVco/SJg==", + "version": "8.39.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.39.1.tgz", + "integrity": "sha512-gu9/ahyatyAdQbKeHnhT4R+y3YLtqqHyvkfDxaBYk97EcbfChSJXyaJnIL3ygUv7OuZatePHmQvuH5ru0lnVeA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.38.0", - "@typescript-eslint/typescript-estree": "8.38.0", - "@typescript-eslint/utils": "8.38.0", + "@typescript-eslint/types": "8.39.1", + "@typescript-eslint/typescript-estree": "8.39.1", + "@typescript-eslint/utils": "8.39.1", "debug": "^4.3.4", "ts-api-utils": "^2.1.0" }, @@ -2379,13 +2375,13 @@ }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <5.9.0" + "typescript": ">=4.8.4 <6.0.0" } }, "node_modules/@typescript-eslint/types": { - "version": "8.38.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.38.0.tgz", - "integrity": "sha512-wzkUfX3plUqij4YwWaJyqhiPE5UCRVlFpKn1oCRn2O1bJ592XxWJj8ROQ3JD5MYXLORW84063z3tZTb/cs4Tyw==", + "version": "8.39.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.39.1.tgz", + "integrity": "sha512-7sPDKQQp+S11laqTrhHqeAbsCfMkwJMrV7oTDvtDds4mEofJYir414bYKUEb8YPUm9QL3U+8f6L6YExSoAGdQw==", "dev": true, "license": "MIT", "engines": { @@ -2397,16 +2393,16 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.38.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.38.0.tgz", - "integrity": "sha512-fooELKcAKzxux6fA6pxOflpNS0jc+nOQEEOipXFNjSlBS6fqrJOVY/whSn70SScHrcJ2LDsxWrneFoWYSVfqhQ==", + "version": "8.39.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.39.1.tgz", + "integrity": "sha512-EKkpcPuIux48dddVDXyQBlKdeTPMmALqBUbEk38McWv0qVEZwOpVJBi7ugK5qVNgeuYjGNQxrrnoM/5+TI/BPw==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/project-service": "8.38.0", - "@typescript-eslint/tsconfig-utils": "8.38.0", - "@typescript-eslint/types": "8.38.0", - "@typescript-eslint/visitor-keys": "8.38.0", + "@typescript-eslint/project-service": "8.39.1", + "@typescript-eslint/tsconfig-utils": "8.39.1", + "@typescript-eslint/types": "8.39.1", + "@typescript-eslint/visitor-keys": "8.39.1", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", @@ -2422,7 +2418,7 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "typescript": ">=4.8.4 <5.9.0" + "typescript": ">=4.8.4 <6.0.0" } }, "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { @@ -2465,16 +2461,16 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "8.38.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.38.0.tgz", - "integrity": "sha512-hHcMA86Hgt+ijJlrD8fX0j1j8w4C92zue/8LOPAFioIno+W0+L7KqE8QZKCcPGc/92Vs9x36w/4MPTJhqXdyvg==", + "version": "8.39.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.39.1.tgz", + "integrity": "sha512-VF5tZ2XnUSTuiqZFXCZfZs1cgkdd3O/sSYmdo2EpSyDlC86UM/8YytTmKnehOW3TGAlivqTDT6bS87B/GQ/jyg==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.7.0", - "@typescript-eslint/scope-manager": "8.38.0", - "@typescript-eslint/types": "8.38.0", - "@typescript-eslint/typescript-estree": "8.38.0" + "@typescript-eslint/scope-manager": "8.39.1", + "@typescript-eslint/types": "8.39.1", + "@typescript-eslint/typescript-estree": "8.39.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2485,17 +2481,17 @@ }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <5.9.0" + "typescript": ">=4.8.4 <6.0.0" } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.38.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.38.0.tgz", - "integrity": "sha512-pWrTcoFNWuwHlA9CvlfSsGWs14JxfN1TH25zM5L7o0pRLhsoZkDnTsXfQRJBEWJoV5DL0jf+Z+sxiud+K0mq1g==", + "version": "8.39.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.39.1.tgz", + "integrity": "sha512-W8FQi6kEh2e8zVhQ0eeRnxdvIoOkAp/CPAahcNio6nO9dsIwb9b34z90KOlheoyuVf6LSOEdjlkxSkapNEc+4A==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.38.0", + "@typescript-eslint/types": "8.39.1", "eslint-visitor-keys": "^4.2.1" }, "engines": { @@ -2506,6 +2502,13 @@ "url": "https://opencollective.com/typescript-eslint" } }, + "node_modules/@ungap/structured-clone": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", + "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", + "dev": true, + "license": "ISC" + }, "node_modules/@unrs/resolver-binding-android-arm-eabi": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm-eabi/-/resolver-binding-android-arm-eabi-1.11.1.tgz", @@ -2845,9 +2848,9 @@ } }, "node_modules/ansi-regex": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", - "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.0.tgz", + "integrity": "sha512-TKY5pyBkHyADOPYlRT9Lx6F544mPl0vS5Ew7BJ45hA08Q+t3GjbueLliBWN3sMICk6+y7HdyxSzC4bWS8baBdg==", "dev": true, "license": "MIT", "engines": { @@ -3193,42 +3196,42 @@ } }, "node_modules/babel-jest": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz", - "integrity": "sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==", + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-30.0.5.tgz", + "integrity": "sha512-mRijnKimhGDMsizTvBTWotwNpzrkHr+VvZUQBof2AufXKB8NXrL1W69TG20EvOz7aevx6FTJIaBuBkYxS8zolg==", "dev": true, "license": "MIT", "dependencies": { - "@jest/transform": "^29.7.0", - "@types/babel__core": "^7.1.14", - "babel-plugin-istanbul": "^6.1.1", - "babel-preset-jest": "^29.6.3", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", + "@jest/transform": "30.0.5", + "@types/babel__core": "^7.20.5", + "babel-plugin-istanbul": "^7.0.0", + "babel-preset-jest": "30.0.1", + "chalk": "^4.1.2", + "graceful-fs": "^4.2.11", "slash": "^3.0.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" }, "peerDependencies": { - "@babel/core": "^7.8.0" + "@babel/core": "^7.11.0" } }, "node_modules/babel-plugin-istanbul": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", - "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-7.0.0.tgz", + "integrity": "sha512-C5OzENSx/A+gt7t4VH1I2XsflxyPUmXRFPKBxt33xncdOmq7oROVM3bZv9Ysjjkv8OJYDMa+tKuKMvqU/H3xdw==", "dev": true, "license": "BSD-3-Clause", "dependencies": { "@babel/helper-plugin-utils": "^7.0.0", "@istanbuljs/load-nyc-config": "^1.0.0", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-instrument": "^5.0.4", + "@istanbuljs/schema": "^0.1.3", + "istanbul-lib-instrument": "^6.0.2", "test-exclude": "^6.0.0" }, "engines": { - "node": ">=8" + "node": ">=12" } }, "node_modules/babel-plugin-istanbul/node_modules/glob": { @@ -3253,23 +3256,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/babel-plugin-istanbul/node_modules/istanbul-lib-instrument": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", - "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "@babel/core": "^7.12.3", - "@babel/parser": "^7.14.7", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-coverage": "^3.2.0", - "semver": "^6.3.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/babel-plugin-istanbul/node_modules/test-exclude": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", @@ -3286,19 +3272,18 @@ } }, "node_modules/babel-plugin-jest-hoist": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.6.3.tgz", - "integrity": "sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==", + "version": "30.0.1", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-30.0.1.tgz", + "integrity": "sha512-zTPME3pI50NsFW8ZBaVIOeAxzEY7XHlmWeXXu9srI+9kNfzCUTy8MFan46xOGZY8NZThMqq+e3qZUKsvXbasnQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/template": "^7.3.3", - "@babel/types": "^7.3.3", - "@types/babel__core": "^7.1.14", - "@types/babel__traverse": "^7.0.6" + "@babel/template": "^7.27.2", + "@babel/types": "^7.27.3", + "@types/babel__core": "^7.20.5" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/babel-preset-current-node-syntax": { @@ -3329,20 +3314,20 @@ } }, "node_modules/babel-preset-jest": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.6.3.tgz", - "integrity": "sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==", + "version": "30.0.1", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-30.0.1.tgz", + "integrity": "sha512-+YHejD5iTWI46cZmcc/YtX4gaKBtdqCHCVfuVinizVpbmyjO3zYmeuyFdfA8duRqQZfgCAMlsfmkVbJ+e2MAJw==", "dev": true, "license": "MIT", "dependencies": { - "babel-plugin-jest-hoist": "^29.6.3", - "babel-preset-current-node-syntax": "^1.0.0" + "babel-plugin-jest-hoist": "30.0.1", + "babel-preset-current-node-syntax": "^1.1.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" }, "peerDependencies": { - "@babel/core": "^7.0.0" + "@babel/core": "^7.11.0" } }, "node_modules/balanced-match": { @@ -3401,9 +3386,9 @@ } }, "node_modules/browserslist": { - "version": "4.25.1", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.25.1.tgz", - "integrity": "sha512-KGj0KoOMXLpSNkkEI6Z6mShmQy0bc1I+T7K9N81k4WWMrfz+6fQ6es80B/YLAeRoKvjYE1YSHHOW1qe9xIVzHw==", + "version": "4.25.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.25.2.tgz", + "integrity": "sha512-0si2SJK3ooGzIawRu61ZdPCO1IncZwS8IzuX73sPZsXW6EQ/w/DAfPyKI8l1ETTCr2MnvqWitmlCUxgdul45jA==", "dev": true, "funding": [ { @@ -3421,8 +3406,8 @@ ], "license": "MIT", "dependencies": { - "caniuse-lite": "^1.0.30001726", - "electron-to-chromium": "^1.5.173", + "caniuse-lite": "^1.0.30001733", + "electron-to-chromium": "^1.5.199", "node-releases": "^2.0.19", "update-browserslist-db": "^1.1.3" }, @@ -3632,9 +3617,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001731", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001731.tgz", - "integrity": "sha512-lDdp2/wrOmTRWuoB5DpfNkC0rJDU8DqRa6nYL6HK6sytw70QMopt/NIc/9SM7ylItlBWfACXk0tEn37UWM/+mg==", + "version": "1.0.30001735", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001735.tgz", + "integrity": "sha512-EV/laoX7Wq2J9TQlyIXRxTJqIw4sxfXS4OYgudGxBYRuTv0q7AM6yMEpU/Vo1I94thg9U6EZ2NfZx9GJq83u7w==", "dev": true, "funding": [ { @@ -3680,9 +3665,9 @@ } }, "node_modules/ci-info": { - "version": "3.9.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", - "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.3.0.tgz", + "integrity": "sha512-l+2bNRMiQgcfILUi33labAZYIWlH1kWDp+ecNo5iisRKrbm0xcRyCww71/YU0Fkw0mAFpz9bJayXPjey6vkmaQ==", "dev": true, "funding": [ { @@ -3696,9 +3681,9 @@ } }, "node_modules/cjs-module-lexer": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.4.3.tgz", - "integrity": "sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-2.1.0.tgz", + "integrity": "sha512-UX0OwmYRYQQetfrLEZeewIFFI+wSTofC+pMBLNuH3RUuu/xzG1oz84UCEDOSoQlN3fZ4+AzmV50ZYvGqkMh9yA==", "dev": true, "license": "MIT" }, @@ -3842,45 +3827,22 @@ "dev": true, "license": "MIT" }, - "node_modules/create-jest": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/create-jest/-/create-jest-29.7.0.tgz", - "integrity": "sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/types": "^29.6.3", - "chalk": "^4.0.0", - "exit": "^0.1.2", - "graceful-fs": "^4.2.9", - "jest-config": "^29.7.0", - "jest-util": "^29.7.0", - "prompts": "^2.0.1" - }, - "bin": { - "create-jest": "bin/create-jest.js" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, "node_modules/cross-env": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-7.0.3.tgz", - "integrity": "sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==", + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-10.0.0.tgz", + "integrity": "sha512-aU8qlEK/nHYtVuN4p7UQgAwVljzMg8hB4YK5ThRqD2l/ziSnryncPNn7bMLt5cFYsKVKBh8HqLqyCoTupEUu7Q==", "dev": true, "license": "MIT", "dependencies": { - "cross-spawn": "^7.0.1" + "@epic-web/invariant": "^1.0.0", + "cross-spawn": "^7.0.6" }, "bin": { - "cross-env": "src/bin/cross-env.js", - "cross-env-shell": "src/bin/cross-env-shell.js" + "cross-env": "dist/bin/cross-env.js", + "cross-env-shell": "dist/bin/cross-env-shell.js" }, "engines": { - "node": ">=10.14", - "npm": ">=6", - "yarn": ">=1" + "node": ">=20" } }, "node_modules/cross-spawn": { @@ -4157,9 +4119,9 @@ "license": "MIT" }, "node_modules/electron-to-chromium": { - "version": "1.5.194", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.194.tgz", - "integrity": "sha512-SdnWJwSUot04UR51I2oPD8kuP2VI37/CADR1OHsFOUzZIvfWJBO6q11k5P/uKNyTT3cdOsnyjkrZ+DDShqYqJA==", + "version": "1.5.203", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.203.tgz", + "integrity": "sha512-uz4i0vLhfm6dLZWbz/iH88KNDV+ivj5+2SA+utpgjKaj9Q0iDLuwk6Idhe9BTxciHudyx6IvTvijhkPvFGUQ0g==", "dev": true, "license": "ISC" }, @@ -4184,9 +4146,9 @@ "license": "MIT" }, "node_modules/enhanced-resolve": { - "version": "5.18.2", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.2.tgz", - "integrity": "sha512-6Jw4sE1maoRJo3q8MsSIn2onJFbLTOjY9hlx4DZXmOKvLRd1Ok2kXmAGXaafL2+ijsJZ1ClYbl/pmqr9+k4iUQ==", + "version": "5.18.3", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.3.tgz", + "integrity": "sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww==", "dev": true, "license": "MIT", "dependencies": { @@ -4385,9 +4347,9 @@ } }, "node_modules/esbuild": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.8.tgz", - "integrity": "sha512-vVC0USHGtMi8+R4Kz8rt6JhEWLxsv9Rnu/lGYbPR8u47B+DCBksq9JarW0zOO7bs37hyOK1l2/oqtbciutL5+Q==", + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.9.tgz", + "integrity": "sha512-CRbODhYyQx3qp7ZEwzxOk4JBqmD/seJrzPa/cGjY1VtIn5E09Oi9/dB4JwctnfZ8Q8iT7rioVv5k/FNT/uf54g==", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -4398,32 +4360,32 @@ "node": ">=18" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.25.8", - "@esbuild/android-arm": "0.25.8", - "@esbuild/android-arm64": "0.25.8", - "@esbuild/android-x64": "0.25.8", - "@esbuild/darwin-arm64": "0.25.8", - "@esbuild/darwin-x64": "0.25.8", - "@esbuild/freebsd-arm64": "0.25.8", - "@esbuild/freebsd-x64": "0.25.8", - "@esbuild/linux-arm": "0.25.8", - "@esbuild/linux-arm64": "0.25.8", - "@esbuild/linux-ia32": "0.25.8", - "@esbuild/linux-loong64": "0.25.8", - "@esbuild/linux-mips64el": "0.25.8", - "@esbuild/linux-ppc64": "0.25.8", - "@esbuild/linux-riscv64": "0.25.8", - "@esbuild/linux-s390x": "0.25.8", - "@esbuild/linux-x64": "0.25.8", - "@esbuild/netbsd-arm64": "0.25.8", - "@esbuild/netbsd-x64": "0.25.8", - "@esbuild/openbsd-arm64": "0.25.8", - "@esbuild/openbsd-x64": "0.25.8", - "@esbuild/openharmony-arm64": "0.25.8", - "@esbuild/sunos-x64": "0.25.8", - "@esbuild/win32-arm64": "0.25.8", - "@esbuild/win32-ia32": "0.25.8", - "@esbuild/win32-x64": "0.25.8" + "@esbuild/aix-ppc64": "0.25.9", + "@esbuild/android-arm": "0.25.9", + "@esbuild/android-arm64": "0.25.9", + "@esbuild/android-x64": "0.25.9", + "@esbuild/darwin-arm64": "0.25.9", + "@esbuild/darwin-x64": "0.25.9", + "@esbuild/freebsd-arm64": "0.25.9", + "@esbuild/freebsd-x64": "0.25.9", + "@esbuild/linux-arm": "0.25.9", + "@esbuild/linux-arm64": "0.25.9", + "@esbuild/linux-ia32": "0.25.9", + "@esbuild/linux-loong64": "0.25.9", + "@esbuild/linux-mips64el": "0.25.9", + "@esbuild/linux-ppc64": "0.25.9", + "@esbuild/linux-riscv64": "0.25.9", + "@esbuild/linux-s390x": "0.25.9", + "@esbuild/linux-x64": "0.25.9", + "@esbuild/netbsd-arm64": "0.25.9", + "@esbuild/netbsd-x64": "0.25.9", + "@esbuild/openbsd-arm64": "0.25.9", + "@esbuild/openbsd-x64": "0.25.9", + "@esbuild/openharmony-arm64": "0.25.9", + "@esbuild/sunos-x64": "0.25.9", + "@esbuild/win32-arm64": "0.25.9", + "@esbuild/win32-ia32": "0.25.9", + "@esbuild/win32-x64": "0.25.9" } }, "node_modules/escalade": { @@ -4450,20 +4412,20 @@ } }, "node_modules/eslint": { - "version": "9.32.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.32.0.tgz", - "integrity": "sha512-LSehfdpgMeWcTZkWZVIJl+tkZ2nuSkyyB9C27MZqFWXuph7DvaowgcTvKqxvpLW1JZIk8PN7hFY3Rj9LQ7m7lg==", + "version": "9.33.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.33.0.tgz", + "integrity": "sha512-TS9bTNIryDzStCpJN93aC5VRSW3uTx9sClUn4B87pwiCaJh220otoI0X8mJKr+VcPtniMdN8GKjlwgWGUv5ZKA==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.12.1", "@eslint/config-array": "^0.21.0", - "@eslint/config-helpers": "^0.3.0", - "@eslint/core": "^0.15.0", + "@eslint/config-helpers": "^0.3.1", + "@eslint/core": "^0.15.2", "@eslint/eslintrc": "^3.3.1", - "@eslint/js": "9.32.0", - "@eslint/plugin-kit": "^0.3.4", + "@eslint/js": "9.33.0", + "@eslint/plugin-kit": "^0.3.5", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", @@ -4860,24 +4822,6 @@ "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7" } }, - "node_modules/eslint-plugin-react/node_modules/resolve": { - "version": "2.0.0-next.5", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.5.tgz", - "integrity": "sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-core-module": "^2.13.0", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - }, - "bin": { - "resolve": "bin/resolve" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/eslint-rule-docs": { "version": "1.1.235", "resolved": "https://registry.npmjs.org/eslint-rule-docs/-/eslint-rule-docs-1.1.235.tgz", @@ -5089,30 +5033,32 @@ "url": "https://github.com/sindresorhus/execa?sponsor=1" } }, - "node_modules/exit": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", - "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==", + "node_modules/exit-x": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/exit-x/-/exit-x-0.2.2.tgz", + "integrity": "sha512-+I6B/IkJc1o/2tiURyz/ivu/O0nKNEArIUB5O7zBrlDVJr22SCLH3xTeEry428LvFhRzIA1g8izguxJ/gbNcVQ==", "dev": true, + "license": "MIT", "engines": { "node": ">= 0.8.0" } }, "node_modules/expect": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/expect/-/expect-29.7.0.tgz", - "integrity": "sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==", + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/expect/-/expect-30.0.5.tgz", + "integrity": "sha512-P0te2pt+hHI5qLJkIR+iMvS+lYUZml8rKKsohVHAGY+uClp9XVbdyYNJOIjSRpHVp8s8YqxJCiHUkSYZGr8rtQ==", "dev": true, "license": "MIT", "dependencies": { - "@jest/expect-utils": "^29.7.0", - "jest-get-type": "^29.6.3", - "jest-matcher-utils": "^29.7.0", - "jest-message-util": "^29.7.0", - "jest-util": "^29.7.0" + "@jest/expect-utils": "30.0.5", + "@jest/get-type": "30.0.1", + "jest-matcher-utils": "30.0.5", + "jest-message-util": "30.0.5", + "jest-mock": "30.0.5", + "jest-util": "30.0.5" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/fast-check": { @@ -5792,17 +5738,6 @@ "dev": true, "license": "MIT" }, - "node_modules/https-pem": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/https-pem/-/https-pem-3.0.0.tgz", - "integrity": "sha512-JqYVRTpk1WeXziwBaTX6eyXod6Dt70d/kehtY3DR6ygl+11XgcksTjSl4NjZbNCKK3rpTB1qH9hnu75RSOFUWQ==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "dependencies": { - "selfsigned": "^2.0.1" - } - }, "node_modules/human-signals": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-8.0.1.tgz", @@ -6490,15 +6425,15 @@ } }, "node_modules/istanbul-lib-source-maps": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", - "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-5.0.6.tgz", + "integrity": "sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==", "dev": true, "license": "BSD-3-Clause", "dependencies": { + "@jridgewell/trace-mapping": "^0.3.23", "debug": "^4.1.1", - "istanbul-lib-coverage": "^3.0.0", - "source-map": "^0.6.1" + "istanbul-lib-coverage": "^3.0.0" }, "engines": { "node": ">=10" @@ -6553,22 +6488,22 @@ } }, "node_modules/jest": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest/-/jest-29.7.0.tgz", - "integrity": "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==", + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/jest/-/jest-30.0.5.tgz", + "integrity": "sha512-y2mfcJywuTUkvLm2Lp1/pFX8kTgMO5yyQGq/Sk/n2mN7XWYp4JsCZ/QXW34M8YScgk8bPZlREH04f6blPnoHnQ==", "dev": true, "license": "MIT", "dependencies": { - "@jest/core": "^29.7.0", - "@jest/types": "^29.6.3", - "import-local": "^3.0.2", - "jest-cli": "^29.7.0" + "@jest/core": "30.0.5", + "@jest/types": "30.0.5", + "import-local": "^3.2.0", + "jest-cli": "30.0.5" }, "bin": { "jest": "bin/jest.js" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" }, "peerDependencies": { "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" @@ -6580,18 +6515,18 @@ } }, "node_modules/jest-changed-files": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-29.7.0.tgz", - "integrity": "sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w==", + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-30.0.5.tgz", + "integrity": "sha512-bGl2Ntdx0eAwXuGpdLdVYVr5YQHnSZlQ0y9HVDu565lCUAe9sj6JOtBbMmBBikGIegne9piDDIOeiLVoqTkz4A==", "dev": true, "license": "MIT", "dependencies": { - "execa": "^5.0.0", - "jest-util": "^29.7.0", + "execa": "^5.1.1", + "jest-util": "30.0.5", "p-limit": "^3.1.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-changed-files/node_modules/execa": { @@ -6685,78 +6620,60 @@ } }, "node_modules/jest-circus": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-29.7.0.tgz", - "integrity": "sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw==", + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-30.0.5.tgz", + "integrity": "sha512-h/sjXEs4GS+NFFfqBDYT7y5Msfxh04EwWLhQi0F8kuWpe+J/7tICSlswU8qvBqumR3kFgHbfu7vU6qruWWBPug==", "dev": true, "license": "MIT", "dependencies": { - "@jest/environment": "^29.7.0", - "@jest/expect": "^29.7.0", - "@jest/test-result": "^29.7.0", - "@jest/types": "^29.6.3", + "@jest/environment": "30.0.5", + "@jest/expect": "30.0.5", + "@jest/test-result": "30.0.5", + "@jest/types": "30.0.5", "@types/node": "*", - "chalk": "^4.0.0", + "chalk": "^4.1.2", "co": "^4.6.0", - "dedent": "^1.0.0", - "is-generator-fn": "^2.0.0", - "jest-each": "^29.7.0", - "jest-matcher-utils": "^29.7.0", - "jest-message-util": "^29.7.0", - "jest-runtime": "^29.7.0", - "jest-snapshot": "^29.7.0", - "jest-util": "^29.7.0", + "dedent": "^1.6.0", + "is-generator-fn": "^2.1.0", + "jest-each": "30.0.5", + "jest-matcher-utils": "30.0.5", + "jest-message-util": "30.0.5", + "jest-runtime": "30.0.5", + "jest-snapshot": "30.0.5", + "jest-util": "30.0.5", "p-limit": "^3.1.0", - "pretty-format": "^29.7.0", - "pure-rand": "^6.0.0", + "pretty-format": "30.0.5", + "pure-rand": "^7.0.0", "slash": "^3.0.0", - "stack-utils": "^2.0.3" + "stack-utils": "^2.0.6" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/jest-circus/node_modules/pure-rand": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz", - "integrity": "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/dubzzz" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fast-check" - } - ], - "license": "MIT" - }, "node_modules/jest-cli": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-29.7.0.tgz", - "integrity": "sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg==", + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-30.0.5.tgz", + "integrity": "sha512-Sa45PGMkBZzF94HMrlX4kUyPOwUpdZasaliKN3mifvDmkhLYqLLg8HQTzn6gq7vJGahFYMQjXgyJWfYImKZzOw==", "dev": true, "license": "MIT", "dependencies": { - "@jest/core": "^29.7.0", - "@jest/test-result": "^29.7.0", - "@jest/types": "^29.6.3", - "chalk": "^4.0.0", - "create-jest": "^29.7.0", - "exit": "^0.1.2", - "import-local": "^3.0.2", - "jest-config": "^29.7.0", - "jest-util": "^29.7.0", - "jest-validate": "^29.7.0", - "yargs": "^17.3.1" + "@jest/core": "30.0.5", + "@jest/test-result": "30.0.5", + "@jest/types": "30.0.5", + "chalk": "^4.1.2", + "exit-x": "^0.2.2", + "import-local": "^3.2.0", + "jest-config": "30.0.5", + "jest-util": "30.0.5", + "jest-validate": "30.0.5", + "yargs": "^17.7.2" }, "bin": { "jest": "bin/jest.js" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" }, "peerDependencies": { "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" @@ -6768,135 +6685,120 @@ } }, "node_modules/jest-config": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.7.0.tgz", - "integrity": "sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==", + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-30.0.5.tgz", + "integrity": "sha512-aIVh+JNOOpzUgzUnPn5FLtyVnqc3TQHVMupYtyeURSb//iLColiMIR8TxCIDKyx9ZgjKnXGucuW68hCxgbrwmA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/core": "^7.11.6", - "@jest/test-sequencer": "^29.7.0", - "@jest/types": "^29.6.3", - "babel-jest": "^29.7.0", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "deepmerge": "^4.2.2", - "glob": "^7.1.3", - "graceful-fs": "^4.2.9", - "jest-circus": "^29.7.0", - "jest-environment-node": "^29.7.0", - "jest-get-type": "^29.6.3", - "jest-regex-util": "^29.6.3", - "jest-resolve": "^29.7.0", - "jest-runner": "^29.7.0", - "jest-util": "^29.7.0", - "jest-validate": "^29.7.0", - "micromatch": "^4.0.4", + "@babel/core": "^7.27.4", + "@jest/get-type": "30.0.1", + "@jest/pattern": "30.0.1", + "@jest/test-sequencer": "30.0.5", + "@jest/types": "30.0.5", + "babel-jest": "30.0.5", + "chalk": "^4.1.2", + "ci-info": "^4.2.0", + "deepmerge": "^4.3.1", + "glob": "^10.3.10", + "graceful-fs": "^4.2.11", + "jest-circus": "30.0.5", + "jest-docblock": "30.0.1", + "jest-environment-node": "30.0.5", + "jest-regex-util": "30.0.1", + "jest-resolve": "30.0.5", + "jest-runner": "30.0.5", + "jest-util": "30.0.5", + "jest-validate": "30.0.5", + "micromatch": "^4.0.8", "parse-json": "^5.2.0", - "pretty-format": "^29.7.0", + "pretty-format": "30.0.5", "slash": "^3.0.0", "strip-json-comments": "^3.1.1" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" }, "peerDependencies": { "@types/node": "*", + "esbuild-register": ">=3.4.0", "ts-node": ">=9.0.0" }, "peerDependenciesMeta": { "@types/node": { "optional": true }, + "esbuild-register": { + "optional": true + }, "ts-node": { "optional": true } } }, - "node_modules/jest-config/node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "deprecated": "Glob versions prior to v9 are no longer supported", - "dev": true, - "license": "ISC", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/jest-diff": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.7.0.tgz", - "integrity": "sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==", + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-30.0.5.tgz", + "integrity": "sha512-1UIqE9PoEKaHcIKvq2vbibrCog4Y8G0zmOxgQUVEiTqwR5hJVMCoDsN1vFvI5JvwD37hjueZ1C4l2FyGnfpE0A==", "dev": true, "license": "MIT", "dependencies": { - "chalk": "^4.0.0", - "diff-sequences": "^29.6.3", - "jest-get-type": "^29.6.3", - "pretty-format": "^29.7.0" + "@jest/diff-sequences": "30.0.1", + "@jest/get-type": "30.0.1", + "chalk": "^4.1.2", + "pretty-format": "30.0.5" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-docblock": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-29.7.0.tgz", - "integrity": "sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g==", + "version": "30.0.1", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-30.0.1.tgz", + "integrity": "sha512-/vF78qn3DYphAaIc3jy4gA7XSAz167n9Bm/wn/1XhTLW7tTBIzXtCJpb/vcmc73NIIeeohCbdL94JasyXUZsGA==", "dev": true, "license": "MIT", "dependencies": { - "detect-newline": "^3.0.0" + "detect-newline": "^3.1.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-each": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-29.7.0.tgz", - "integrity": "sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ==", + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-30.0.5.tgz", + "integrity": "sha512-dKjRsx1uZ96TVyejD3/aAWcNKy6ajMaN531CwWIsrazIqIoXI9TnnpPlkrEYku/8rkS3dh2rbH+kMOyiEIv0xQ==", "dev": true, "license": "MIT", "dependencies": { - "@jest/types": "^29.6.3", - "chalk": "^4.0.0", - "jest-get-type": "^29.6.3", - "jest-util": "^29.7.0", - "pretty-format": "^29.7.0" + "@jest/get-type": "30.0.1", + "@jest/types": "30.0.5", + "chalk": "^4.1.2", + "jest-util": "30.0.5", + "pretty-format": "30.0.5" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-environment-node": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.7.0.tgz", - "integrity": "sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==", + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-30.0.5.tgz", + "integrity": "sha512-ppYizXdLMSvciGsRsMEnv/5EFpvOdXBaXRBzFUDPWrsfmog4kYrOGWXarLllz6AXan6ZAA/kYokgDWuos1IKDA==", "dev": true, "license": "MIT", "dependencies": { - "@jest/environment": "^29.7.0", - "@jest/fake-timers": "^29.7.0", - "@jest/types": "^29.6.3", + "@jest/environment": "30.0.5", + "@jest/fake-timers": "30.0.5", + "@jest/types": "30.0.5", "@types/node": "*", - "jest-mock": "^29.7.0", - "jest-util": "^29.7.0" + "jest-mock": "30.0.5", + "jest-util": "30.0.5", + "jest-validate": "30.0.5" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-get-type": { @@ -6910,95 +6812,94 @@ } }, "node_modules/jest-haste-map": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.7.0.tgz", - "integrity": "sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==", + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-30.0.5.tgz", + "integrity": "sha512-dkmlWNlsTSR0nH3nRfW5BKbqHefLZv0/6LCccG0xFCTWcJu8TuEwG+5Cm75iBfjVoockmO6J35o5gxtFSn5xeg==", "dev": true, "license": "MIT", "dependencies": { - "@jest/types": "^29.6.3", - "@types/graceful-fs": "^4.1.3", + "@jest/types": "30.0.5", "@types/node": "*", - "anymatch": "^3.0.3", - "fb-watchman": "^2.0.0", - "graceful-fs": "^4.2.9", - "jest-regex-util": "^29.6.3", - "jest-util": "^29.7.0", - "jest-worker": "^29.7.0", - "micromatch": "^4.0.4", + "anymatch": "^3.1.3", + "fb-watchman": "^2.0.2", + "graceful-fs": "^4.2.11", + "jest-regex-util": "30.0.1", + "jest-util": "30.0.5", + "jest-worker": "30.0.5", + "micromatch": "^4.0.8", "walker": "^1.0.8" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" }, "optionalDependencies": { - "fsevents": "^2.3.2" + "fsevents": "^2.3.3" } }, "node_modules/jest-leak-detector": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.7.0.tgz", - "integrity": "sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw==", + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-30.0.5.tgz", + "integrity": "sha512-3Uxr5uP8jmHMcsOtYMRB/zf1gXN3yUIc+iPorhNETG54gErFIiUhLvyY/OggYpSMOEYqsmRxmuU4ZOoX5jpRFg==", "dev": true, "license": "MIT", "dependencies": { - "jest-get-type": "^29.6.3", - "pretty-format": "^29.7.0" + "@jest/get-type": "30.0.1", + "pretty-format": "30.0.5" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-matcher-utils": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz", - "integrity": "sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==", + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-30.0.5.tgz", + "integrity": "sha512-uQgGWt7GOrRLP1P7IwNWwK1WAQbq+m//ZY0yXygyfWp0rJlksMSLQAA4wYQC3b6wl3zfnchyTx+k3HZ5aPtCbQ==", "dev": true, "license": "MIT", "dependencies": { - "chalk": "^4.0.0", - "jest-diff": "^29.7.0", - "jest-get-type": "^29.6.3", - "pretty-format": "^29.7.0" + "@jest/get-type": "30.0.1", + "chalk": "^4.1.2", + "jest-diff": "30.0.5", + "pretty-format": "30.0.5" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-message-util": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.7.0.tgz", - "integrity": "sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==", + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-30.0.5.tgz", + "integrity": "sha512-NAiDOhsK3V7RU0Aa/HnrQo+E4JlbarbmI3q6Pi4KcxicdtjV82gcIUrejOtczChtVQR4kddu1E1EJlW6EN9IyA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.12.13", - "@jest/types": "^29.6.3", - "@types/stack-utils": "^2.0.0", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "micromatch": "^4.0.4", - "pretty-format": "^29.7.0", + "@babel/code-frame": "^7.27.1", + "@jest/types": "30.0.5", + "@types/stack-utils": "^2.0.3", + "chalk": "^4.1.2", + "graceful-fs": "^4.2.11", + "micromatch": "^4.0.8", + "pretty-format": "30.0.5", "slash": "^3.0.0", - "stack-utils": "^2.0.3" + "stack-utils": "^2.0.6" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-mock": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.7.0.tgz", - "integrity": "sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==", + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-30.0.5.tgz", + "integrity": "sha512-Od7TyasAAQX/6S+QCbN6vZoWOMwlTtzzGuxJku1GhGanAjz9y+QsQkpScDmETvdc9aSXyJ/Op4rhpMYBWW91wQ==", "dev": true, "license": "MIT", "dependencies": { - "@jest/types": "^29.6.3", + "@jest/types": "30.0.5", "@types/node": "*", - "jest-util": "^29.7.0" + "jest-util": "30.0.5" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-pnp-resolver": { @@ -7020,169 +6921,148 @@ } }, "node_modules/jest-regex-util": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz", - "integrity": "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==", + "version": "30.0.1", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-30.0.1.tgz", + "integrity": "sha512-jHEQgBXAgc+Gh4g0p3bCevgRCVRkB4VB70zhoAE48gxeSr1hfUOsM/C2WoJgVL7Eyg//hudYENbm3Ne+/dRVVA==", "dev": true, "license": "MIT", "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-resolve": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.7.0.tgz", - "integrity": "sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA==", + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-30.0.5.tgz", + "integrity": "sha512-d+DjBQ1tIhdz91B79mywH5yYu76bZuE96sSbxj8MkjWVx5WNdt1deEFRONVL4UkKLSrAbMkdhb24XN691yDRHg==", "dev": true, "license": "MIT", "dependencies": { - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.7.0", - "jest-pnp-resolver": "^1.2.2", - "jest-util": "^29.7.0", - "jest-validate": "^29.7.0", - "resolve": "^1.20.0", - "resolve.exports": "^2.0.0", - "slash": "^3.0.0" + "chalk": "^4.1.2", + "graceful-fs": "^4.2.11", + "jest-haste-map": "30.0.5", + "jest-pnp-resolver": "^1.2.3", + "jest-util": "30.0.5", + "jest-validate": "30.0.5", + "slash": "^3.0.0", + "unrs-resolver": "^1.7.11" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-resolve-dependencies": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-29.7.0.tgz", - "integrity": "sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA==", + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-30.0.5.tgz", + "integrity": "sha512-/xMvBR4MpwkrHW4ikZIWRttBBRZgWK4d6xt3xW1iRDSKt4tXzYkMkyPfBnSCgv96cpkrctfXs6gexeqMYqdEpw==", "dev": true, "license": "MIT", "dependencies": { - "jest-regex-util": "^29.6.3", - "jest-snapshot": "^29.7.0" + "jest-regex-util": "30.0.1", + "jest-snapshot": "30.0.5" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-runner": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-29.7.0.tgz", - "integrity": "sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ==", + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-30.0.5.tgz", + "integrity": "sha512-JcCOucZmgp+YuGgLAXHNy7ualBx4wYSgJVWrYMRBnb79j9PD0Jxh0EHvR5Cx/r0Ce+ZBC4hCdz2AzFFLl9hCiw==", "dev": true, "license": "MIT", "dependencies": { - "@jest/console": "^29.7.0", - "@jest/environment": "^29.7.0", - "@jest/test-result": "^29.7.0", - "@jest/transform": "^29.7.0", - "@jest/types": "^29.6.3", + "@jest/console": "30.0.5", + "@jest/environment": "30.0.5", + "@jest/test-result": "30.0.5", + "@jest/transform": "30.0.5", + "@jest/types": "30.0.5", "@types/node": "*", - "chalk": "^4.0.0", + "chalk": "^4.1.2", "emittery": "^0.13.1", - "graceful-fs": "^4.2.9", - "jest-docblock": "^29.7.0", - "jest-environment-node": "^29.7.0", - "jest-haste-map": "^29.7.0", - "jest-leak-detector": "^29.7.0", - "jest-message-util": "^29.7.0", - "jest-resolve": "^29.7.0", - "jest-runtime": "^29.7.0", - "jest-util": "^29.7.0", - "jest-watcher": "^29.7.0", - "jest-worker": "^29.7.0", + "exit-x": "^0.2.2", + "graceful-fs": "^4.2.11", + "jest-docblock": "30.0.1", + "jest-environment-node": "30.0.5", + "jest-haste-map": "30.0.5", + "jest-leak-detector": "30.0.5", + "jest-message-util": "30.0.5", + "jest-resolve": "30.0.5", + "jest-runtime": "30.0.5", + "jest-util": "30.0.5", + "jest-watcher": "30.0.5", + "jest-worker": "30.0.5", "p-limit": "^3.1.0", "source-map-support": "0.5.13" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-runtime": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.7.0.tgz", - "integrity": "sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ==", + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-30.0.5.tgz", + "integrity": "sha512-7oySNDkqpe4xpX5PPiJTe5vEa+Ak/NnNz2bGYZrA1ftG3RL3EFlHaUkA1Cjx+R8IhK0Vg43RML5mJedGTPNz3A==", "dev": true, "license": "MIT", "dependencies": { - "@jest/environment": "^29.7.0", - "@jest/fake-timers": "^29.7.0", - "@jest/globals": "^29.7.0", - "@jest/source-map": "^29.6.3", - "@jest/test-result": "^29.7.0", - "@jest/transform": "^29.7.0", - "@jest/types": "^29.6.3", + "@jest/environment": "30.0.5", + "@jest/fake-timers": "30.0.5", + "@jest/globals": "30.0.5", + "@jest/source-map": "30.0.1", + "@jest/test-result": "30.0.5", + "@jest/transform": "30.0.5", + "@jest/types": "30.0.5", "@types/node": "*", - "chalk": "^4.0.0", - "cjs-module-lexer": "^1.0.0", - "collect-v8-coverage": "^1.0.0", - "glob": "^7.1.3", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.7.0", - "jest-message-util": "^29.7.0", - "jest-mock": "^29.7.0", - "jest-regex-util": "^29.6.3", - "jest-resolve": "^29.7.0", - "jest-snapshot": "^29.7.0", - "jest-util": "^29.7.0", + "chalk": "^4.1.2", + "cjs-module-lexer": "^2.1.0", + "collect-v8-coverage": "^1.0.2", + "glob": "^10.3.10", + "graceful-fs": "^4.2.11", + "jest-haste-map": "30.0.5", + "jest-message-util": "30.0.5", + "jest-mock": "30.0.5", + "jest-regex-util": "30.0.1", + "jest-resolve": "30.0.5", + "jest-snapshot": "30.0.5", + "jest-util": "30.0.5", "slash": "^3.0.0", "strip-bom": "^4.0.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-runtime/node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "deprecated": "Glob versions prior to v9 are no longer supported", - "dev": true, - "license": "ISC", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-snapshot": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.7.0.tgz", - "integrity": "sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/core": "^7.11.6", - "@babel/generator": "^7.7.2", - "@babel/plugin-syntax-jsx": "^7.7.2", - "@babel/plugin-syntax-typescript": "^7.7.2", - "@babel/types": "^7.3.3", - "@jest/expect-utils": "^29.7.0", - "@jest/transform": "^29.7.0", - "@jest/types": "^29.6.3", - "babel-preset-current-node-syntax": "^1.0.0", - "chalk": "^4.0.0", - "expect": "^29.7.0", - "graceful-fs": "^4.2.9", - "jest-diff": "^29.7.0", - "jest-get-type": "^29.6.3", - "jest-matcher-utils": "^29.7.0", - "jest-message-util": "^29.7.0", - "jest-util": "^29.7.0", - "natural-compare": "^1.4.0", - "pretty-format": "^29.7.0", - "semver": "^7.5.3" + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-30.0.5.tgz", + "integrity": "sha512-T00dWU/Ek3LqTp4+DcW6PraVxjk28WY5Ua/s+3zUKSERZSNyxTqhDXCWKG5p2HAJ+crVQ3WJ2P9YVHpj1tkW+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.27.4", + "@babel/generator": "^7.27.5", + "@babel/plugin-syntax-jsx": "^7.27.1", + "@babel/plugin-syntax-typescript": "^7.27.1", + "@babel/types": "^7.27.3", + "@jest/expect-utils": "30.0.5", + "@jest/get-type": "30.0.1", + "@jest/snapshot-utils": "30.0.5", + "@jest/transform": "30.0.5", + "@jest/types": "30.0.5", + "babel-preset-current-node-syntax": "^1.1.0", + "chalk": "^4.1.2", + "expect": "30.0.5", + "graceful-fs": "^4.2.11", + "jest-diff": "30.0.5", + "jest-matcher-utils": "30.0.5", + "jest-message-util": "30.0.5", + "jest-util": "30.0.5", + "pretty-format": "30.0.5", + "semver": "^7.7.2", + "synckit": "^0.11.8" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-snapshot/node_modules/semver": { @@ -7199,39 +7079,52 @@ } }, "node_modules/jest-util": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", - "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-30.0.5.tgz", + "integrity": "sha512-pvyPWssDZR0FlfMxCBoc0tvM8iUEskaRFALUtGQYzVEAqisAztmy+R8LnU14KT4XA0H/a5HMVTXat1jLne010g==", "dev": true, "license": "MIT", "dependencies": { - "@jest/types": "^29.6.3", + "@jest/types": "30.0.5", "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" + "chalk": "^4.1.2", + "ci-info": "^4.2.0", + "graceful-fs": "^4.2.11", + "picomatch": "^4.0.2" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-util/node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" } }, "node_modules/jest-validate": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.7.0.tgz", - "integrity": "sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==", + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-30.0.5.tgz", + "integrity": "sha512-ouTm6VFHaS2boyl+k4u+Qip4TSH7Uld5tyD8psQ8abGgt2uYYB8VwVfAHWHjHc0NWmGGbwO5h0sCPOGHHevefw==", "dev": true, "license": "MIT", "dependencies": { - "@jest/types": "^29.6.3", - "camelcase": "^6.2.0", - "chalk": "^4.0.0", - "jest-get-type": "^29.6.3", + "@jest/get-type": "30.0.1", + "@jest/types": "30.0.5", + "camelcase": "^6.3.0", + "chalk": "^4.1.2", "leven": "^3.1.0", - "pretty-format": "^29.7.0" + "pretty-format": "30.0.5" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-validate/node_modules/camelcase": { @@ -7248,39 +7141,40 @@ } }, "node_modules/jest-watcher": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.7.0.tgz", - "integrity": "sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g==", + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-30.0.5.tgz", + "integrity": "sha512-z9slj/0vOwBDBjN3L4z4ZYaA+pG56d6p3kTUhFRYGvXbXMWhXmb/FIxREZCD06DYUwDKKnj2T80+Pb71CQ0KEg==", "dev": true, "license": "MIT", "dependencies": { - "@jest/test-result": "^29.7.0", - "@jest/types": "^29.6.3", + "@jest/test-result": "30.0.5", + "@jest/types": "30.0.5", "@types/node": "*", - "ansi-escapes": "^4.2.1", - "chalk": "^4.0.0", + "ansi-escapes": "^4.3.2", + "chalk": "^4.1.2", "emittery": "^0.13.1", - "jest-util": "^29.7.0", - "string-length": "^4.0.1" + "jest-util": "30.0.5", + "string-length": "^4.0.2" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-worker": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz", - "integrity": "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==", + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-30.0.5.tgz", + "integrity": "sha512-ojRXsWzEP16NdUuBw/4H/zkZdHOa7MMYCk4E430l+8fELeLg/mqmMlRhjL7UNZvQrDmnovWZV4DxX03fZF48fQ==", "dev": true, "license": "MIT", "dependencies": { "@types/node": "*", - "jest-util": "^29.7.0", + "@ungap/structured-clone": "^1.3.0", + "jest-util": "30.0.5", "merge-stream": "^2.0.0", - "supports-color": "^8.0.0" + "supports-color": "^8.1.1" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-worker/node_modules/supports-color": { @@ -7409,16 +7303,6 @@ "node": ">=0.10.0" } }, - "node_modules/kleur": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", - "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/leven": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", @@ -7755,9 +7639,9 @@ "license": "MIT" }, "node_modules/napi-postinstall": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/napi-postinstall/-/napi-postinstall-0.3.2.tgz", - "integrity": "sha512-tWVJxJHmBWLy69PvO96TZMZDrzmw5KeiZBz3RHmiM2XZ9grBJ2WgMAFVVg25nqp3ZjTFUs2Ftw1JhscL3Teliw==", + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/napi-postinstall/-/napi-postinstall-0.3.3.tgz", + "integrity": "sha512-uTp172LLXSxuSYHv/kou+f6KW3SMppU9ivthaVTXian9sOt3XM/zHYHpRZiLgQoxeWfYUnslNWQHF1+G71xcow==", "dev": true, "license": "MIT", "bin": { @@ -8504,18 +8388,18 @@ } }, "node_modules/pretty-format": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", - "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.0.5.tgz", + "integrity": "sha512-D1tKtYvByrBkFLe2wHJl2bwMJIiT8rW+XA+TiataH79/FszLQMrpGEvzUVkzPau7OCO0Qnrhpe87PqtOAIB8Yw==", "dev": true, "license": "MIT", "dependencies": { - "@jest/schemas": "^29.6.3", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" + "@jest/schemas": "30.0.5", + "ansi-styles": "^5.2.0", + "react-is": "^18.3.1" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/pretty-format/node_modules/ansi-styles": { @@ -8547,20 +8431,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/prompts": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", - "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "kleur": "^3.0.3", - "sisteransi": "^1.0.5" - }, - "engines": { - "node": ">= 6" - } - }, "node_modules/prop-types": { "version": "15.8.1", "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", @@ -8793,6 +8663,27 @@ "validate-npm-package-license": "^3.0.1" } }, + "node_modules/read-pkg/node_modules/resolve": { + "version": "1.22.10", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", + "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-core-module": "^2.16.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/read-pkg/node_modules/semver": { "version": "5.7.2", "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", @@ -8882,22 +8773,19 @@ } }, "node_modules/resolve": { - "version": "1.22.10", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", - "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", + "version": "2.0.0-next.5", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.5.tgz", + "integrity": "sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA==", "dev": true, "license": "MIT", "dependencies": { - "is-core-module": "^2.16.0", + "is-core-module": "^2.13.0", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" }, - "engines": { - "node": ">= 0.4" - }, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -8945,16 +8833,6 @@ "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" } }, - "node_modules/resolve.exports": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.3.tgz", - "integrity": "sha512-OcXjMsGdhL4XnbShKpAcSqPMzQoYkYyhbEaeSko47MjRP9NfEQMhZkXL1DoFlt9LWQn4YttrdnV6X2OiyzBi+A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - } - }, "node_modules/reusify": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", @@ -9046,13 +8924,12 @@ } }, "node_modules/selfsigned": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-2.4.1.tgz", - "integrity": "sha512-th5B4L2U+eGLq1TVh7zNRGBapioSORUeymIydxgFpwww9d2qyKvtuPU2jJuHvYAwwqi2Y596QBL3eEqcPEYL8Q==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-3.0.1.tgz", + "integrity": "sha512-6U6w6kSLrM9Zxo0D7mC7QdGS6ZZytMWBnj/vhF9p+dAHx6CwGezuRcO4VclTbrrI7mg7SD6zNiqXUuBHOVopNQ==", "dev": true, "license": "MIT", "dependencies": { - "@types/node-forge": "^1.3.0", "node-forge": "^1" }, "engines": { @@ -9230,13 +9107,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/sisteransi": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", - "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", - "dev": true, - "license": "MIT" - }, "node_modules/slash": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", @@ -9298,9 +9168,9 @@ } }, "node_modules/spdx-license-ids": { - "version": "3.0.21", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.21.tgz", - "integrity": "sha512-Bvg/8F5XephndSK3JffaRqdT+gyhfqIPwDHpX80tJrF8QQRYMo8sNMeaZ2Dp5+jhwKnUmIOyFFQfHRkjJm5nXg==", + "version": "3.0.22", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.22.tgz", + "integrity": "sha512-4PRT4nh1EImPbt2jASOKHX7PB7I+e4IWNLvkKFDxNhJlfjbYlleYQh285Z/3mPTHSAK/AvdMmw5BNNuYH8ShgQ==", "dev": true, "license": "CC0-1.0" }, @@ -9693,6 +9563,22 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/synckit": { + "version": "0.11.11", + "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.11.tgz", + "integrity": "sha512-MeQTA1r0litLUf0Rp/iisCaL8761lKAZHaimlbGK4j0HysC4PLfqygQj9srcs0m2RdtDYnF8UuYyKpbjHYp7Jw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@pkgr/core": "^0.2.9" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/synckit" + } + }, "node_modules/tapable": { "version": "2.2.2", "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.2.tgz", @@ -9762,11 +9648,14 @@ } }, "node_modules/tinyglobby/node_modules/fdir": { - "version": "6.4.6", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.6.tgz", - "integrity": "sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==", + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", "dev": true, "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, "peerDependencies": { "picomatch": "^3 || ^4" }, @@ -9869,13 +9758,13 @@ } }, "node_modules/tsd": { - "version": "0.32.0", - "resolved": "https://registry.npmjs.org/tsd/-/tsd-0.32.0.tgz", - "integrity": "sha512-R5lBZCbxGBowOcW0gpQaiIjGYrG5NmU+PfFDKcc3zbtzWjML1o/zAwzdDnS2ZheSlPu9GW51azpFqEPUBq9DoQ==", + "version": "0.33.0", + "resolved": "https://registry.npmjs.org/tsd/-/tsd-0.33.0.tgz", + "integrity": "sha512-/PQtykJFVw90QICG7zyPDMIyueOXKL7jOJVoX5pILnb3Ux+7QqynOxfVvarE+K+yi7BZyOSY4r+OZNWSWRiEwQ==", "dev": true, "license": "MIT", "dependencies": { - "@tsd/typescript": "~5.8.3", + "@tsd/typescript": "^5.9.2", "eslint-formatter-pretty": "^4.1.0", "globby": "^11.0.1", "jest-diff": "^29.0.3", @@ -9890,6 +9779,55 @@ "node": ">=14.16" } }, + "node_modules/tsd/node_modules/@jest/schemas": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", + "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sinclair/typebox": "^0.27.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/tsd/node_modules/@sinclair/typebox": { + "version": "0.27.8", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", + "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", + "dev": true, + "license": "MIT" + }, + "node_modules/tsd/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/tsd/node_modules/jest-diff": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.7.0.tgz", + "integrity": "sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.0.0", + "diff-sequences": "^29.6.3", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, "node_modules/tsd/node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -9900,6 +9838,21 @@ "node": ">=8" } }, + "node_modules/tsd/node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, "node_modules/tslib": { "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", @@ -10033,9 +9986,9 @@ } }, "node_modules/typescript": { - "version": "5.8.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", - "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", + "version": "5.9.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.2.tgz", + "integrity": "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==", "dev": true, "license": "Apache-2.0", "bin": { @@ -10047,16 +10000,16 @@ } }, "node_modules/typescript-eslint": { - "version": "8.38.0", - "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.38.0.tgz", - "integrity": "sha512-FsZlrYK6bPDGoLeZRuvx2v6qrM03I0U0SnfCLPs/XCCPCFD80xU9Pg09H/K+XFa68uJuZo7l/Xhs+eDRg2l3hg==", + "version": "8.39.1", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.39.1.tgz", + "integrity": "sha512-GDUv6/NDYngUlNvwaHM1RamYftxf782IyEDbdj3SeaIHHv8fNQVRC++fITT7kUJV/5rIA/tkoRSSskt6osEfqg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/eslint-plugin": "8.38.0", - "@typescript-eslint/parser": "8.38.0", - "@typescript-eslint/typescript-estree": "8.38.0", - "@typescript-eslint/utils": "8.38.0" + "@typescript-eslint/eslint-plugin": "8.39.1", + "@typescript-eslint/parser": "8.39.1", + "@typescript-eslint/typescript-estree": "8.39.1", + "@typescript-eslint/utils": "8.39.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -10067,7 +10020,7 @@ }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <5.9.0" + "typescript": ">=4.8.4 <6.0.0" } }, "node_modules/unbox-primitive": { @@ -10462,26 +10415,19 @@ "license": "ISC" }, "node_modules/write-file-atomic": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz", - "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-5.0.1.tgz", + "integrity": "sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw==", "dev": true, "license": "ISC", "dependencies": { "imurmurhash": "^0.1.4", - "signal-exit": "^3.0.7" + "signal-exit": "^4.0.1" }, "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, - "node_modules/write-file-atomic/node_modules/signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "dev": true, - "license": "ISC" - }, "node_modules/ws": { "version": "8.18.3", "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", @@ -10522,9 +10468,9 @@ "license": "ISC" }, "node_modules/yaml": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.0.tgz", - "integrity": "sha512-4lLa/EcQCB0cJkyts+FpIRx5G/llPxfP6VQU5KByHEhLxY3IJCH0f0Hy1MHI8sClTvsIb8qwRJ6R/ZdlDJ/leQ==", + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.1.tgz", + "integrity": "sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw==", "dev": true, "license": "ISC", "bin": { diff --git a/deps/undici/src/package.json b/deps/undici/src/package.json index 48948be80dd166..ef6fe2b902d370 100644 --- a/deps/undici/src/package.json +++ b/deps/undici/src/package.json @@ -1,6 +1,6 @@ { "name": "undici", - "version": "7.13.0", + "version": "7.14.0", "description": "An HTTP/1.1 client, written from scratch for Node.js", "homepage": "https://undici.nodejs.org", "bugs": { @@ -109,23 +109,23 @@ "devDependencies": { "@fastify/busboy": "3.1.1", "@matteo.collina/tspl": "^0.2.0", + "@metcoder95/https-pem": "^1.0.0", "@sinonjs/fake-timers": "^12.0.0", "@types/node": "^18.19.50", "abort-controller": "^3.0.0", "borp": "^0.20.0", "c8": "^10.0.0", - "cross-env": "^7.0.3", + "cross-env": "^10.0.0", "dns-packet": "^5.4.0", "esbuild": "^0.25.2", "eslint": "^9.9.0", "fast-check": "^4.1.1", - "https-pem": "^3.0.0", "husky": "^9.0.7", - "jest": "^29.0.2", + "jest": "^30.0.5", "neostandard": "^0.12.0", "node-forge": "^1.3.1", "proxy": "^2.1.1", - "tsd": "^0.32.0", + "tsd": "^0.33.0", "typescript": "^5.6.2", "ws": "^8.11.0" }, diff --git a/deps/undici/src/scripts/generate-pem.js b/deps/undici/src/scripts/generate-pem.js index 88ac5c8392f610..7401b03733b893 100644 --- a/deps/undici/src/scripts/generate-pem.js +++ b/deps/undici/src/scripts/generate-pem.js @@ -1,4 +1,4 @@ 'use strict' /* istanbul ignore file */ -require('https-pem/install') +require('@metcoder95/https-pem/install') diff --git a/deps/undici/src/types/eventsource.d.ts b/deps/undici/src/types/eventsource.d.ts index c85c4a14e586e4..081ca09aee97ff 100644 --- a/deps/undici/src/types/eventsource.d.ts +++ b/deps/undici/src/types/eventsource.d.ts @@ -56,6 +56,11 @@ export declare const EventSource: { } interface EventSourceInit { - withCredentials?: boolean, + withCredentials?: boolean + // @deprecated use `node.dispatcher` instead dispatcher?: Dispatcher + node?: { + dispatcher?: Dispatcher + reconnectionTime?: number + } } diff --git a/deps/undici/src/types/index.d.ts b/deps/undici/src/types/index.d.ts index f9035293a95037..be0bc289c5f48c 100644 --- a/deps/undici/src/types/index.d.ts +++ b/deps/undici/src/types/index.d.ts @@ -34,7 +34,9 @@ export * from './content-type' export * from './cache' export { Interceptable } from './mock-interceptor' -export { Dispatcher, BalancedPool, Pool, Client, buildConnector, errors, Agent, request, stream, pipeline, connect, upgrade, setGlobalDispatcher, getGlobalDispatcher, setGlobalOrigin, getGlobalOrigin, interceptors, MockClient, MockPool, MockAgent, SnapshotAgent, MockCallHistory, MockCallHistoryLog, mockErrors, ProxyAgent, EnvHttpProxyAgent, RedirectHandler, DecoratorHandler, RetryHandler, RetryAgent, H2CClient } +declare function globalThisInstall (): void + +export { Dispatcher, BalancedPool, Pool, Client, buildConnector, errors, Agent, request, stream, pipeline, connect, upgrade, setGlobalDispatcher, getGlobalDispatcher, setGlobalOrigin, getGlobalOrigin, interceptors, MockClient, MockPool, MockAgent, SnapshotAgent, MockCallHistory, MockCallHistoryLog, mockErrors, ProxyAgent, EnvHttpProxyAgent, RedirectHandler, DecoratorHandler, RetryHandler, RetryAgent, H2CClient, globalThisInstall as install } export default Undici declare namespace Undici { @@ -74,4 +76,5 @@ declare namespace Undici { MemoryCacheStore: typeof import('./cache-interceptor').default.MemoryCacheStore, SqliteCacheStore: typeof import('./cache-interceptor').default.SqliteCacheStore } + const install: typeof globalThisInstall } diff --git a/deps/undici/undici.js b/deps/undici/undici.js index 651a8cd6159569..39132374b7ac2e 100644 --- a/deps/undici/undici.js +++ b/deps/undici/undici.js @@ -5477,7 +5477,7 @@ var require_formdata = __commonJS({ __name(this, "FormData"); } #state = []; - constructor(form) { + constructor(form = void 0) { webidl.util.markAsUncloneable(this); if (form !== void 0) { throw webidl.errors.conversionFailed({ @@ -8782,7 +8782,6 @@ var require_proxy_agent = __commonJS({ "lib/dispatcher/proxy-agent.js"(exports2, module2) { "use strict"; var { kProxy, kClose, kDestroy, kDispatch } = require_symbols(); - var { URL: URL2 } = require("node:url"); var Agent = require_agent(); var Pool = require_pool(); var DispatcherBase = require_dispatcher_base(); @@ -8848,7 +8847,7 @@ var require_proxy_agent = __commonJS({ } = opts; opts.path = origin + path; if (!("host" in headers) && !("Host" in headers)) { - const { host } = new URL2(origin); + const { host } = new URL(origin); headers.host = host; } opts.headers = { ...this[kProxyHeaders], ...headers }; @@ -8866,7 +8865,7 @@ var require_proxy_agent = __commonJS({ __name(this, "ProxyAgent"); } constructor(opts) { - if (!opts || typeof opts === "object" && !(opts instanceof URL2) && !opts.uri) { + if (!opts || typeof opts === "object" && !(opts instanceof URL) && !opts.uri) { throw new InvalidArgumentError("Proxy uri is mandatory"); } const { clientFactory = defaultFactory } = opts; @@ -8895,7 +8894,7 @@ var require_proxy_agent = __commonJS({ this[kConnectEndpoint] = buildConnector({ ...opts.requestTls }); const agentFactory = opts.factory || defaultAgentFactory; const factory = /* @__PURE__ */ __name((origin2, options) => { - const { protocol: protocol2 } = new URL2(origin2); + const { protocol: protocol2 } = new URL(origin2); if (!this[kTunnelProxy] && protocol2 === "http:" && this[kProxy].protocol === "http:") { return new Http1ProxyWrapper(this[kProxy].uri, { headers: this[kProxyHeaders], @@ -8956,7 +8955,7 @@ var require_proxy_agent = __commonJS({ const headers = buildHeaders(opts.headers); throwIfProxyAuthIsSent(headers); if (headers && !("host" in headers) && !("Host" in headers)) { - const { host } = new URL2(opts.origin); + const { host } = new URL(opts.origin); headers.host = host; } return this[kAgent].dispatch( @@ -8968,16 +8967,16 @@ var require_proxy_agent = __commonJS({ ); } /** - * @param {import('../types/proxy-agent').ProxyAgent.Options | string | URL} opts + * @param {import('../../types/proxy-agent').ProxyAgent.Options | string | URL} opts * @returns {URL} */ #getUrl(opts) { if (typeof opts === "string") { - return new URL2(opts); - } else if (opts instanceof URL2) { + return new URL(opts); + } else if (opts instanceof URL) { return opts; } else { - return new URL2(opts.uri); + return new URL(opts.uri); } } async [kClose]() { @@ -9642,7 +9641,7 @@ var require_response = __commonJS({ var { URLSerializer } = require_data_url(); var { kConstruct } = require_symbols(); var assert = require("node:assert"); - var { types } = require("node:util"); + var { isArrayBuffer } = nodeUtil.types; var textEncoder = new TextEncoder("utf-8"); var Response = class _Response { static { @@ -9769,6 +9768,9 @@ var require_response = __commonJS({ }); } const clonedResponse = cloneResponse(this.#state); + if (this.#state.body?.stream) { + streamRegistry.register(this, new WeakRef(this.#state.body.stream)); + } return fromInnerResponse(clonedResponse, getHeadersGuard(this.#headers)); } [nodeUtil.inspect.custom](depth, options) { @@ -9853,7 +9855,6 @@ var require_response = __commonJS({ const newResponse = makeResponse({ ...response, body: null }); if (response.body != null) { newResponse.body = cloneBody(response.body); - streamRegistry.register(newResponse, new WeakRef(response.body.stream)); } return newResponse; } @@ -9999,7 +10000,7 @@ var require_response = __commonJS({ if (webidl.is.Blob(V)) { return V; } - if (ArrayBuffer.isView(V) || types.isArrayBuffer(V)) { + if (ArrayBuffer.isView(V) || isArrayBuffer(V)) { return V; } if (webidl.is.FormData(V)) { @@ -11632,7 +11633,7 @@ var require_fetch = __commonJS({ fetchParams.controller.terminate(e); } }, "processBodyError"); - requestBody = async function* () { + requestBody = (async function* () { try { for await (const bytes of request.body.stream) { yield* processBodyChunk(bytes); @@ -11641,7 +11642,7 @@ var require_fetch = __commonJS({ } catch (err) { processBodyError(err); } - }(); + })(); } try { const { body, status, statusText, headersList, socket } = await dispatch({ body: requestBody }); @@ -13147,6 +13148,7 @@ var require_sender = __commonJS({ var require_websocket = __commonJS({ "lib/web/websocket/websocket.js"(exports2, module2) { "use strict"; + var { isArrayBuffer } = require("node:util/types"); var { webidl } = require_webidl(); var { URLSerializer } = require_data_url(); var { environmentSettingsObject } = require_util2(); @@ -13166,7 +13168,6 @@ var require_websocket = __commonJS({ var { ByteParser } = require_receiver(); var { kEnumerableProperty } = require_util(); var { getGlobalDispatcher: getGlobalDispatcher2 } = require_global2(); - var { types } = require("node:util"); var { ErrorEvent: ErrorEvent2, CloseEvent: CloseEvent2, createFastMessageEvent: createFastMessageEvent2 } = require_events(); var { SendQueue } = require_sender(); var { WebsocketFrameSend } = require_frame(); @@ -13306,7 +13307,7 @@ var require_websocket = __commonJS({ this.#sendQueue.add(buffer, () => { this.#bufferedAmount -= buffer.byteLength; }, sendHints.text); - } else if (types.isArrayBuffer(data)) { + } else if (isArrayBuffer(data)) { this.#bufferedAmount += data.byteLength; this.#sendQueue.add(data, () => { this.#bufferedAmount -= data.byteLength; @@ -13440,11 +13441,17 @@ var require_websocket = __commonJS({ } fireEvent("open", this); if (channels.open.hasSubscribers) { + const headers = response.headersList.entries; channels.open.publish({ address: response.socket.address(), protocol: this.#protocol, extensions: this.#extensions, - websocket: this + websocket: this, + handshakeResponse: { + status: response.status, + statusText: response.statusText, + headers + } }); } } @@ -13611,7 +13618,7 @@ var require_websocket = __commonJS({ if (webidl.is.Blob(V)) { return V; } - if (ArrayBuffer.isView(V) || types.isArrayBuffer(V)) { + if (ArrayBuffer.isView(V) || isArrayBuffer(V)) { return V; } } @@ -13951,10 +13958,10 @@ var require_eventsource = __commonJS({ } url = webidl.converters.USVString(url); eventSourceInitDict = webidl.converters.EventSourceInitDict(eventSourceInitDict, prefix, "eventSourceInitDict"); - this.#dispatcher = eventSourceInitDict.dispatcher; + this.#dispatcher = eventSourceInitDict.node.dispatcher || eventSourceInitDict.dispatcher; this.#state = { lastEventId: "", - reconnectionTime: defaultReconnectionTime + reconnectionTime: eventSourceInitDict.node.reconnectionTime }; const settings = environmentSettingsObject; let urlRecord; @@ -14180,6 +14187,22 @@ var require_eventsource = __commonJS({ key: "dispatcher", // undici only converter: webidl.converters.any + }, + { + key: "node", + // undici only + converter: webidl.dictionaryConverter([ + { + key: "reconnectionTime", + converter: webidl.converters["unsigned long"], + defaultValue: /* @__PURE__ */ __name(() => defaultReconnectionTime, "defaultValue") + }, + { + key: "dispatcher", + converter: webidl.converters.any + } + ]), + defaultValue: /* @__PURE__ */ __name(() => ({}), "defaultValue") } ]); module2.exports = { diff --git a/deps/v8/src/heap/base/asm/s390/push_registers_asm.cc b/deps/v8/src/heap/base/asm/s390/push_registers_asm.cc index ef954fa03ae8f2..80b6cf0664d627 100644 --- a/deps/v8/src/heap/base/asm/s390/push_registers_asm.cc +++ b/deps/v8/src/heap/base/asm/s390/push_registers_asm.cc @@ -21,17 +21,17 @@ asm(".text \n" "PushAllRegistersAndIterateStack: \n" // Push all callee-saved registers. // r6-r13, r14 and sp(r15) - " stmg %r6, %sp, 48(%sp) \n" + " stmg %r6, %r15, 48(%r15) \n" // Allocate frame. - " lay %sp, -160(%sp) \n" + " lay %r15, -160(%r15) \n" // Pass 1st parameter (r2) unchanged (Stack*). // Pass 2nd parameter (r3) unchanged (StackVisitor*). // Save 3rd parameter (r4; IterateStackCallback). " lgr %r5, %r4 \n" // Pass sp as 3rd parameter. 160+48 to point // to callee saved region stored above. - " lay %r4, 208(%sp) \n" + " lay %r4, 208(%r15) \n" // Call the callback. " basr %r14, %r5 \n" - " lmg %r14,%sp, 272(%sp) \n" + " lmg %r14,%r15, 272(%r15) \n" " br %r14 \n"); diff --git a/deps/v8/src/wasm/baseline/ppc/liftoff-assembler-ppc-inl.h b/deps/v8/src/wasm/baseline/ppc/liftoff-assembler-ppc-inl.h index 7ab51af2be9e68..2256229f868c56 100644 --- a/deps/v8/src/wasm/baseline/ppc/liftoff-assembler-ppc-inl.h +++ b/deps/v8/src/wasm/baseline/ppc/liftoff-assembler-ppc-inl.h @@ -2893,14 +2893,38 @@ void LiftoffAssembler::CallC(const std::initializer_list args, parallel_move.LoadIntoRegister(LiftoffRegister{kCArgRegs[reg_args]}, arg); ++reg_args; } else { - int bias = 0; - // On BE machines values with less than 8 bytes are right justified. - // bias here is relative to the stack pointer. - if (arg.kind() == kI32 || arg.kind() == kF32) bias = -stack_bias; int offset = (kStackFrameExtraParamSlot + stack_args) * kSystemPointerSize; - MemOperand dst{sp, offset + bias}; - liftoff::StoreToMemory(this, dst, arg, r0, ip); + MemOperand dst{sp, offset}; + Register scratch1 = r0; + Register scratch2 = ip; + if (arg.is_reg()) { + switch (arg.kind()) { + case kI16: + extsh(scratch1, arg.reg().gp()); + StoreU64(scratch1, dst); + break; + case kI32: + extsw(scratch1, arg.reg().gp()); + StoreU64(scratch1, dst); + break; + case kI64: + StoreU64(arg.reg().gp(), dst); + break; + default: + UNREACHABLE(); + } + } else if (arg.is_const()) { + mov(scratch1, Operand(static_cast(arg.i32_const()))); + StoreU64(scratch1, dst); + } else if (value_kind_size(arg.kind()) == 4) { + LoadS32(scratch1, liftoff::GetStackSlot(arg.offset()), scratch2); + StoreU64(scratch1, dst); + } else { + DCHECK_EQ(8, value_kind_size(arg.kind())); + LoadU64(scratch1, liftoff::GetStackSlot(arg.offset()), scratch1); + StoreU64(scratch1, dst); + } ++stack_args; } } diff --git a/deps/v8/src/wasm/baseline/s390/liftoff-assembler-s390-inl.h b/deps/v8/src/wasm/baseline/s390/liftoff-assembler-s390-inl.h index bfd1eaf61e69f0..0be7b0e880a543 100644 --- a/deps/v8/src/wasm/baseline/s390/liftoff-assembler-s390-inl.h +++ b/deps/v8/src/wasm/baseline/s390/liftoff-assembler-s390-inl.h @@ -3271,14 +3271,37 @@ void LiftoffAssembler::CallC(const std::initializer_list args, parallel_move.LoadIntoRegister(LiftoffRegister{kCArgRegs[reg_args]}, arg); ++reg_args; } else { - int bias = 0; - // On BE machines values with less than 8 bytes are right justified. - // bias here is relative to the stack pointer. - if (arg.kind() == kI32 || arg.kind() == kF32) bias = -stack_bias; int offset = (kStackFrameExtraParamSlot + stack_args) * kSystemPointerSize; - MemOperand dst{sp, offset + bias}; - liftoff::StoreToMemory(this, dst, arg, ip); + MemOperand dst{sp, offset}; + Register scratch = ip; + if (arg.is_reg()) { + switch (arg.kind()) { + case kI16: + LoadS16(scratch, arg.reg().gp()); + StoreU64(scratch, dst); + break; + case kI32: + LoadS32(scratch, arg.reg().gp()); + StoreU64(scratch, dst); + break; + case kI64: + StoreU64(arg.reg().gp(), dst); + break; + default: + UNREACHABLE(); + } + } else if (arg.is_const()) { + mov(scratch, Operand(static_cast(arg.i32_const()))); + StoreU64(scratch, dst); + } else if (value_kind_size(arg.kind()) == 4) { + LoadS32(scratch, liftoff::GetStackSlot(arg.offset()), scratch); + StoreU64(scratch, dst); + } else { + DCHECK_EQ(8, value_kind_size(arg.kind())); + LoadU64(scratch, liftoff::GetStackSlot(arg.offset()), scratch); + StoreU64(scratch, dst); + } ++stack_args; } } diff --git a/doc/api/buffer.md b/doc/api/buffer.md index c6c90b0bd29cce..70e32a6c998216 100644 --- a/doc/api/buffer.md +++ b/doc/api/buffer.md @@ -405,7 +405,7 @@ implementations. Specifically, the {TypedArray} variants accept a second argument that is a mapping function that is invoked on every element of the typed array: -* `TypedArray.from(source[, mapFn[, thisArg]])` +* [`TypedArray.from(source[, mapFn[, thisArg]])`][`TypedArray.from()`] The `Buffer.from()` method, however, does not support the use of a mapping function: diff --git a/doc/api/cli.md b/doc/api/cli.md index ba1356650c2383..01735cafddf762 100644 --- a/doc/api/cli.md +++ b/doc/api/cli.md @@ -2601,6 +2601,20 @@ changes: The destination for the corresponding test reporter. See the documentation on [test reporters][] for more details. +### `--test-rerun-failures` + + + +A path to a file allowing the test runner to persist the state of the test +suite between runs. The test runner will use this file to determine which tests +have already succeeded or failed, allowing for re-running of failed tests +without having to re-run the entire test suite. The test runner will create this +file if it does not exist. +See the documentation on [test reruns][] for more details. + ### `--test-shard` + +> Stability: 1.2 - Release candidate + +* `algorithm` {string} Variant of Argon2, one of `"argon2d"`, `"argon2i"` or `"argon2id"`. +* `parameters` {Object} + * `message` {string|ArrayBuffer|Buffer|TypedArray|DataView} REQUIRED, this is the password for password + hashing applications of Argon2. + * `nonce` {string|ArrayBuffer|Buffer|TypedArray|DataView} REQUIRED, must be at + least 8 bytes long. This is the salt for password hashing applications of Argon2. + * `parallelism` {number} REQUIRED, degree of parallelism determines how many computational chains (lanes) + can be run. Must be greater than 1 and less than `2**24-1`. + * `tagLength` {number} REQUIRED, the length of the key to generate. Must be greater than 4 and + less than `2**32-1`. + * `memory` {number} REQUIRED, memory cost in 1KiB blocks. Must be greater than + `8 * parallelism` and less than `2**32-1`. The actual number of blocks is rounded + down to the nearest multiple of `4 * parallelism`. + * `passes` {number} REQUIRED, number of passes (iterations). Must be greater than 1 and less + than `2**32-1`. + * `secret` {string|ArrayBuffer|Buffer|TypedArray|DataView|undefined} OPTIONAL, Random additional input, + similar to the salt, that should **NOT** be stored with the derived key. This is known as pepper in + password hashing applications. If used, must have a length not greater than `2**32-1` bytes. + * `associatedData` {string|ArrayBuffer|Buffer|TypedArray|DataView|undefined} OPTIONAL, Additional data to + be added to the hash, functionally equivalent to salt or secret, but meant for + non-random data. If used, must have a length not greater than `2**32-1` bytes. +* `callback` {Function} + * `err` {Error} + * `derivedKey` {Buffer} + +Provides an asynchronous [Argon2][] implementation. Argon2 is a password-based +key derivation function that is designed to be expensive computationally and +memory-wise in order to make brute-force attacks unrewarding. + +The `nonce` should be as unique as possible. It is recommended that a nonce is +random and at least 16 bytes long. See [NIST SP 800-132][] for details. + +When passing strings for `message`, `nonce`, `secret` or `associatedData`, please +consider [caveats when using strings as inputs to cryptographic APIs][]. + +The `callback` function is called with two arguments: `err` and `derivedKey`. +`err` is an exception object when key derivation fails, otherwise `err` is +`null`. `derivedKey` is passed to the callback as a [`Buffer`][]. + +An exception is thrown when any of the input arguments specify invalid values +or types. + +```mjs +const { argon2, randomBytes } = await import('node:crypto'); + +const parameters = { + message: 'password', + nonce: randomBytes(16), + parallelism: 4, + tagLength: 64, + memory: 65536, + passes: 3, +}; + +argon2('argon2id', parameters, (err, derivedKey) => { + if (err) throw err; + console.log(derivedKey.toString('hex')); // 'af91dad...9520f15' +}); +``` + +```cjs +const { argon2, randomBytes } = require('node:crypto'); + +const parameters = { + message: 'password', + nonce: randomBytes(16), + parallelism: 4, + tagLength: 64, + memory: 65536, + passes: 3, +}; + +argon2('argon2id', parameters, (err, derivedKey) => { + if (err) throw err; + console.log(derivedKey.toString('hex')); // 'af91dad...9520f15' +}); +``` + +### `crypto.argon2Sync(algorithm, parameters)` + + + +> Stability: 1.2 - Release candidate + +* `algorithm` {string} Variant of Argon2, one of `"argon2d"`, `"argon2i"` or `"argon2id"`. +* `parameters` {Object} + * `message` {string|ArrayBuffer|Buffer|TypedArray|DataView} REQUIRED, this is the password for password + hashing applications of Argon2. + * `nonce` {string|ArrayBuffer|Buffer|TypedArray|DataView} REQUIRED, must be at + least 8 bytes long. This is the salt for password hashing applications of Argon2. + * `parallelism` {number} REQUIRED, degree of parallelism determines how many computational chains (lanes) + can be run. Must be greater than 1 and less than `2**24-1`. + * `tagLength` {number} REQUIRED, the length of the key to generate. Must be greater than 4 and + less than `2**32-1`. + * `memory` {number} REQUIRED, memory cost in 1KiB blocks. Must be greater than + `8 * parallelism` and less than `2**32-1`. The actual number of blocks is rounded + down to the nearest multiple of `4 * parallelism`. + * `passes` {number} REQUIRED, number of passes (iterations). Must be greater than 1 and less + than `2**32-1`. + * `secret` {string|ArrayBuffer|Buffer|TypedArray|DataView|undefined} OPTIONAL, Random additional input, + similar to the salt, that should **NOT** be stored with the derived key. This is known as pepper in + password hashing applications. If used, must have a length not greater than `2**32-1` bytes. + * `associatedData` {string|ArrayBuffer|Buffer|TypedArray|DataView|undefined} OPTIONAL, Additional data to + be added to the hash, functionally equivalent to salt or secret, but meant for + non-random data. If used, must have a length not greater than `2**32-1` bytes. +* Returns: {Buffer} + +Provides a synchronous [Argon2][] implementation. Argon2 is a password-based +key derivation function that is designed to be expensive computationally and +memory-wise in order to make brute-force attacks unrewarding. + +The `nonce` should be as unique as possible. It is recommended that a nonce is +random and at least 16 bytes long. See [NIST SP 800-132][] for details. + +When passing strings for `message`, `nonce`, `secret` or `associatedData`, please +consider [caveats when using strings as inputs to cryptographic APIs][]. + +An exception is thrown when key derivation fails, otherwise the derived key is +returned as a [`Buffer`][]. + +An exception is thrown when any of the input arguments specify invalid values +or types. + +```mjs +const { argon2Sync, randomBytes } = await import('node:crypto'); + +const parameters = { + message: 'password', + nonce: randomBytes(16), + parallelism: 4, + tagLength: 64, + memory: 65536, + passes: 3, +}; + +const derivedKey = argon2Sync('argon2id', parameters); +console.log(derivedKey.toString('hex')); // 'af91dad...9520f15' +``` + +```cjs +const { argon2Sync, randomBytes } = require('node:crypto'); + +const parameters = { + message: 'password', + nonce: randomBytes(16), + parallelism: 4, + tagLength: 64, + memory: 65536, + passes: 3, +}; + +const derivedKey = argon2Sync('argon2id', parameters); +console.log(derivedKey.toString('hex')); // 'af91dad...9520f15' +``` + ### `crypto.checkPrime(candidate[, options], callback)` + +> Stability: 1.2 - Release candidate + +* `key` {Object|string|ArrayBuffer|Buffer|TypedArray|DataView|KeyObject} Private Key +* `ciphertext` {ArrayBuffer|Buffer|TypedArray|DataView} +* `callback` {Function} + * `err` {Error} + * `sharedKey` {Buffer} +* Returns: {Buffer} if the `callback` function is not provided. + + + +Key decapsulation using a KEM algorithm with a private key. + +Supported key types and their KEM algorithms are: + +* `'rsa'`[^openssl30] RSA Secret Value Encapsulation +* `'ec'`[^openssl32] DHKEM(P-256, HKDF-SHA256), DHKEM(P-384, HKDF-SHA256), DHKEM(P-521, HKDF-SHA256) +* `'x25519'`[^openssl32] DHKEM(X25519, HKDF-SHA256) +* `'x448'`[^openssl32] DHKEM(X448, HKDF-SHA512) +* `'ml-kem-512'`[^openssl35] ML-KEM +* `'ml-kem-768'`[^openssl35] ML-KEM +* `'ml-kem-1024'`[^openssl35] ML-KEM + +If `key` is not a [`KeyObject`][], this function behaves as if `key` had been +passed to [`crypto.createPrivateKey()`][]. + +If the `callback` function is provided this function uses libuv's threadpool. + ### `crypto.diffieHellman(options[, callback])` + +> Stability: 1.2 - Release candidate + +* `key` {Object|string|ArrayBuffer|Buffer|TypedArray|DataView|KeyObject} Public Key +* `callback` {Function} + * `err` {Error} + * `result` {Object} + * `sharedKey` {Buffer} + * `ciphertext` {Buffer} +* Returns: {Object} if the `callback` function is not provided. + * `sharedKey` {Buffer} + * `ciphertext` {Buffer} + + + +Key encapsulation using a KEM algorithm with a public key. + +Supported key types and their KEM algorithms are: + +* `'rsa'`[^openssl30] RSA Secret Value Encapsulation +* `'ec'`[^openssl32] DHKEM(P-256, HKDF-SHA256), DHKEM(P-384, HKDF-SHA256), DHKEM(P-521, HKDF-SHA256) +* `'x25519'`[^openssl32] DHKEM(X25519, HKDF-SHA256) +* `'x448'`[^openssl32] DHKEM(X448, HKDF-SHA512) +* `'ml-kem-512'`[^openssl35] ML-KEM +* `'ml-kem-768'`[^openssl35] ML-KEM +* `'ml-kem-1024'`[^openssl35] ML-KEM + +If `key` is not a [`KeyObject`][], this function behaves as if `key` had been +passed to [`crypto.createPublicKey()`][]. If the `callback` function is provided this function uses libuv's threadpool. @@ -3663,6 +3911,9 @@ underlying hash function. See [`crypto.createHmac()`][] for more information. -* `type` {string} Must be `'rsa'`, `'rsa-pss'`, `'dsa'`, `'ec'`, `'ed25519'`, - `'ed448'`, `'x25519'`, `'x448'`, `'dh'`, `'ml-dsa-44'`[^openssl35], - `'ml-dsa-65'`[^openssl35], or `'ml-dsa-87'`[^openssl35]. +* `type` {string} The asymmetric key type to generate. See the + supported [asymmetric key types][]. * `options` {Object} * `modulusLength` {number} Key size in bits (RSA, DSA). * `publicExponent` {number} Public exponent (RSA). **Default:** `0x10001`. @@ -3786,6 +4036,9 @@ a `Promise` for an `Object` with `publicKey` and `privateKey` properties. -* `type` {string} Must be `'rsa'`, `'rsa-pss'`, `'dsa'`, `'ec'`, `'ed25519'`, - `'ed448'`, `'x25519'`, `'x448'`, `'dh'`, `'ml-dsa-44'`[^openssl35], - `'ml-dsa-65'`[^openssl35], or `'ml-dsa-87'`[^openssl35]. +* `type` {string} The asymmetric key type to generate. See the + supported [asymmetric key types][]. * `options` {Object} * `modulusLength` {number} Key size in bits (RSA, DSA). * `publicExponent` {number} Public exponent (RSA). **Default:** `0x10001`. @@ -6185,6 +6437,10 @@ See the [list of SSL OP Flags][] for details. +[^openssl30]: Requires OpenSSL >= 3.0 + +[^openssl32]: Requires OpenSSL >= 3.2 + [^openssl35]: Requires OpenSSL >= 3.5 [AEAD algorithms]: https://en.wikipedia.org/wiki/Authenticated_encryption @@ -6268,6 +6524,8 @@ See the [list of SSL OP Flags][] for details. [`verify.verify()`]: #verifyverifyobject-signature-signatureencoding [`x509.fingerprint256`]: #x509fingerprint256 [`x509.verify(publicKey)`]: #x509verifypublickey +[argon2]: https://www.rfc-editor.org/rfc/rfc9106.html +[asymmetric key types]: #asymmetric-key-types [caveats when using strings as inputs to cryptographic APIs]: #using-strings-as-inputs-to-cryptographic-apis [certificate object]: tls.md#certificate-object [encoding]: buffer.md#buffers-and-character-encodings diff --git a/doc/api/environment_variables.md b/doc/api/environment_variables.md index 4878eeaa9e6dcd..747d8313c4b7fa 100644 --- a/doc/api/environment_variables.md +++ b/doc/api/environment_variables.md @@ -96,10 +96,10 @@ For example: MY_VAR_B = ' my variable b ' ``` -will be treaded identically to: +will be treated identically to: ```text -MY_VAR_A = my variable +MY_VAR_A = my variable a MY_VAR_B = ' my variable b ' ``` diff --git a/doc/api/errors.md b/doc/api/errors.md index 17d7c6edcec2ab..86527156e57355 100644 --- a/doc/api/errors.md +++ b/doc/api/errors.md @@ -826,6 +826,12 @@ when an error occurs (and is caught) during the creation of the context, for example, when the allocation fails or the maximum call stack size is reached when the context is created. + + +### `ERR_CRYPTO_ARGON2_NOT_SUPPORTED` + +Argon2 is not supported by the current version of OpenSSL being used. + ### `ERR_CRYPTO_CUSTOM_ENGINE_NOT_SUPPORTED` @@ -1059,6 +1065,17 @@ Key's Elliptic Curve is not registered for use in the Key's Asymmetric Key Type is not registered for use in the [JSON Web Key Types Registry][]. + + +### `ERR_CRYPTO_KEM_NOT_SUPPORTED` + + + +Attempted to use KEM operations while Node.js was not compiled with +OpenSSL with KEM support. + ### `ERR_CRYPTO_OPERATION_FAILED` diff --git a/doc/api/fs.md b/doc/api/fs.md index 57ce4c0bae3a10..abde34c68dbe96 100644 --- a/doc/api/fs.md +++ b/doc/api/fs.md @@ -1108,6 +1108,9 @@ changes: * `exclude` {Function|string\[]} Function to filter out files/directories or a list of glob patterns to be excluded. If a function is provided, return `true` to exclude the item, `false` to include it. **Default:** `undefined`. + If a string array is provided, each string should be a glob pattern that + specifies paths to exclude. Note: Negation patterns (e.g., '!foo.js') are + not supported. * `withFileTypes` {boolean} `true` if the glob should return paths as Dirents, `false` otherwise. **Default:** `false`. * Returns: {AsyncIterator} An AsyncIterator that yields the paths of files diff --git a/doc/api/globals.md b/doc/api/globals.md index 7d272e127cecfa..fa612bb8d7d74f 100644 --- a/doc/api/globals.md +++ b/doc/api/globals.md @@ -330,6 +330,9 @@ with the [`--no-experimental-websocket`][] CLI flag. * `headers` {HTTP/2 Headers Object|Array} @@ -1850,6 +1855,10 @@ and will throw an error. -* `headers` {HTTP/2 Headers Object} +* `headers` {HTTP/2 Headers Object|Array} * `options` {Object} * `endStream` {boolean} Set to `true` to indicate that the response will not include payload data. diff --git a/doc/api/inspector.md b/doc/api/inspector.md index 0ab07b995749d1..ea8a9434c14a1c 100644 --- a/doc/api/inspector.md +++ b/doc/api/inspector.md @@ -598,6 +598,48 @@ This feature is only available with the `--experimental-network-inspection` flag Broadcasts the `Network.loadingFailed` event to connected frontends. This event indicates that HTTP request has failed to load. +### `inspector.Network.webSocketCreated([params])` + + + +* `params` {Object} + +This feature is only available with the `--experimental-network-inspection` flag enabled. + +Broadcasts the `Network.webSocketCreated` event to connected frontends. This event indicates that +a WebSocket connection has been initiated. + +### `inspector.Network.webSocketHandshakeResponseReceived([params])` + + + +* `params` {Object} + +This feature is only available with the `--experimental-network-inspection` flag enabled. + +Broadcasts the `Network.webSocketHandshakeResponseReceived` event to connected frontends. +This event indicates that the WebSocket handshake response has been received. + +### `inspector.Network.webSocketClosed([params])` + + + +* `params` {Object} + +This feature is only available with the `--experimental-network-inspection` flag enabled. + +Broadcasts the `Network.webSocketClosed` event to connected frontends. +This event indicates that a WebSocket connection has been closed. + ### `inspector.NetworkResources.put` * Type: {string} The default value of the `ciphers` option of diff --git a/doc/api/webcrypto.md b/doc/api/webcrypto.md index 0e1376c5d3eea2..4e6594bfbe4db5 100644 --- a/doc/api/webcrypto.md +++ b/doc/api/webcrypto.md @@ -2,6 +2,24 @@ +### Static method: `SubtleCrypto.supports(operation, algorithm[, lengthOrAdditionalAlgorithm])` + + + +> Stability: 1.1 - Active development + + + +* `operation` {string} "encrypt", "decrypt", "sign", "verify", "digest", "generateKey", "deriveKey", "deriveBits", "importKey", "exportKey", "getPublicKey", "wrapKey", or "unwrapKey" +* `algorithm` {string|Algorithm} +* `lengthOrAdditionalAlgorithm` {null|number|string|Algorithm|undefined} Depending on the operation this is either ignored, the value of the length argument when operation is "deriveBits", the algorithm of key to be derived when operation is "deriveKey", the algorithm of key to be exported before wrapping when operation is "wrapKey", or the algorithm of key to be imported after unwrapping when operation is "unwrapKey". **Default:** `null` when operation is "deriveBits", `undefined` otherwise. +* Returns: {boolean} Indicating whether the implementation supports the given operation + + + +Allows feature detection in Web Crypto API, +which can be used to detect whether a given algorithm identifier +(including its parameters) is supported for the given operation. + +### `subtle.decapsulateBits(decapsulationAlgorithm, decapsulationKey, ciphertext)` + + + +> Stability: 1.1 - Active development + +* `decapsulationAlgorithm` {string|Algorithm} +* `decapsulationKey` {CryptoKey} +* `ciphertext` {ArrayBuffer|TypedArray|DataView|Buffer} +* Returns: {Promise} Fulfills with {ArrayBuffer} upon success. + +The algorithms currently supported include: + +* `'ML-KEM-512'`[^modern-algos] +* `'ML-KEM-768'`[^modern-algos] +* `'ML-KEM-1024'`[^modern-algos] + +### `subtle.decapsulateKey(decapsulationAlgorithm, decapsulationKey, ciphertext, sharedKeyAlgorithm, extractable, usages)` + + + +> Stability: 1.1 - Active development + +* `decapsulationAlgorithm` {string|Algorithm} +* `decapsulationKey` {CryptoKey} +* `ciphertext` {ArrayBuffer|TypedArray|DataView|Buffer} +* `sharedKeyAlgorithm` {string|Algorithm|HmacImportParams|AesDerivedKeyParams} +* `extractable` {boolean} +* `usages` {string\[]} See [Key usages][]. +* Returns: {Promise} Fulfills with {CryptoKey} upon success. + +The algorithms currently supported include: + +* `'ML-KEM-512'`[^modern-algos] +* `'ML-KEM-768'`[^modern-algos] +* `'ML-KEM-1024'`[^modern-algos] + ### `subtle.decrypt(algorithm, key, data)` -* `algorithm` {RsaOaepParams|AesCtrParams|AesCbcParams|AesGcmParams} +* `algorithm` {RsaOaepParams|AesCtrParams|AesCbcParams|AeadParams} * `key` {CryptoKey} * `data` {ArrayBuffer|TypedArray|DataView|Buffer} * Returns: {Promise} Fulfills with an {ArrayBuffer} upon success. @@ -567,10 +851,12 @@ an {ArrayBuffer} containing the plaintext result. The algorithms currently supported include: -* `'RSA-OAEP'` -* `'AES-CTR'` * `'AES-CBC'` +* `'AES-CTR'` * `'AES-GCM'` +* `'AES-OCB'`[^modern-algos] +* `'ChaCha20-Poly1305'`[^modern-algos] +* `'RSA-OAEP'` ### `subtle.deriveBits(algorithm, baseKey[, length])` @@ -605,7 +891,7 @@ material provided by `baseKey`, `subtle.deriveBits()` attempts to generate `length` bits. When `length` is not provided or `null` the maximum number of bits for a given -algorithm is generated. This is allowed for the `'ECDH'`, `'X25519'`, and `'X448'` +algorithm is generated. This is allowed for the `'ECDH'`, `'X25519'`, and `'X448'`[^secure-curves] algorithms, for other algorithms `length` is required to be a number. If successful, the returned promise will be resolved with an {ArrayBuffer} @@ -614,10 +900,10 @@ containing the generated data. The algorithms currently supported include: * `'ECDH'` -* `'X25519'` -* `'X448'` [^1] * `'HKDF'` * `'PBKDF2'` +* `'X25519'` +* `'X448'`[^secure-curves] ### `subtle.deriveKey(algorithm, baseKey, derivedKeyAlgorithm, extractable, keyUsages)` @@ -654,18 +940,25 @@ generate raw keying material, then passing the result into the The algorithms currently supported include: * `'ECDH'` -* `'X25519'` -* `'X448'` [^1] * `'HKDF'` * `'PBKDF2'` +* `'X25519'` +* `'X448'`[^secure-curves] ### `subtle.digest(algorithm, data)` -* `algorithm` {string|Algorithm} +* `algorithm` {string|Algorithm|CShakeParams} * `data` {ArrayBuffer|TypedArray|DataView|Buffer} * Returns: {Promise} Fulfills with an {ArrayBuffer} upon success. @@ -675,21 +968,72 @@ with an {ArrayBuffer} containing the computed digest. If `algorithm` is provided as a {string}, it must be one of: +* `'cSHAKE128'`[^modern-algos] +* `'cSHAKE256'`[^modern-algos] * `'SHA-1'` * `'SHA-256'` * `'SHA-384'` * `'SHA-512'` +* `'SHA3-256'`[^modern-algos] +* `'SHA3-384'`[^modern-algos] +* `'SHA3-512'`[^modern-algos] If `algorithm` is provided as an {Object}, it must have a `name` property whose value is one of the above. +### `subtle.encapsulateBits(encapsulationAlgorithm, encapsulationKey)` + + + +> Stability: 1.1 - Active development + +* `encapsulationAlgorithm` {string|Algorithm} +* `encapsulationKey` {CryptoKey} +* Returns: {Promise} Fulfills with {EncapsulatedBits} upon success. + +The algorithms currently supported include: + +* `'ML-KEM-512'`[^modern-algos] +* `'ML-KEM-768'`[^modern-algos] +* `'ML-KEM-1024'`[^modern-algos] + +### `subtle.encapsulateKey(encapsulationAlgorithm, encapsulationKey, sharedKeyAlgorithm, extractable, usages)` + + + +> Stability: 1.1 - Active development + +* `encapsulationAlgorithm` {string|Algorithm} +* `encapsulationKey` {CryptoKey} +* `sharedKeyAlgorithm` {string|Algorithm|HmacImportParams|AesDerivedKeyParams} +* `extractable` {boolean} +* `usages` {string\[]} See [Key usages][]. +* Returns: {Promise} Fulfills with {EncapsulatedKey} upon success. + +The algorithms currently supported include: + +* `'ML-KEM-512'`[^modern-algos] +* `'ML-KEM-768'`[^modern-algos] +* `'ML-KEM-1024'`[^modern-algos] + ### `subtle.encrypt(algorithm, key, data)` -* `algorithm` {RsaOaepParams|AesCtrParams|AesCbcParams|AesGcmParams} +* `algorithm` {RsaOaepParams|AesCtrParams|AesCbcParams|AeadParams} * `key` {CryptoKey} * `data` {ArrayBuffer|TypedArray|DataView|Buffer} * Returns: {Promise} Fulfills with an {ArrayBuffer} upon success. @@ -701,16 +1045,27 @@ containing the encrypted result. The algorithms currently supported include: -* `'RSA-OAEP'` -* `'AES-CTR'` * `'AES-CBC'` +* `'AES-CTR'` * `'AES-GCM'` +* `'AES-OCB'`[^modern-algos] +* `'ChaCha20-Poly1305'`[^modern-algos] +* `'RSA-OAEP'` ### `subtle.exportKey(format, key)` -* `format` {string} Must be one of `'raw'`, `'pkcs8'`, `'spki'`, or `'jwk'`. +* `format` {string} Must be one of `'raw'`, `'pkcs8'`, `'spki'`, `'jwk'`, `'raw-secret'`[^modern-algos], + `'raw-public'`[^modern-algos], or `'raw-seed'`[^modern-algos]. * `key` {CryptoKey} * Returns: {Promise} Fulfills with an {ArrayBuffer|Object} upon success. @@ -738,25 +1094,57 @@ When `format` is `'jwk'` and the export is successful, the returned promise will be resolved with a JavaScript object conforming to the [JSON Web Key][] specification. -| Supported Key Algorithm | `'spki'` | `'pkcs8'` | `'jwk'` | `'raw'` | -| ------------------------------------------------------- | -------- | --------- | ------- | ------- | -| `'AES-CBC'` | | | ✔ | ✔ | -| `'AES-CTR'` | | | ✔ | ✔ | -| `'AES-GCM'` | | | ✔ | ✔ | -| `'AES-KW'` | | | ✔ | ✔ | -| `'ECDH'` | ✔ | ✔ | ✔ | ✔ | -| `'ECDSA'` | ✔ | ✔ | ✔ | ✔ | -| `'Ed25519'` | ✔ | ✔ | ✔ | ✔ | -| `'Ed448'` [^1] | ✔ | ✔ | ✔ | ✔ | -| `'HMAC'` | | | ✔ | ✔ | -| `'RSA-OAEP'` | ✔ | ✔ | ✔ | | -| `'RSA-PSS'` | ✔ | ✔ | ✔ | | -| `'RSASSA-PKCS1-v1_5'` | ✔ | ✔ | ✔ | | +| Supported Key Algorithm | `'spki'` | `'pkcs8'` | `'jwk'` | `'raw'` | `'raw-secret'` | `'raw-public'` | `'raw-seed'` | +| ------------------------------------ | -------- | --------- | ------- | ------- | -------------- | -------------- | ------------ | +| `'AES-CBC'` | | | ✔ | ✔ | ✔ | | | +| `'AES-CTR'` | | | ✔ | ✔ | ✔ | | | +| `'AES-GCM'` | | | ✔ | ✔ | ✔ | | | +| `'AES-KW'` | | | ✔ | ✔ | ✔ | | | +| `'AES-OCB'`[^modern-algos] | | | ✔ | | ✔ | | | +| `'ChaCha20-Poly1305'`[^modern-algos] | | | ✔ | | ✔ | | | +| `'ECDH'` | ✔ | ✔ | ✔ | ✔ | | ✔ | | +| `'ECDSA'` | ✔ | ✔ | ✔ | ✔ | | ✔ | | +| `'Ed25519'` | ✔ | ✔ | ✔ | ✔ | | ✔ | | +| `'Ed448'`[^secure-curves] | ✔ | ✔ | ✔ | ✔ | | ✔ | | +| `'HMAC'` | | | ✔ | ✔ | ✔ | | | +| `'ML-DSA-44'`[^modern-algos] | ✔ | ✔ | ✔ | | | ✔ | ✔ | +| `'ML-DSA-65'`[^modern-algos] | ✔ | ✔ | ✔ | | | ✔ | ✔ | +| `'ML-DSA-87'`[^modern-algos] | ✔ | ✔ | ✔ | | | ✔ | ✔ | +| `'ML-KEM-512'`[^modern-algos] | ✔ | ✔ | | | | ✔ | ✔ | +| `'ML-KEM-768'`[^modern-algos] | ✔ | ✔ | | | | ✔ | ✔ | +| `'ML-KEM-1024'`[^modern-algos] | ✔ | ✔ | | | | ✔ | ✔ | +| `'RSA-OAEP'` | ✔ | ✔ | ✔ | | | | | +| `'RSA-PSS'` | ✔ | ✔ | ✔ | | | | | +| `'RSASSA-PKCS1-v1_5'` | ✔ | ✔ | ✔ | | | | | + +### `subtle.getPublicKey(key, keyUsages)` + + + +> Stability: 1.1 - Active development + +* `key` {CryptoKey} A private key from which to derive the corresponding public key. +* `keyUsages` {string\[]} See [Key usages][]. +* Returns: {Promise} Fulfills with a {CryptoKey} upon success. + +Derives the public key from a given private key. ### `subtle.generateKey(algorithm, extractable, keyUsages)` @@ -776,29 +1164,46 @@ may generate either a single {CryptoKey} or a {CryptoKeyPair}. The {CryptoKeyPair} (public and private key) generating algorithms supported include: -* `'RSASSA-PKCS1-v1_5'` -* `'RSA-PSS'` -* `'RSA-OAEP'` +* `'ECDH'` * `'ECDSA'` * `'Ed25519'` -* `'Ed448'` [^1] -* `'ECDH'` +* `'Ed448'`[^secure-curves] +* `'ML-DSA-44'`[^modern-algos] +* `'ML-DSA-65'`[^modern-algos] +* `'ML-DSA-87'`[^modern-algos] +* `'ML-KEM-512'`[^modern-algos] +* `'ML-KEM-768'`[^modern-algos] +* `'ML-KEM-1024'`[^modern-algos] +* `'RSA-OAEP'` +* `'RSA-PSS'` +* `'RSASSA-PKCS1-v1_5'` * `'X25519'` -* `'X448'` [^1] +* `'X448'`[^secure-curves] The {CryptoKey} (secret key) generating algorithms supported include: -* `'HMAC'` -* `'AES-CTR'` * `'AES-CBC'` +* `'AES-CTR'` * `'AES-GCM'` * `'AES-KW'` +* `'AES-OCB'`[^modern-algos] +* `'ChaCha20-Poly1305'`[^modern-algos] +* `'HMAC'` ### `subtle.importKey(format, keyData, algorithm, extractable, keyUsages)` -* `format` {string} Must be one of `'raw'`, `'pkcs8'`, `'spki'`, or `'jwk'`. +* `format` {string} Must be one of `'raw'`, `'pkcs8'`, `'spki'`, `'jwk'`, `'raw-secret'`[^modern-algos], + `'raw-public'`[^modern-algos], or `'raw-seed'`[^modern-algos]. * `keyData` {ArrayBuffer|TypedArray|DataView|Buffer|Object} @@ -832,30 +1238,41 @@ If importing a `'PBKDF2'` key, `extractable` must be `false`. The algorithms currently supported include: -| Supported Key Algorithm | `'spki'` | `'pkcs8'` | `'jwk'` | `'raw'` | -| ------------------------------------------------------- | -------- | --------- | ------- | ------- | -| `'AES-CBC'` | | | ✔ | ✔ | -| `'AES-CTR'` | | | ✔ | ✔ | -| `'AES-GCM'` | | | ✔ | ✔ | -| `'AES-KW'` | | | ✔ | ✔ | -| `'ECDH'` | ✔ | ✔ | ✔ | ✔ | -| `'X25519'` | ✔ | ✔ | ✔ | ✔ | -| `'X448'` [^1] | ✔ | ✔ | ✔ | ✔ | -| `'ECDSA'` | ✔ | ✔ | ✔ | ✔ | -| `'Ed25519'` | ✔ | ✔ | ✔ | ✔ | -| `'Ed448'` [^1] | ✔ | ✔ | ✔ | ✔ | -| `'HDKF'` | | | | ✔ | -| `'HMAC'` | | | ✔ | ✔ | -| `'PBKDF2'` | | | | ✔ | -| `'RSA-OAEP'` | ✔ | ✔ | ✔ | | -| `'RSA-PSS'` | ✔ | ✔ | ✔ | | -| `'RSASSA-PKCS1-v1_5'` | ✔ | ✔ | ✔ | | +| Supported Key Algorithm | `'spki'` | `'pkcs8'` | `'jwk'` | `'raw'` | `'raw-secret'` | `'raw-public'` | `'raw-seed'` | +| ------------------------------------ | -------- | --------- | ------- | ------- | -------------- | -------------- | ------------ | +| `'AES-CBC'` | | | ✔ | ✔ | ✔ | | | +| `'AES-CTR'` | | | ✔ | ✔ | ✔ | | | +| `'AES-GCM'` | | | ✔ | ✔ | ✔ | | | +| `'AES-KW'` | | | ✔ | ✔ | ✔ | | | +| `'AES-OCB'`[^modern-algos] | | | ✔ | | ✔ | | | +| `'ChaCha20-Poly1305'`[^modern-algos] | | | ✔ | | ✔ | | | +| `'ECDH'` | ✔ | ✔ | ✔ | ✔ | | ✔ | | +| `'ECDSA'` | ✔ | ✔ | ✔ | ✔ | | ✔ | | +| `'Ed25519'` | ✔ | ✔ | ✔ | ✔ | | ✔ | | +| `'Ed448'`[^secure-curves] | ✔ | ✔ | ✔ | ✔ | | ✔ | | +| `'HDKF'` | | | | ✔ | ✔ | | | +| `'HMAC'` | | | ✔ | ✔ | ✔ | | | +| `'ML-DSA-44'`[^modern-algos] | ✔ | ✔ | ✔ | | | ✔ | ✔ | +| `'ML-DSA-65'`[^modern-algos] | ✔ | ✔ | ✔ | | | ✔ | ✔ | +| `'ML-DSA-87'`[^modern-algos] | ✔ | ✔ | ✔ | | | ✔ | ✔ | +| `'ML-KEM-512'`[^modern-algos] | ✔ | ✔ | | | | ✔ | ✔ | +| `'ML-KEM-768'`[^modern-algos] | ✔ | ✔ | | | | ✔ | ✔ | +| `'ML-KEM-1024'`[^modern-algos] | ✔ | ✔ | | | | ✔ | ✔ | +| `'PBKDF2'` | | | | ✔ | ✔ | | | +| `'RSA-OAEP'` | ✔ | ✔ | ✔ | | | | | +| `'RSA-PSS'` | ✔ | ✔ | ✔ | | | | | +| `'RSASSA-PKCS1-v1_5'` | ✔ | ✔ | ✔ | | | | | +| `'X25519'` | ✔ | ✔ | ✔ | ✔ | | ✔ | | +| `'X448'`[^secure-curves] | ✔ | ✔ | ✔ | ✔ | | ✔ | | ### `subtle.sign(algorithm, key, data)` -* `algorithm` {string|Algorithm|RsaPssParams|EcdsaParams|Ed448Params} +* `algorithm` {string|Algorithm|RsaPssParams|EcdsaParams|Ed448Params|ContextParams} * `key` {CryptoKey} * `data` {ArrayBuffer|TypedArray|DataView|Buffer} * Returns: {Promise} Fulfills with an {ArrayBuffer} upon success. @@ -879,26 +1296,37 @@ an {ArrayBuffer} containing the generated signature. The algorithms currently supported include: -* `'RSASSA-PKCS1-v1_5'` -* `'RSA-PSS'` * `'ECDSA'` * `'Ed25519'` -* `'Ed448'` [^1] +* `'Ed448'`[^secure-curves] * `'HMAC'` +* `'ML-DSA-44'`[^modern-algos] +* `'ML-DSA-65'`[^modern-algos] +* `'ML-DSA-87'`[^modern-algos] +* `'RSA-PSS'` +* `'RSASSA-PKCS1-v1_5'` ### `subtle.unwrapKey(format, wrappedKey, unwrappingKey, unwrapAlgo, unwrappedKeyAlgo, extractable, keyUsages)` -* `format` {string} Must be one of `'raw'`, `'pkcs8'`, `'spki'`, or `'jwk'`. +* `format` {string} Must be one of `'raw'`, `'pkcs8'`, `'spki'`, `'jwk'`, `'raw-secret'`[^modern-algos], + `'raw-public'`[^modern-algos], or `'raw-seed'`[^modern-algos]. * `wrappedKey` {ArrayBuffer|TypedArray|DataView|Buffer} * `unwrappingKey` {CryptoKey} -* `unwrapAlgo` {string|Algorithm|RsaOaepParams|AesCtrParams|AesCbcParams|AesGcmParams} +* `unwrapAlgo` {string|Algorithm|RsaOaepParams|AesCtrParams|AesCbcParams|AeadParams} * `unwrappedKeyAlgo` {string|Algorithm|RsaHashedImportParams|EcKeyImportParams|HmacImportParams} @@ -918,34 +1346,47 @@ promise is resolved with a {CryptoKey} object. The wrapping algorithms currently supported include: -* `'RSA-OAEP'` -* `'AES-CTR'` * `'AES-CBC'` +* `'AES-CTR'` * `'AES-GCM'` * `'AES-KW'` +* `'AES-OCB'`[^modern-algos] +* `'ChaCha20-Poly1305'`[^modern-algos] +* `'RSA-OAEP'` The unwrapped key algorithms supported include: -* `'RSASSA-PKCS1-v1_5'` -* `'RSA-PSS'` -* `'RSA-OAEP'` -* `'ECDSA'` -* `'Ed25519'` -* `'Ed448'` [^1] -* `'ECDH'` -* `'X25519'` -* `'X448'` [^1] -* `'HMAC'` -* `'AES-CTR'` * `'AES-CBC'` +* `'AES-CTR'` * `'AES-GCM'` * `'AES-KW'` +* `'AES-OCB'`[^modern-algos] +* `'ChaCha20-Poly1305'`[^modern-algos] +* `'ECDH'` +* `'ECDSA'` +* `'Ed25519'` +* `'Ed448'`[^secure-curves] +* `'HMAC'` +* `'ML-DSA-44'`[^modern-algos] +* `'ML-DSA-65'`[^modern-algos] +* `'ML-DSA-87'`[^modern-algos] +* `'ML-KEM-512'`[^modern-algos] +* `'ML-KEM-768'`[^modern-algos] +* `'ML-KEM-1024'`[^modern-algos]v +* `'RSA-OAEP'` +* `'RSA-PSS'` +* `'RSASSA-PKCS1-v1_5'` +* `'X25519'` +* `'X448'`[^secure-curves] ### `subtle.verify(algorithm, key, signature, data)` -* `algorithm` {string|Algorithm|RsaPssParams|EcdsaParams|Ed448Params} +* `algorithm` {string|Algorithm|RsaPssParams|EcdsaParams|Ed448Params|ContextParams} * `key` {CryptoKey} * `signature` {ArrayBuffer|TypedArray|DataView|Buffer} * `data` {ArrayBuffer|TypedArray|DataView|Buffer} @@ -970,25 +1411,36 @@ with either `true` or `false`. The algorithms currently supported include: -* `'RSASSA-PKCS1-v1_5'` -* `'RSA-PSS'` * `'ECDSA'` * `'Ed25519'` -* `'Ed448'` [^1] +* `'Ed448'`[^secure-curves] * `'HMAC'` +* `'ML-DSA-44'`[^modern-algos] +* `'ML-DSA-65'`[^modern-algos] +* `'ML-DSA-87'`[^modern-algos] +* `'RSA-PSS'` +* `'RSASSA-PKCS1-v1_5'` ### `subtle.wrapKey(format, key, wrappingKey, wrapAlgo)` -* `format` {string} Must be one of `'raw'`, `'pkcs8'`, `'spki'`, or `'jwk'`. +* `format` {string} Must be one of `'raw'`, `'pkcs8'`, `'spki'`, `'jwk'`, `'raw-secret'`[^modern-algos], + `'raw-public'`[^modern-algos], or `'raw-seed'`[^modern-algos]. * `key` {CryptoKey} * `wrappingKey` {CryptoKey} -* `wrapAlgo` {string|Algorithm|RsaOaepParams|AesCtrParams|AesCbcParams|AesGcmParams} +* `wrapAlgo` {string|Algorithm|RsaOaepParams|AesCtrParams|AesCbcParams|AeadParams} * Returns: {Promise} Fulfills with an {ArrayBuffer} upon success. @@ -1005,11 +1457,13 @@ containing the encrypted key data. The wrapping algorithms currently supported include: -* `'RSA-OAEP'` -* `'AES-CTR'` * `'AES-CBC'` +* `'AES-CTR'` * `'AES-GCM'` * `'AES-KW'` +* `'AES-OCB'`[^modern-algos] +* `'ChaCha20-Poly1305'`[^modern-algos] +* `'RSA-OAEP'` ## Algorithm parameters @@ -1031,6 +1485,50 @@ added: v15.0.0 * Type: {string} +### Class: `AeadParams` + + + +#### `aeadParams.additionalData` + + + +* Type: {ArrayBuffer|TypedArray|DataView|Buffer|undefined} + +Extra input that is not encrypted but is included in the authentication +of the data. The use of `additionalData` is optional. + +#### `aeadParams.iv` + + + +* Type: {ArrayBuffer|TypedArray|DataView|Buffer} + +The initialization vector must be unique for every encryption operation using a +given key. + +#### `aeadParams.name` + + + +* Type: {string} Must be `'AES-GCM'`, `'AES-OCB'`, or `'ChaCha20-Poly1305'`. + +#### `aeadParams.tagLength` + + + +* Type: {number} The size in bits of the generated authentication tag. + ### Class: `AesDerivedKeyParams` -* Type: {string} Must be one of `'AES-CBC'`, `'AES-CTR'`, `'AES-GCM'`, or - `'AES-KW'` +* Type: {string} Must be one of `'AES-CBC'`, `'AES-CTR'`, `'AES-GCM'`, `'AES-OCB'`, or `'AES-KW'` #### `aesDerivedKeyParams.length` @@ -1118,108 +1615,129 @@ added: v15.0.0 * Type: {string} Must be `'AES-CTR'`. -### Class: `AesGcmParams` +### Class: `AesKeyAlgorithm` -#### `aesGcmParams.additionalData` +#### `aesKeyAlgorithm.length` -* Type: {ArrayBuffer|TypedArray|DataView|Buffer|undefined} +* Type: {number} -With the AES-GCM method, the `additionalData` is extra input that is not -encrypted but is included in the authentication of the data. The use of -`additionalData` is optional. +The length of the AES key in bits. -#### `aesGcmParams.iv` +#### `aesKeyAlgorithm.name` -* Type: {ArrayBuffer|TypedArray|DataView|Buffer} +* Type: {string} -The initialization vector must be unique for every encryption operation using a -given key. +### Class: `AesKeyGenParams` -Ideally, this is a deterministic 12-byte value that is computed in such a way -that it is guaranteed to be unique across all invocations that use the same key. -Alternatively, the initialization vector may consist of at least 12 -cryptographically random bytes. For more information on constructing -initialization vectors for AES-GCM, refer to Section 8 of [NIST SP 800-38D][]. + -#### `aesGcmParams.name` +#### `aesKeyGenParams.length` -* Type: {string} Must be `'AES-GCM'`. +* Type: {number} + +The length of the AES key to be generated. This must be either `128`, `192`, +or `256`. -#### `aesGcmParams.tagLength` +#### `aesKeyGenParams.name` -* Type: {number} The size in bits of the generated authentication tag. - This values must be one of `32`, `64`, `96`, `104`, `112`, `120`, or - `128`. **Default:** `128`. +* Type: {string} Must be one of `'AES-CBC'`, `'AES-CTR'`, `'AES-GCM'`, or + `'AES-KW'` -### Class: `AesKeyAlgorithm` +### Class: `ContextParams` -#### `aesKeyAlgorithm.length` +#### `contextParams.name` -* Type: {number} - -The length of the AES key in bits. +* Type: {string} Must be `'ML-DSA-44'`[^modern-algos], `'ML-DSA-65'`[^modern-algos], or `'ML-DSA-87'`[^modern-algos]. -#### `aesKeyAlgorithm.name` +#### `contextParams.context` -* Type: {string} +* Type: {ArrayBuffer|TypedArray|DataView|Buffer|undefined} -### Class: `AesKeyGenParams` +The `context` member represents the optional context data to associate with +the message. +The Node.js Web Crypto API implementation only supports zero-length context +which is equivalent to not providing context at all. + +### Class: `CShakeParams` -#### `aesKeyGenParams.length` +#### `cShakeParams.customization` -* Type: {number} +* Type: {ArrayBuffer|TypedArray|DataView|Buffer|undefined} -The length of the AES key to be generated. This must be either `128`, `192`, -or `256`. +The `customization` member represents the customization string. +The Node.js Web Crypto API implementation only supports zero-length customization +which is equivalent to not providing customization at all. -#### `aesKeyGenParams.name` +#### `cShakeParams.functionName` -* Type: {string} Must be one of `'AES-CBC'`, `'AES-CTR'`, `'AES-GCM'`, or - `'AES-KW'` +* Type: {ArrayBuffer|TypedArray|DataView|Buffer|undefined} + +The `functionName` member represents represents the function name, used by NIST to define +functions based on cSHAKE. +The Node.js Web Crypto API implementation only supports zero-length functionName +which is equivalent to not providing functionName at all. + +#### `cShakeParams.length` + + + +* Type: {number} represents the requested output length in bits. + +#### `cShakeParams.name` + + + +* Type: {string} Must be `'cSHAKE128'`[^modern-algos] or `'cSHAKE256'`[^modern-algos] ### Class: `EcdhKeyDeriveParams` @@ -1233,7 +1751,7 @@ added: v15.0.0 added: v15.0.0 --> -* Type: {string} Must be `'ECDH'`, `'X25519'`, or `'X448'`. +* Type: {string} Must be `'ECDH'`, `'X25519'`, or `'X448'`[^secure-curves]. #### `ecdhKeyDeriveParams.public` @@ -1258,6 +1776,10 @@ added: v15.0.0 * Type: {string|Algorithm} @@ -1268,6 +1790,9 @@ If represented as a {string}, the value must be one of: * `'SHA-256'` * `'SHA-384'` * `'SHA-512'` +* `'SHA3-256'`[^modern-algos] +* `'SHA3-384'`[^modern-algos] +* `'SHA3-512'`[^modern-algos] If represented as an {Algorithm}, the object's `name` property must be one of the above listed values. @@ -1360,7 +1885,7 @@ added: - v16.17.0 --> -* Type: {string} Must be `'Ed448'`. +* Type: {string} Must be `'Ed448'`[^secure-curves]. #### `ed448Params.context` @@ -1377,6 +1902,50 @@ the message. The Node.js Web Crypto API implementation only supports zero-length context which is equivalent to not providing context at all. +### Class: `EncapsulatedBits` + + + +#### `encapsulatedBits.ciphertext` + + + +* Type: {ArrayBuffer} + +#### `encapsulatedBits.sharedKey` + + + +* Type: {ArrayBuffer} + +### Class: `EncapsulatedKey` + + + +#### `encapsulatedKey.ciphertext` + + + +* Type: {ArrayBuffer} + +#### `encapsulatedKey.sharedKey` + + + +* Type: {CryptoKey} + ### Class: `HkdfParams` * Type: {string|Algorithm} @@ -1397,6 +1970,9 @@ If represented as a {string}, the value must be one of: * `'SHA-256'` * `'SHA-384'` * `'SHA-512'` +* `'SHA3-256'`[^modern-algos] +* `'SHA3-384'`[^modern-algos] +* `'SHA3-512'`[^modern-algos] If represented as an {Algorithm}, the object's `name` property must be one of the above listed values. @@ -1443,6 +2019,10 @@ added: v15.0.0 * Type: {string|Algorithm} @@ -1453,6 +2033,9 @@ If represented as a {string}, the value must be one of: * `'SHA-256'` * `'SHA-384'` * `'SHA-512'` +* `'SHA3-256'`[^modern-algos] +* `'SHA3-384'`[^modern-algos] +* `'SHA3-512'`[^modern-algos] If represented as an {Algorithm}, the object's `name` property must be one of the above listed values. @@ -1518,6 +2101,10 @@ added: v15.0.0 * Type: {string|Algorithm} @@ -1528,6 +2115,9 @@ If represented as a {string}, the value must be one of: * `'SHA-256'` * `'SHA-384'` * `'SHA-512'` +* `'SHA3-256'`[^modern-algos] +* `'SHA3-384'`[^modern-algos] +* `'SHA3-512'`[^modern-algos] If represented as an {Algorithm}, the object's `name` property must be one of the above listed values. @@ -1576,6 +2166,10 @@ added: v15.0.0 * Type: {string|Algorithm} @@ -1586,6 +2180,9 @@ If represented as a {string}, the value must be one of: * `'SHA-256'` * `'SHA-384'` * `'SHA-512'` +* `'SHA3-256'`[^modern-algos] +* `'SHA3-384'`[^modern-algos] +* `'SHA3-512'`[^modern-algos] If represented as an {Algorithm}, the object's `name` property must be one of the above listed values. @@ -1628,6 +2225,10 @@ added: v15.0.0 * Type: {string|Algorithm} @@ -1638,6 +2239,9 @@ If represented as a {string}, the value must be one of: * `'SHA-256'` * `'SHA-384'` * `'SHA-512'` +* `'SHA3-256'`[^modern-algos] +* `'SHA3-384'`[^modern-algos] +* `'SHA3-512'`[^modern-algos] If represented as an {Algorithm}, the object's `name` property must be one of the above listed values. @@ -1703,6 +2307,10 @@ added: v15.0.0 * Type: {string|Algorithm} @@ -1713,6 +2321,9 @@ If represented as a {string}, the value must be one of: * `'SHA-256'` * `'SHA-384'` * `'SHA-512'` +* `'SHA3-256'`[^modern-algos] +* `'SHA3-384'`[^modern-algos] +* `'SHA3-512'`[^modern-algos] If represented as an {Algorithm}, the object's `name` property must be one of the above listed values. @@ -1802,12 +2413,35 @@ added: v15.0.0 The length (in bytes) of the random salt to use. -[^1]: An experimental implementation of Ed448 and X448 algorithms from - [Secure Curves in the Web Cryptography API][] as of 21 October 2024 +[^secure-curves]: See [Secure Curves in the Web Cryptography API][] + +[^modern-algos]: See [Modern Algorithms in the Web Cryptography API][] + +[^openssl30]: Requires OpenSSL >= 3.0 + +[^openssl35]: Requires OpenSSL >= 3.5 [JSON Web Key]: https://tools.ietf.org/html/rfc7517 [Key usages]: #cryptokeyusages -[NIST SP 800-38D]: https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38d.pdf +[Modern Algorithms in the Web Cryptography API]: #modern-algorithms-in-the-web-cryptography-api [RFC 4122]: https://www.rfc-editor.org/rfc/rfc4122.txt -[Secure Curves in the Web Cryptography API]: https://wicg.github.io/webcrypto-secure-curves/ +[Secure Curves in the Web Cryptography API]: #secure-curves-in-the-web-cryptography-api [Web Crypto API]: https://www.w3.org/TR/WebCryptoAPI/ +[`SubtleCrypto.supports()`]: #static-method-subtlecryptosupportsoperation-algorithm-lengthoradditionalalgorithm +[`subtle.decapsulateBits()`]: #subtledecapsulatebitsdecapsulationalgorithm-decapsulationkey-ciphertext +[`subtle.decapsulateKey()`]: #subtledecapsulatekeydecapsulationalgorithm-decapsulationkey-ciphertext-sharedkeyalgorithm-extractable-usages +[`subtle.decrypt()`]: #subtledecryptalgorithm-key-data +[`subtle.deriveBits()`]: #subtlederivebitsalgorithm-basekey-length +[`subtle.deriveKey()`]: #subtlederivekeyalgorithm-basekey-derivedkeyalgorithm-extractable-keyusages +[`subtle.digest()`]: #subtledigestalgorithm-data +[`subtle.encapsulateBits()`]: #subtleencapsulatebitsencapsulationalgorithm-encapsulationkey +[`subtle.encapsulateKey()`]: #subtleencapsulatekeyencapsulationalgorithm-encapsulationkey-sharedkeyalgorithm-extractable-usages +[`subtle.encrypt()`]: #subtleencryptalgorithm-key-data +[`subtle.exportKey()`]: #subtleexportkeyformat-key +[`subtle.generateKey()`]: #subtlegeneratekeyalgorithm-extractable-keyusages +[`subtle.getPublicKey()`]: #subtlegetpublickeykey-keyusages +[`subtle.importKey()`]: #subtleimportkeyformat-keydata-algorithm-extractable-keyusages +[`subtle.sign()`]: #subtlesignalgorithm-key-data +[`subtle.unwrapKey()`]: #subtleunwrapkeyformat-wrappedkey-unwrappingkey-unwrapalgo-unwrappedkeyalgo-extractable-keyusages +[`subtle.verify()`]: #subtleverifyalgorithm-key-signature-data +[`subtle.wrapKey()`]: #subtlewrapkeyformat-key-wrappingkey-wrapalgo diff --git a/doc/api/webstreams.md b/doc/api/webstreams.md index b1b22a56e2b45e..d38196ab0432fa 100644 --- a/doc/api/webstreams.md +++ b/doc/api/webstreams.md @@ -1480,6 +1480,9 @@ changes: -* `format` {string} One of `'deflate'`, `'deflate-raw'`, or `'gzip'`. +* `format` {string} One of `'deflate'`, `'deflate-raw'`, `'gzip'`, or `'brotli'`. #### `compressionStream.readable` @@ -1520,6 +1523,9 @@ changes: -* `format` {string} One of `'deflate'`, `'deflate-raw'`, or `'gzip'`. +* `format` {string} One of `'deflate'`, `'deflate-raw'`, `'gzip'`, or `'brotli'`. #### `decompressionStream.readable` diff --git a/doc/api/zlib.md b/doc/api/zlib.md index c3f827ee0c23d0..e07b0eb4ccd297 100644 --- a/doc/api/zlib.md +++ b/doc/api/zlib.md @@ -750,6 +750,34 @@ The most important options are: * `ZSTD_c_compressionLevel` * Set compression parameters according to pre-defined cLevel table. Default level is ZSTD\_CLEVEL\_DEFAULT==3. +* `ZSTD_c_strategy` + * Select the compression strategy. + * Possible values are listed in the strategy options section below. + +#### Strategy options + +The following constants can be used as values for the `ZSTD_c_strategy` +parameter: + +* `zlib.constants.ZSTD_fast` +* `zlib.constants.ZSTD_dfast` +* `zlib.constants.ZSTD_greedy` +* `zlib.constants.ZSTD_lazy` +* `zlib.constants.ZSTD_lazy2` +* `zlib.constants.ZSTD_btlazy2` +* `zlib.constants.ZSTD_btopt` +* `zlib.constants.ZSTD_btultra` +* `zlib.constants.ZSTD_btultra2` + +Example: + +```js +const stream = zlib.createZstdCompress({ + params: { + [zlib.constants.ZSTD_c_strategy]: zlib.constants.ZSTD_btultra, + }, +}); +``` #### Pledged Source Size diff --git a/doc/changelogs/CHANGELOG_V24.md b/doc/changelogs/CHANGELOG_V24.md index eb6c549d0b8191..550d1492a30f93 100644 --- a/doc/changelogs/CHANGELOG_V24.md +++ b/doc/changelogs/CHANGELOG_V24.md @@ -8,6 +8,7 @@ +24.7.0
24.6.0
24.5.0
24.4.1
@@ -48,6 +49,218 @@ * [io.js](CHANGELOG_IOJS.md) * [Archive](CHANGELOG_ARCHIVE.md) + + +## 2025-08-27, Version 24.7.0 (Current), @targos + +### Notable Changes + +#### Post-Quantum Cryptography in `node:crypto` + +OpenSSL 3.5 on 24.x kicked off post-quantum cryptography efforts in Node.js by +allowing use of NIST's post-quantum cryptography standards for future-proofing +applications against quantum computing threats. The following post-quantum +algorithms are now available in `node:crypto`: + +* ML-KEM (FIPS 203, Module-Lattice-Based Key-Encapsulation Mechanism Standard) through new `crypto.encapsulate()` and `crypto.decapsulate()` methods. +* ML-DSA (FIPS 204, Module-Lattice-Based Digital Signature Standard) in the existing `crypto.sign()` and `crypto.verify()` methods. + +Contributed by Filip Skokan in [#59259](https://github.com/nodejs/node/pull/59259) and [#59491](https://github.com/nodejs/node/pull/59491). + +### Modern Algorithms in Web Cryptography API + +The second substantial [extension to the Web Cryptography API](https://wicg.github.io/webcrypto-modern-algos/) +(`globalThis.crypto.subtle`) was recently accepted for incubation by WICG. +The following algorithms and methods from this extension are now available in +the Node.js Web Cryptography API implementation: + +* AES-OCB +* ChaCha20-Poly1305 +* ML-DSA +* ML-KEM +* SHA-3 +* SHAKE +* `subtle.getPublicKey()` +* `SubtleCrypto.supports()` +* ... with more coming in future releases. + +Contributed by Filip Skokan in [#59365](https://github.com/nodejs/node/pull/59365), [#59569](https://github.com/nodejs/node/pull/59569), [#59461](https://github.com/nodejs/node/pull/59461), and [#59539](https://github.com/nodejs/node/pull/59539). + +#### Node.js execution argument support in single executable applications + +The single executable application configuration now supports additional fields +to specify Node.js execution arguments and control how they can be extended when +the application is run. + +* `execArgv` takes an array of strings for the execution arguments to be used. +* `execArgvExtension` takes one of the following values: + * `"none"`: No additional execution arguments are allowed. + * `"cli"`: Additional execution arguments can be provided via a special command-line flag `--node-options="--flag1 --flag2=value"` at run time. + * `"env"` (default): Additional execution arguments can be provided via the `NODE_OPTIONS` environment variable at run time. + +For example, with the following configuration: + +```json +{ + "main": "/path/to/bundled/script.js", + "output": "/path/to/write/the/generated/blob.blob", + "execArgv": ["--no-warnings"], + "execArgvExtension": "cli", +} +``` + +If the generated single executable application is named `sea`, then running: + +```console +sea --node-options="--max-old-space-size=4096" user-arg1 user-arg2 +``` + +Would be equivalent to running: + +```console +node --no-warnings --max-old-space-size=4096 /path/to/bundled/script.js user-arg1 user-arg2 +``` + +Contributed by Joyee Cheung in [#59314](https://github.com/nodejs/node/pull/59314) and [#59560](https://github.com/nodejs/node/pull/59560). + +#### Root certificates updated to NSS 3.114 + +Certificates added: + +* TrustAsia TLS ECC Root CA +* TrustAsia TLS RSA Root CA +* SwissSign RSA TLS Root CA 2022 - 1 + +Certificates removed: + +* GlobalSign Root CA +* Entrust.net Premium 2048 Secure Server CA +* Baltimore CyberTrust Root +* Comodo AAA Services root +* XRamp Global CA Root +* Go Daddy Class 2 CA +* Starfield Class 2 CA + +#### Other Notable Changes + +* \[[`d3afc63c44`](https://github.com/nodejs/node/commit/d3afc63c44)] - **(SEMVER-MINOR)** **crypto**: add argon2() and argon2Sync() methods (Ranieri Althoff) [#50353](https://github.com/nodejs/node/pull/50353) +* \[[`6ae202fcdf`](https://github.com/nodejs/node/commit/6ae202fcdf)] - **(SEMVER-MINOR)** **http**: add Agent.agentKeepAliveTimeoutBuffer option (Haram Jeong) [#59315](https://github.com/nodejs/node/pull/59315) +* \[[`dafee05358`](https://github.com/nodejs/node/commit/dafee05358)] - **(SEMVER-MINOR)** **http2**: add support for raw header arrays in h2Stream.respond() (Tim Perry) [#59455](https://github.com/nodejs/node/pull/59455) +* \[[`8dc6f5b696`](https://github.com/nodejs/node/commit/8dc6f5b696)] - **(SEMVER-MINOR)** **stream**: add brotli support to CompressionStream and DecompressionStream (Matthew Aitken) [#59464](https://github.com/nodejs/node/pull/59464) + +### Commits + +* \[[`0fa22cbf7c`](https://github.com/nodejs/node/commit/0fa22cbf7c)] - **benchmark**: calibrate config v8/serialize.js (Rafael Gonzaga) [#59586](https://github.com/nodejs/node/pull/59586) +* \[[`f5ece45b45`](https://github.com/nodejs/node/commit/f5ece45b45)] - **benchmark**: reduce readfile-permission-enabled config (Rafael Gonzaga) [#59589](https://github.com/nodejs/node/pull/59589) +* \[[`8ebd4f4434`](https://github.com/nodejs/node/commit/8ebd4f4434)] - **benchmark**: calibrate length of util.diff (Rafael Gonzaga) [#59588](https://github.com/nodejs/node/pull/59588) +* \[[`7dee3ffd14`](https://github.com/nodejs/node/commit/7dee3ffd14)] - **benchmark**: reflect current OpenSSL in crypto key benchmarks (Filip Skokan) [#59459](https://github.com/nodejs/node/pull/59459) +* \[[`027b861ca1`](https://github.com/nodejs/node/commit/027b861ca1)] - **benchmark, test**: replace CRLF variable with string literal (Lee Jiho) [#59466](https://github.com/nodejs/node/pull/59466) +* \[[`89dd770889`](https://github.com/nodejs/node/commit/89dd770889)] - **build**: do not set `-mminimal-toc` with `clang` (Richard Lau) [#59484](https://github.com/nodejs/node/pull/59484) +* \[[`e13de4542f`](https://github.com/nodejs/node/commit/e13de4542f)] - **child\_process**: remove unsafe array iteration (hotpineapple) [#59347](https://github.com/nodejs/node/pull/59347) +* \[[`89fe63551e`](https://github.com/nodejs/node/commit/89fe63551e)] - **crypto**: load system CA certificates off thread (Joyee Cheung) [#59550](https://github.com/nodejs/node/pull/59550) +* \[[`152c5ef518`](https://github.com/nodejs/node/commit/152c5ef518)] - **(SEMVER-MINOR)** **crypto**: add AES-OCB Web Cryptography algorithm (Filip Skokan) [#59539](https://github.com/nodejs/node/pull/59539) +* \[[`c6c418343d`](https://github.com/nodejs/node/commit/c6c418343d)] - **crypto**: update root certificates to NSS 3.114 (Node.js GitHub Bot) [#59571](https://github.com/nodejs/node/pull/59571) +* \[[`18a2ee5b6c`](https://github.com/nodejs/node/commit/18a2ee5b6c)] - **(SEMVER-MINOR)** **crypto**: support ML-KEM in Web Cryptography (Filip Skokan) [#59569](https://github.com/nodejs/node/pull/59569) +* \[[`72937e5144`](https://github.com/nodejs/node/commit/72937e5144)] - **crypto**: require HMAC key length with SHA-3 hashes in Web Cryptography (Filip Skokan) [#59567](https://github.com/nodejs/node/pull/59567) +* \[[`b7383186c7`](https://github.com/nodejs/node/commit/b7383186c7)] - **crypto**: fix subtle.getPublicKey error for secret type key inputs (Filip Skokan) [#59558](https://github.com/nodejs/node/pull/59558) +* \[[`2d05c046db`](https://github.com/nodejs/node/commit/2d05c046db)] - **crypto**: return cached copies from CryptoKey algorithm and usages getters (Filip Skokan) [#59538](https://github.com/nodejs/node/pull/59538) +* \[[`207ffbeb07`](https://github.com/nodejs/node/commit/207ffbeb07)] - **crypto**: use CryptoKey internal slots in Web Cryptography (Filip Skokan) [#59538](https://github.com/nodejs/node/pull/59538) +* \[[`4276516781`](https://github.com/nodejs/node/commit/4276516781)] - **crypto**: normalize RsaHashedKeyParams publicExponent (Filip Skokan) [#59538](https://github.com/nodejs/node/pull/59538) +* \[[`14741539a7`](https://github.com/nodejs/node/commit/14741539a7)] - **(SEMVER-MINOR)** **crypto**: support ML-KEM, DHKEM, and RSASVE key encapsulation mechanisms (Filip Skokan) [#59491](https://github.com/nodejs/node/pull/59491) +* \[[`d3afc63c44`](https://github.com/nodejs/node/commit/d3afc63c44)] - **(SEMVER-MINOR)** **crypto**: add argon2() and argon2Sync() methods (Ranieri Althoff) [#50353](https://github.com/nodejs/node/pull/50353) +* \[[`4fe383e45a`](https://github.com/nodejs/node/commit/4fe383e45a)] - **(SEMVER-MINOR)** **crypto**: support ML-DSA spki/pkcs8 key formats in Web Cryptography (Filip Skokan) [#59365](https://github.com/nodejs/node/pull/59365) +* \[[`a95386fbf9`](https://github.com/nodejs/node/commit/a95386fbf9)] - **(SEMVER-MINOR)** **crypto**: subject some algorithms in Web Cryptography on BoringSSL absence (Filip Skokan) [#59365](https://github.com/nodejs/node/pull/59365) +* \[[`3f47a2fb63`](https://github.com/nodejs/node/commit/3f47a2fb63)] - **(SEMVER-MINOR)** **crypto**: add ChaCha20-Poly1305 Web Cryptography algorithm (Filip Skokan) [#59365](https://github.com/nodejs/node/pull/59365) +* \[[`6fcce9058a`](https://github.com/nodejs/node/commit/6fcce9058a)] - **(SEMVER-MINOR)** **crypto**: add subtle.getPublicKey() utility function in Web Cryptography (Filip Skokan) [#59365](https://github.com/nodejs/node/pull/59365) +* \[[`76cde76429`](https://github.com/nodejs/node/commit/76cde76429)] - **(SEMVER-MINOR)** **crypto**: add SHA-3 Web Cryptography digest algorithms (Filip Skokan) [#59365](https://github.com/nodejs/node/pull/59365) +* \[[`247d017501`](https://github.com/nodejs/node/commit/247d017501)] - **(SEMVER-MINOR)** **crypto**: add SHAKE Web Cryptography digest algorithms (Filip Skokan) [#59365](https://github.com/nodejs/node/pull/59365) +* \[[`f4fbcca5ce`](https://github.com/nodejs/node/commit/f4fbcca5ce)] - **(SEMVER-MINOR)** **crypto**: add SubtleCrypto.supports feature detection in Web Cryptography (Filip Skokan) [#59365](https://github.com/nodejs/node/pull/59365) +* \[[`a55382214f`](https://github.com/nodejs/node/commit/a55382214f)] - **(SEMVER-MINOR)** **crypto**: support ML-DSA in Web Cryptography (Filip Skokan) [#59365](https://github.com/nodejs/node/pull/59365) +* \[[`c38988c860`](https://github.com/nodejs/node/commit/c38988c860)] - **crypto**: fix EVPKeyCtxPointer::publicCheck() (Tobias Nießen) [#59471](https://github.com/nodejs/node/pull/59471) +* \[[`61c3bcdc56`](https://github.com/nodejs/node/commit/61c3bcdc56)] - **(SEMVER-MINOR)** **crypto**: support ML-KEM KeyObject (Filip Skokan) [#59461](https://github.com/nodejs/node/pull/59461) +* \[[`0821b446fb`](https://github.com/nodejs/node/commit/0821b446fb)] - **deps**: update undici to 7.14.0 (Node.js GitHub Bot) [#59507](https://github.com/nodejs/node/pull/59507) +* \[[`b3af17c065`](https://github.com/nodejs/node/commit/b3af17c065)] - **deps**: V8: cherry-pick 7b91e3e2cbaf (Milad Fa) [#59485](https://github.com/nodejs/node/pull/59485) +* \[[`9b69baf146`](https://github.com/nodejs/node/commit/9b69baf146)] - **deps**: V8: cherry-pick 59d52e311bb1 (Milad Fa) [#59485](https://github.com/nodejs/node/pull/59485) +* \[[`b4f202c2f1`](https://github.com/nodejs/node/commit/b4f202c2f1)] - **doc**: improve `sqlite.backup()` progress/fulfillment documentation (René) [#59598](https://github.com/nodejs/node/pull/59598) +* \[[`40b217a2f9`](https://github.com/nodejs/node/commit/40b217a2f9)] - **doc**: clarify experimental platform vulnerability policy (Matteo Collina) [#59591](https://github.com/nodejs/node/pull/59591) +* \[[`cf84fffea5`](https://github.com/nodejs/node/commit/cf84fffea5)] - **doc**: link to `TypedArray.from()` in signature (Aviv Keller) [#59226](https://github.com/nodejs/node/pull/59226) +* \[[`4bf6ed0bf5`](https://github.com/nodejs/node/commit/4bf6ed0bf5)] - **doc**: fix typos in `environment_variables.md` (PhistucK) [#59536](https://github.com/nodejs/node/pull/59536) +* \[[`1784c35a49`](https://github.com/nodejs/node/commit/1784c35a49)] - **doc**: add security incident reponse plan (Rafael Gonzaga) [#59470](https://github.com/nodejs/node/pull/59470) +* \[[`b962560240`](https://github.com/nodejs/node/commit/b962560240)] - **doc**: clarify maxRSS unit in `process.resourceUsage()` (Alex Yang) [#59511](https://github.com/nodejs/node/pull/59511) +* \[[`e6a6cdb9df`](https://github.com/nodejs/node/commit/e6a6cdb9df)] - **doc**: add missing Zstd strategy constants (RANDRIAMANANTENA Narindra Tiana Annaick) [#59312](https://github.com/nodejs/node/pull/59312) +* \[[`a6a31cb467`](https://github.com/nodejs/node/commit/a6a31cb467)] - **(SEMVER-MINOR)** **doc**: compress Web Cryptography Algorithm matrix (Filip Skokan) [#59365](https://github.com/nodejs/node/pull/59365) +* \[[`8f8960cfcb`](https://github.com/nodejs/node/commit/8f8960cfcb)] - **doc**: fix the version tls.DEFAULT\_CIPHERS was added (Allon Murienik) [#59247](https://github.com/nodejs/node/pull/59247) +* \[[`9e76089f1a`](https://github.com/nodejs/node/commit/9e76089f1a)] - **doc**: clarify glob's exclude option behavior (hotpineapple) [#59245](https://github.com/nodejs/node/pull/59245) +* \[[`dd5f835af7`](https://github.com/nodejs/node/commit/dd5f835af7)] - **doc**: add RafaelGSS as performance strategic lead (Rafael Gonzaga) [#59445](https://github.com/nodejs/node/pull/59445) +* \[[`2b7a7a525e`](https://github.com/nodejs/node/commit/2b7a7a525e)] - **doc,crypto**: add supported asymmetric key types section (Filip Skokan) [#59492](https://github.com/nodejs/node/pull/59492) +* \[[`2fafe4c3bb`](https://github.com/nodejs/node/commit/2fafe4c3bb)] - **esm**: link modules synchronously when no async loader hooks are used (Joyee Cheung) [#59519](https://github.com/nodejs/node/pull/59519) +* \[[`5347c4997a`](https://github.com/nodejs/node/commit/5347c4997a)] - **esm**: show race error message for inner module job race (Joyee Cheung) [#59519](https://github.com/nodejs/node/pull/59519) +* \[[`b56d8af2fe`](https://github.com/nodejs/node/commit/b56d8af2fe)] - **esm**: sync-ify module translation (Joyee Cheung) [#59453](https://github.com/nodejs/node/pull/59453) +* \[[`b4a23d6a69`](https://github.com/nodejs/node/commit/b4a23d6a69)] - **http**: trim off brackets from IPv6 addresses with string operations (Krishnadas PC) [#59420](https://github.com/nodejs/node/pull/59420) +* \[[`6ae202fcdf`](https://github.com/nodejs/node/commit/6ae202fcdf)] - **(SEMVER-MINOR)** **http**: add Agent.agentKeepAliveTimeoutBuffer option (Haram Jeong) [#59315](https://github.com/nodejs/node/pull/59315) +* \[[`dafee05358`](https://github.com/nodejs/node/commit/dafee05358)] - **(SEMVER-MINOR)** **http2**: add support for raw header arrays in h2Stream.respond() (Tim Perry) [#59455](https://github.com/nodejs/node/pull/59455) +* \[[`b7ea39d860`](https://github.com/nodejs/node/commit/b7ea39d860)] - **http2**: report sent headers object in client stream dcs (Darshan Sen) [#59419](https://github.com/nodejs/node/pull/59419) +* \[[`ebe9272dae`](https://github.com/nodejs/node/commit/ebe9272dae)] - **inspector**: initial support websocket inspection (Shima Ryuhei) [#59404](https://github.com/nodejs/node/pull/59404) +* \[[`b35041c7dc`](https://github.com/nodejs/node/commit/b35041c7dc)] - **inspector**: prevent propagation of promise hooks to noPromise hooks (Shima Ryuhei) [#58841](https://github.com/nodejs/node/pull/58841) +* \[[`fe7176d7c6`](https://github.com/nodejs/node/commit/fe7176d7c6)] - **lib**: do not modify prototype deprecated asyncResource (encore) (Szymon Łągiewka) [#59518](https://github.com/nodejs/node/pull/59518) +* \[[`93fc80a1e2`](https://github.com/nodejs/node/commit/93fc80a1e2)] - **(SEMVER-MINOR)** **lib**: refactor kSupportedAlgorithms (Filip Skokan) [#59365](https://github.com/nodejs/node/pull/59365) +* \[[`9a12f71ad9`](https://github.com/nodejs/node/commit/9a12f71ad9)] - **lib**: simplify IPv6 checks in isLoopback() (Krishnadas) [#59375](https://github.com/nodejs/node/pull/59375) +* \[[`566fb04c82`](https://github.com/nodejs/node/commit/566fb04c82)] - **meta**: update devcontainer to the latest schema (Aviv Keller) [#54347](https://github.com/nodejs/node/pull/54347) +* \[[`389a24bbff`](https://github.com/nodejs/node/commit/389a24bbff)] - **module**: allow overriding linked requests for a ModuleWrap (Chengzhong Wu) [#59527](https://github.com/nodejs/node/pull/59527) +* \[[`7880978fe3`](https://github.com/nodejs/node/commit/7880978fe3)] - **module**: correctly detect top-level await in ambiguous contexts (Shima Ryuhei) [#58646](https://github.com/nodejs/node/pull/58646) +* \[[`99128d9244`](https://github.com/nodejs/node/commit/99128d9244)] - **node-api**: link to other programming language bindings (Chengzhong Wu) [#59516](https://github.com/nodejs/node/pull/59516) +* \[[`65c870e6cb`](https://github.com/nodejs/node/commit/65c870e6cb)] - **node-api**: clarify enum value ABI stability (Chengzhong Wu) [#59085](https://github.com/nodejs/node/pull/59085) +* \[[`352d63541a`](https://github.com/nodejs/node/commit/352d63541a)] - **sea**: implement execArgvExtension (Joyee Cheung) [#59560](https://github.com/nodejs/node/pull/59560) +* \[[`c6e3d5d98d`](https://github.com/nodejs/node/commit/c6e3d5d98d)] - **(SEMVER-MINOR)** **sea**: support execArgv in sea config (Joyee Cheung) [#59314](https://github.com/nodejs/node/pull/59314) +* \[[`e7084df4db`](https://github.com/nodejs/node/commit/e7084df4db)] - **sqlite**: add sqlite-type symbol for DatabaseSync (Alex Yang) [#59405](https://github.com/nodejs/node/pull/59405) +* \[[`e2b6bdc640`](https://github.com/nodejs/node/commit/e2b6bdc640)] - **sqlite**: handle ?NNN parameters as positional (Edy Silva) [#59350](https://github.com/nodejs/node/pull/59350) +* \[[`99e4a12731`](https://github.com/nodejs/node/commit/99e4a12731)] - **sqlite**: avoid useless call to FromMaybe() (Tobias Nießen) [#59490](https://github.com/nodejs/node/pull/59490) +* \[[`dfd4962e5f`](https://github.com/nodejs/node/commit/dfd4962e5f)] - **src**: enforce assumptions in FIXED\_ONE\_BYTE\_STRING (Tobias Nießen) [#58155](https://github.com/nodejs/node/pull/58155) +* \[[`93a368df04`](https://github.com/nodejs/node/commit/93a368df04)] - **src**: use simdjson to parse --snapshot-config (Joyee Cheung) [#59473](https://github.com/nodejs/node/pull/59473) +* \[[`716750fcf8`](https://github.com/nodejs/node/commit/716750fcf8)] - **src**: fix order of CHECK\_NOT\_NULL/dereference (Tobias Nießen) [#59487](https://github.com/nodejs/node/pull/59487) +* \[[`44a8ecf8d4`](https://github.com/nodejs/node/commit/44a8ecf8d4)] - **src**: assert memory calc for max-old-space-size-percentage (Asaf Federman) [#59460](https://github.com/nodejs/node/pull/59460) +* \[[`3462b46fca`](https://github.com/nodejs/node/commit/3462b46fca)] - **src**: use simdjson::pad (0hm☘️) [#59391](https://github.com/nodejs/node/pull/59391) +* \[[`3e1551d845`](https://github.com/nodejs/node/commit/3e1551d845)] - **src**: move shared\_ptr objects in KeyObjectData (Tobias Nießen) [#59472](https://github.com/nodejs/node/pull/59472) +* \[[`c022c1f85a`](https://github.com/nodejs/node/commit/c022c1f85a)] - **src**: add internal GetOptionsAsFlags (Pietro Marchini) [#59138](https://github.com/nodejs/node/pull/59138) +* \[[`c0f08454a3`](https://github.com/nodejs/node/commit/c0f08454a3)] - **src**: iterate metadata version entries with std::array (Chengzhong Wu) [#57866](https://github.com/nodejs/node/pull/57866) +* \[[`f87836f3ae`](https://github.com/nodejs/node/commit/f87836f3ae)] - **src**: internalize `v8::ConvertableToTraceFormat` in traces (Chengzhong Wu) [#57866](https://github.com/nodejs/node/pull/57866) +* \[[`852b8e46d8`](https://github.com/nodejs/node/commit/852b8e46d8)] - **src**: remove duplicate assignment of `O_EXCL` in node\_constants.cc (Daniel Osvaldo R) [#59049](https://github.com/nodejs/node/pull/59049) +* \[[`64ffde608f`](https://github.com/nodejs/node/commit/64ffde608f)] - **src**: add Intel CET properties to large\_pages.S (tjuhaszrh) [#59363](https://github.com/nodejs/node/pull/59363) +* \[[`823dce32ec`](https://github.com/nodejs/node/commit/823dce32ec)] - **src**: update OpenSSL pqc checks (Filip Skokan) [#59436](https://github.com/nodejs/node/pull/59436) +* \[[`8dc6f5b696`](https://github.com/nodejs/node/commit/8dc6f5b696)] - **(SEMVER-MINOR)** **stream**: add brotli support to CompressionStream and DecompressionStream (Matthew Aitken) [#59464](https://github.com/nodejs/node/pull/59464) +* \[[`b2b8383755`](https://github.com/nodejs/node/commit/b2b8383755)] - **test**: use mustSucceed in test-repl-tab-complete-import (Sohyeon Kim) [#59368](https://github.com/nodejs/node/pull/59368) +* \[[`e3ad5cc2c6`](https://github.com/nodejs/node/commit/e3ad5cc2c6)] - **test**: skip sea tests on Linux ppc64le (Richard Lau) [#59563](https://github.com/nodejs/node/pull/59563) +* \[[`f78f47ca5a`](https://github.com/nodejs/node/commit/f78f47ca5a)] - **test**: support standalone env comment in tests (Pietro Marchini) [#59546](https://github.com/nodejs/node/pull/59546) +* \[[`0e8bc2c7ac`](https://github.com/nodejs/node/commit/0e8bc2c7ac)] - **test**: rename test-net-server-drop-connections-in-cluster.js to -http- (Meghan Denny) [#59532](https://github.com/nodejs/node/pull/59532) +* \[[`ed339580af`](https://github.com/nodejs/node/commit/ed339580af)] - **test**: lazy-load internalTTy (Pietro Marchini) [#59517](https://github.com/nodejs/node/pull/59517) +* \[[`fe86bc6da8`](https://github.com/nodejs/node/commit/fe86bc6da8)] - **test**: fix `test-setproctitle` status when `ps` is not available (Antoine du Hamel) [#59523](https://github.com/nodejs/node/pull/59523) +* \[[`e517792973`](https://github.com/nodejs/node/commit/e517792973)] - **test**: add parseTestMetadata support (Pietro Marchini) [#59503](https://github.com/nodejs/node/pull/59503) +* \[[`31092972d6`](https://github.com/nodejs/node/commit/31092972d6)] - **test**: update WPT for WebCryptoAPI to ff26d9b307 (Node.js GitHub Bot) [#59497](https://github.com/nodejs/node/pull/59497) +* \[[`16afd103cc`](https://github.com/nodejs/node/commit/16afd103cc)] - **(SEMVER-MINOR)** **test**: add Web Cryptography wrap/unwrap vectors (Filip Skokan) [#59365](https://github.com/nodejs/node/pull/59365) +* \[[`5598baf34e`](https://github.com/nodejs/node/commit/5598baf34e)] - **(SEMVER-MINOR)** **test**: cleanup test-webcrypto-supports (Filip Skokan) [#59365](https://github.com/nodejs/node/pull/59365) +* \[[`e7809d6ddb`](https://github.com/nodejs/node/commit/e7809d6ddb)] - **test**: make test-debug-process locale-independent (BCD1me) [#59254](https://github.com/nodejs/node/pull/59254) +* \[[`ca7856e73c`](https://github.com/nodejs/node/commit/ca7856e73c)] - **test**: mark test-wasi-pthread as flaky (Joyee Cheung) [#59488](https://github.com/nodejs/node/pull/59488) +* \[[`0ecd82197f`](https://github.com/nodejs/node/commit/0ecd82197f)] - **test**: split test-wasi.js (Joyee Cheung) [#59488](https://github.com/nodejs/node/pull/59488) +* \[[`0930c218d6`](https://github.com/nodejs/node/commit/0930c218d6)] - **test**: deflake connection refused proxy tests (Joyee Cheung) [#59476](https://github.com/nodejs/node/pull/59476) +* \[[`7f457f886a`](https://github.com/nodejs/node/commit/7f457f886a)] - **test**: use case-insensitive path checking on Windows in fs.cpSync tests (Joyee Cheung) [#59475](https://github.com/nodejs/node/pull/59475) +* \[[`37809115f9`](https://github.com/nodejs/node/commit/37809115f9)] - **test**: add missing hasPostData in test-inspector-emit-protocol-event (Shima Ryuhei) [#59412](https://github.com/nodejs/node/pull/59412) +* \[[`f4722b1672`](https://github.com/nodejs/node/commit/f4722b1672)] - **test**: refactor error checks to use assert.ifError/mustSucceed (Sohyeon Kim) [#59424](https://github.com/nodejs/node/pull/59424) +* \[[`9ff71a672d`](https://github.com/nodejs/node/commit/9ff71a672d)] - **test**: fix typos (Lee Jiho) [#59330](https://github.com/nodejs/node/pull/59330) +* \[[`9a7700da62`](https://github.com/nodejs/node/commit/9a7700da62)] - **test**: skip test-watch-mode inspect when no inspector (James M Snell) [#59440](https://github.com/nodejs/node/pull/59440) +* \[[`e964c4334e`](https://github.com/nodejs/node/commit/e964c4334e)] - **test\_runner**: do not error when getting `fullName` of root context (René) [#59377](https://github.com/nodejs/node/pull/59377) +* \[[`e076f7857c`](https://github.com/nodejs/node/commit/e076f7857c)] - **test\_runner**: add option to rerun only failed tests (Moshe Atlow) [#59443](https://github.com/nodejs/node/pull/59443) +* \[[`eb8b1939a4`](https://github.com/nodejs/node/commit/eb8b1939a4)] - **test\_runner**: fix isSkipped check in junit (Sungwon) [#59414](https://github.com/nodejs/node/pull/59414) +* \[[`4e02ea1c52`](https://github.com/nodejs/node/commit/4e02ea1c52)] - **tools**: update gyp-next to 0.20.3 (Node.js GitHub Bot) [#59603](https://github.com/nodejs/node/pull/59603) +* \[[`99da7fbe11`](https://github.com/nodejs/node/commit/99da7fbe11)] - **tools**: avoid parsing test files twice (Pietro Marchini) [#59526](https://github.com/nodejs/node/pull/59526) +* \[[`9a6a8e319b`](https://github.com/nodejs/node/commit/9a6a8e319b)] - **tools**: update coverage GitHub Actions to fixed version (Rich Trott) [#59512](https://github.com/nodejs/node/pull/59512) +* \[[`8d28236aff`](https://github.com/nodejs/node/commit/8d28236aff)] - **tools**: fix return value of try\_check\_compiler (theanarkh) [#59434](https://github.com/nodejs/node/pull/59434) +* \[[`52ab64ec3a`](https://github.com/nodejs/node/commit/52ab64ec3a)] - **tools**: bump @eslint/plugin-kit from 0.3.3 to 0.3.4 in /tools/eslint (dependabot\[bot]) [#59271](https://github.com/nodejs/node/pull/59271) +* \[[`baa22893bb`](https://github.com/nodejs/node/commit/baa22893bb)] - **typings**: add missing URLBinding methods (성우현 | Woohyun Sung) [#59468](https://github.com/nodejs/node/pull/59468) +* \[[`b68e0d1eca`](https://github.com/nodejs/node/commit/b68e0d1eca)] - **util**: fix error's namespaced node\_modules highlighting using inspect (Ruben Bridgewater) [#59446](https://github.com/nodejs/node/pull/59446) +* \[[`15ae21b88a`](https://github.com/nodejs/node/commit/15ae21b88a)] - **util**: add some additional error classes to `wellKnownPrototypes` (Mark S. Miller) [#59456](https://github.com/nodejs/node/pull/59456) +* \[[`c38b7cfa35`](https://github.com/nodejs/node/commit/c38b7cfa35)] - **worker**: fix worker name with \0 (theanarkh) [#59214](https://github.com/nodejs/node/pull/59214) +* \[[`f54ace694a`](https://github.com/nodejs/node/commit/f54ace694a)] - **worker**: add worker name to report (theanarkh) [#58935](https://github.com/nodejs/node/pull/58935) + ## 2025-08-14, Version 24.6.0 (Current), @RafaelGSS diff --git a/doc/contributing/strategic-initiatives.md b/doc/contributing/strategic-initiatives.md index 0643b9254ea7ee..b148f7c95c8d10 100644 --- a/doc/contributing/strategic-initiatives.md +++ b/doc/contributing/strategic-initiatives.md @@ -14,7 +14,7 @@ agenda to ensure they are active and have the support they need. | V8 Currency | [Michaël Zasso][targos] | | | Next-10 | [Michael Dawson][mhdawson] | | | Single executable apps | [Darshan Sen][RaisinTen] | | -| Performance | | | +| Performance | [Rafael Gonzaga][RafaelGSS] | | | Primordials | [Benjamin Gruenbaum][benjamingr] | |
@@ -39,6 +39,7 @@ agenda to ensure they are active and have the support they need.
+[RafaelGSS]: https://github.com/RafaelGSS [RaisinTen]: https://github.com/RaisinTen [benjamingr]: https://github.com/benjamingr [jasnell]: https://github.com/jasnell diff --git a/doc/node-config-schema.json b/doc/node-config-schema.json index ba83550f09426c..4340c95b952d2a 100644 --- a/doc/node-config-schema.json +++ b/doc/node-config-schema.json @@ -443,6 +443,9 @@ } ] }, + "test-rerun-failures": { + "type": "string" + }, "test-shard": { "type": "string" }, @@ -695,6 +698,9 @@ } ] }, + "test-rerun-failures": { + "type": "string" + }, "test-shard": { "type": "string" }, diff --git a/doc/node.1 b/doc/node.1 index 3e9b376a0739b2..6210cbf42b26d4 100644 --- a/doc/node.1 +++ b/doc/node.1 @@ -490,6 +490,10 @@ A test reporter to use when running tests. .It Fl -test-reporter-destination The destination for the corresponding test reporter. . +.It Fl -test-rerun-failures +Configures the tests runner to persist the state of tests to allow +rerunning only failed tests. +. .It Fl -test-only Configures the test runner to only execute top level tests that have the `only` option set. diff --git a/lib/_http_agent.js b/lib/_http_agent.js index ddb6cd7d9a4c03..28006353a35f34 100644 --- a/lib/_http_agent.js +++ b/lib/_http_agent.js @@ -22,6 +22,7 @@ 'use strict'; const { + NumberIsFinite, NumberParseInt, ObjectKeys, ObjectSetPrototypeOf, @@ -60,8 +61,6 @@ const kOnKeylog = Symbol('onkeylog'); const kRequestOptions = Symbol('requestOptions'); const kRequestAsyncResource = Symbol('requestAsyncResource'); -// TODO(jazelly): make this configurable -const HTTP_AGENT_KEEP_ALIVE_TIMEOUT_BUFFER = 1000; // New Agent code. // The largest departure from the previous implementation is that @@ -114,6 +113,14 @@ function Agent(options) { this.scheduling = this.options.scheduling || 'lifo'; this.maxTotalSockets = this.options.maxTotalSockets; this.totalSocketCount = 0; + + this.agentKeepAliveTimeoutBuffer = + typeof this.options.agentKeepAliveTimeoutBuffer === 'number' && + this.options.agentKeepAliveTimeoutBuffer >= 0 && + NumberIsFinite(this.options.agentKeepAliveTimeoutBuffer) ? + this.options.agentKeepAliveTimeoutBuffer : + 1000; + const proxyEnv = this.options.proxyEnv; if (typeof proxyEnv === 'object' && proxyEnv !== null) { this[kProxyConfig] = parseProxyConfigFromEnv(proxyEnv, this.protocol, this.keepAlive); @@ -559,7 +566,7 @@ Agent.prototype.keepSocketAlive = function keepSocketAlive(socket) { if (hint) { // Let the timer expire before the announced timeout to reduce // the likelihood of ECONNRESET errors - let serverHintTimeout = (NumberParseInt(hint) * 1000) - HTTP_AGENT_KEEP_ALIVE_TIMEOUT_BUFFER; + let serverHintTimeout = (NumberParseInt(hint) * 1000) - this.agentKeepAliveTimeoutBuffer; serverHintTimeout = serverHintTimeout > 0 ? serverHintTimeout : 0; if (serverHintTimeout === 0) { // Cannot safely reuse the socket because the server timeout is diff --git a/lib/async_hooks.js b/lib/async_hooks.js index 4216ee3f240c48..1eb00871e2ebe6 100644 --- a/lib/async_hooks.js +++ b/lib/async_hooks.js @@ -262,10 +262,10 @@ class AsyncResource { enumerable: true, get: deprecate(function() { return self; - }, 'The asyncResource property on bound functions is deprecated', 'DEP0172', false), + }, 'The asyncResource property on bound functions is deprecated', 'DEP0172', false, false), set: deprecate(function(val) { self = val; - }, 'The asyncResource property on bound functions is deprecated', 'DEP0172', false), + }, 'The asyncResource property on bound functions is deprecated', 'DEP0172', false, false), }, }); return bound; diff --git a/lib/child_process.js b/lib/child_process.js index baa0a56d1ecdc7..17c6b69c118a75 100644 --- a/lib/child_process.js +++ b/lib/child_process.js @@ -648,7 +648,8 @@ function normalizeSpawnArguments(file, args, options) { 'DEP0190'); emittedDEP0190Already = true; } - const command = ArrayPrototypeJoin([file, ...args], ' '); + + const command = args.length > 0 ? `${file} ${ArrayPrototypeJoin(args, ' ')}` : file; // Set the shell, switches, and commands. if (process.platform === 'win32') { if (typeof options.shell === 'string') diff --git a/lib/crypto.js b/lib/crypto.js index 8e0c39f756a72b..4290989f3cd6b1 100644 --- a/lib/crypto.js +++ b/lib/crypto.js @@ -57,6 +57,10 @@ const { randomInt, randomUUID, } = require('internal/crypto/random'); +const { + argon2, + argon2Sync, +} = require('internal/crypto/argon2'); const { pbkdf2, pbkdf2Sync, @@ -118,6 +122,10 @@ const { secureHeapUsed, } = require('internal/crypto/util'); const Certificate = require('internal/crypto/certificate'); +const { + encapsulate, + decapsulate, +} = require('internal/crypto/kem'); let webcrypto; function lazyWebCrypto() { @@ -171,6 +179,8 @@ function createVerify(algorithm, options) { module.exports = { // Methods + argon2, + argon2Sync, checkPrime, checkPrimeSync, createCipheriv, @@ -219,6 +229,8 @@ module.exports = { setFips, verify: verifyOneShot, hash, + encapsulate, + decapsulate, // Classes Certificate, diff --git a/lib/inspector.js b/lib/inspector.js index 4bf3ef7b61e99f..a6b835bbc7d529 100644 --- a/lib/inspector.js +++ b/lib/inspector.js @@ -219,6 +219,10 @@ const Network = { loadingFailed: (params) => broadcastToFrontend('Network.loadingFailed', params), dataSent: (params) => broadcastToFrontend('Network.dataSent', params), dataReceived: (params) => broadcastToFrontend('Network.dataReceived', params), + webSocketCreated: (params) => broadcastToFrontend('Network.webSocketCreated', params), + webSocketClosed: (params) => broadcastToFrontend('Network.webSocketClosed', params), + webSocketHandshakeResponseReceived: + (params) => broadcastToFrontend('Network.webSocketHandshakeResponseReceived', params), }; const NetworkResources = { diff --git a/lib/internal/async_hooks.js b/lib/internal/async_hooks.js index f4a45c99858c55..5f28ea6a0e8193 100644 --- a/lib/internal/async_hooks.js +++ b/lib/internal/async_hooks.js @@ -189,7 +189,7 @@ function lookupPublicResource(resource) { // Used by C++ to call all init() callbacks. Because some state can be setup // from C++ there's no need to perform all the same operations as in // emitInitScript. -function emitInitNative(asyncId, type, triggerAsyncId, resource) { +function emitInitNative(asyncId, type, triggerAsyncId, resource, isPromiseHook) { active_hooks.call_depth += 1; resource = lookupPublicResource(resource); // Use a single try/catch for all hooks to avoid setting up one per iteration. @@ -199,6 +199,10 @@ function emitInitNative(asyncId, type, triggerAsyncId, resource) { // eslint-disable-next-line no-var for (var i = 0; i < active_hooks.array.length; i++) { if (typeof active_hooks.array[i][init_symbol] === 'function') { + if (isPromiseHook && + active_hooks.array[i][kNoPromiseHook]) { + continue; + } active_hooks.array[i][init_symbol]( asyncId, type, triggerAsyncId, resource, @@ -222,7 +226,7 @@ function emitInitNative(asyncId, type, triggerAsyncId, resource) { // Called from native. The asyncId stack handling is taken care of there // before this is called. -function emitHook(symbol, asyncId) { +function emitHook(symbol, asyncId, isPromiseHook) { active_hooks.call_depth += 1; // Use a single try/catch for all hook to avoid setting up one per // iteration. @@ -232,6 +236,10 @@ function emitHook(symbol, asyncId) { // eslint-disable-next-line no-var for (var i = 0; i < active_hooks.array.length; i++) { if (typeof active_hooks.array[i][symbol] === 'function') { + if (isPromiseHook && + active_hooks.array[i][kNoPromiseHook]) { + continue; + } active_hooks.array[i][symbol](asyncId); } } @@ -321,7 +329,7 @@ function promiseInitHook(promise, parent) { trackPromise(promise, parent); const asyncId = promise[async_id_symbol]; const triggerAsyncId = promise[trigger_async_id_symbol]; - emitInitScript(asyncId, 'PROMISE', triggerAsyncId, promise); + emitInitScript(asyncId, 'PROMISE', triggerAsyncId, promise, true); } function promiseInitHookWithDestroyTracking(promise, parent) { @@ -339,14 +347,14 @@ function promiseBeforeHook(promise) { trackPromise(promise); const asyncId = promise[async_id_symbol]; const triggerId = promise[trigger_async_id_symbol]; - emitBeforeScript(asyncId, triggerId, promise); + emitBeforeScript(asyncId, triggerId, promise, true); } function promiseAfterHook(promise) { trackPromise(promise); const asyncId = promise[async_id_symbol]; if (hasHooks(kAfter)) { - emitAfterNative(asyncId); + emitAfterNative(asyncId, true); } if (asyncId === executionAsyncId()) { // This condition might not be true if async_hooks was enabled during @@ -361,7 +369,7 @@ function promiseAfterHook(promise) { function promiseResolveHook(promise) { trackPromise(promise); const asyncId = promise[async_id_symbol]; - emitPromiseResolveNative(asyncId); + emitPromiseResolveNative(asyncId, true); } let wantPromiseHook = false; @@ -492,7 +500,7 @@ function promiseResolveHooksExist() { } -function emitInitScript(asyncId, type, triggerAsyncId, resource) { +function emitInitScript(asyncId, type, triggerAsyncId, resource, isPromiseHook = false) { // Short circuit all checks for the common case. Which is that no hooks have // been set. Do this to remove performance impact for embedders (and core). if (!hasHooks(kInit)) @@ -502,15 +510,15 @@ function emitInitScript(asyncId, type, triggerAsyncId, resource) { triggerAsyncId = getDefaultTriggerAsyncId(); } - emitInitNative(asyncId, type, triggerAsyncId, resource); + emitInitNative(asyncId, type, triggerAsyncId, resource, isPromiseHook); } -function emitBeforeScript(asyncId, triggerAsyncId, resource) { +function emitBeforeScript(asyncId, triggerAsyncId, resource, isPromiseHook = false) { pushAsyncContext(asyncId, triggerAsyncId, resource); if (hasHooks(kBefore)) - emitBeforeNative(asyncId); + emitBeforeNative(asyncId, isPromiseHook); } diff --git a/lib/internal/crypto/aes.js b/lib/internal/crypto/aes.js index b7d1abf4a85daf..0abffe85c9881b 100644 --- a/lib/internal/crypto/aes.js +++ b/lib/internal/crypto/aes.js @@ -18,14 +18,17 @@ const { kKeyVariantAES_CBC_128, kKeyVariantAES_GCM_128, kKeyVariantAES_KW_128, + kKeyVariantAES_OCB_128, kKeyVariantAES_CTR_192, kKeyVariantAES_CBC_192, kKeyVariantAES_GCM_192, kKeyVariantAES_KW_192, + kKeyVariantAES_OCB_192, kKeyVariantAES_CTR_256, kKeyVariantAES_CBC_256, kKeyVariantAES_GCM_256, kKeyVariantAES_KW_256, + kKeyVariantAES_OCB_256, kWebCryptoCipherDecrypt, kWebCryptoCipherEncrypt, } = internalBinding('crypto'); @@ -47,6 +50,7 @@ const { InternalCryptoKey, SecretKeyObject, createSecretKey, + kAlgorithm, } = require('internal/crypto/keys'); const { @@ -61,6 +65,7 @@ function getAlgorithmName(name, length) { case 'AES-CTR': return `A${length}CTR`; case 'AES-GCM': return `A${length}GCM`; case 'AES-KW': return `A${length}KW`; + case 'AES-OCB': return `A${length}OCB`; } } @@ -99,6 +104,13 @@ function getVariant(name, length) { case 256: return kKeyVariantAES_KW_256; } break; + case 'AES-OCB': + switch (length) { + case 128: return kKeyVariantAES_OCB_128; + case 192: return kKeyVariantAES_OCB_192; + case 256: return kKeyVariantAES_OCB_256; + } + break; } } @@ -108,7 +120,7 @@ function asyncAesCtrCipher(mode, key, data, algorithm) { mode, key[kKeyObject][kHandle], data, - getVariant('AES-CTR', key.algorithm.length), + getVariant('AES-CTR', key[kAlgorithm].length), algorithm.counter, algorithm.length)); } @@ -119,7 +131,7 @@ function asyncAesCbcCipher(mode, key, data, algorithm) { mode, key[kKeyObject][kHandle], data, - getVariant('AES-CBC', key.algorithm.length), + getVariant('AES-CBC', key[kAlgorithm].length), algorithm.iv)); } @@ -129,7 +141,7 @@ function asyncAesKwCipher(mode, key, data) { mode, key[kKeyObject][kHandle], data, - getVariant('AES-KW', key.algorithm.length))); + getVariant('AES-KW', key[kAlgorithm].length))); } function asyncAesGcmCipher(mode, key, data, algorithm) { @@ -166,7 +178,44 @@ function asyncAesGcmCipher(mode, key, data, algorithm) { mode, key[kKeyObject][kHandle], data, - getVariant('AES-GCM', key.algorithm.length), + getVariant('AES-GCM', key[kAlgorithm].length), + algorithm.iv, + tag, + algorithm.additionalData)); +} + +function asyncAesOcbCipher(mode, key, data, algorithm) { + const { tagLength = 128 } = algorithm; + + const tagByteLength = tagLength / 8; + let tag; + switch (mode) { + case kWebCryptoCipherDecrypt: { + const slice = ArrayBufferIsView(data) ? + TypedArrayPrototypeSlice : ArrayBufferPrototypeSlice; + tag = slice(data, -tagByteLength); + + // Similar to GCM, OCB requires the tag to be present for decryption + if (tagByteLength > tag.byteLength) { + return PromiseReject(lazyDOMException( + 'The provided data is too small.', + 'OperationError')); + } + + data = slice(data, 0, -tagByteLength); + break; + } + case kWebCryptoCipherEncrypt: + tag = tagByteLength; + break; + } + + return jobPromise(() => new AESCipherJob( + kCryptoJobAsync, + mode, + key[kKeyObject][kHandle], + data, + getVariant('AES-OCB', key.algorithm.length), algorithm.iv, tag, algorithm.additionalData)); @@ -177,6 +226,7 @@ function aesCipher(mode, key, data, algorithm) { case 'AES-CTR': return asyncAesCtrCipher(mode, key, data, algorithm); case 'AES-CBC': return asyncAesCbcCipher(mode, key, data, algorithm); case 'AES-GCM': return asyncAesGcmCipher(mode, key, data, algorithm); + case 'AES-OCB': return asyncAesOcbCipher(mode, key, data, algorithm); case 'AES-KW': return asyncAesKwCipher(mode, key, data); } } @@ -235,7 +285,11 @@ function aesImportKey( keyObject = keyData; break; } + case 'raw-secret': case 'raw': { + if (format === 'raw' && name === 'AES-OCB') { + return undefined; + } validateKeyLength(keyData.byteLength * 8); keyObject = createSecretKey(keyData); break; diff --git a/lib/internal/crypto/argon2.js b/lib/internal/crypto/argon2.js new file mode 100644 index 00000000000000..a17b9ed643d8b5 --- /dev/null +++ b/lib/internal/crypto/argon2.js @@ -0,0 +1,185 @@ +'use strict'; + +const { + FunctionPrototypeCall, + MathPow, + Uint8Array, +} = primordials; + +const { Buffer } = require('buffer'); + +const { + Argon2Job, + kCryptoJobAsync, + kCryptoJobSync, + kTypeArgon2d, + kTypeArgon2i, + kTypeArgon2id, +} = internalBinding('crypto'); + +const { getArrayBufferOrView } = require('internal/crypto/util'); +const { + validateString, + validateFunction, + validateInteger, + validateObject, + validateOneOf, + validateUint32, +} = require('internal/validators'); + +const { + codes: { + ERR_CRYPTO_ARGON2_NOT_SUPPORTED, + }, +} = require('internal/errors'); + +/** + * @param {'argon2d' | 'argon2i' | 'argon2id'} algorithm + * @param {object} parameters + * @param {ArrayBufferLike} parameters.message + * @param {ArrayBufferLike} parameters.nonce + * @param {number} parameters.parallelism + * @param {number} parameters.tagLength + * @param {number} parameters.memory + * @param {number} parameters.passes + * @param {ArrayBufferLike} [parameters.secret] + * @param {ArrayBufferLike} [parameters.associatedData] + * @param {Function} callback + */ +function argon2(algorithm, parameters, callback) { + parameters = check(algorithm, parameters); + + validateFunction(callback, 'callback'); + + const job = new Argon2Job( + kCryptoJobAsync, + parameters.message, + parameters.nonce, + parameters.parallelism, + parameters.tagLength, + parameters.memory, + parameters.passes, + parameters.secret, + parameters.associatedData, + parameters.type); + + job.ondone = (error, result) => { + if (error !== undefined) + return FunctionPrototypeCall(callback, job, error); + const buf = Buffer.from(result); + return FunctionPrototypeCall(callback, job, null, buf); + }; + + job.run(); +} + +/** + * @param {'argon2d' | 'argon2i' | 'argon2id'} algorithm + * @param {object} parameters + * @param {ArrayBufferLike} parameters.message + * @param {ArrayBufferLike} parameters.nonce + * @param {number} parameters.parallelism + * @param {number} parameters.tagLength + * @param {number} parameters.memory + * @param {number} parameters.passes + * @param {ArrayBufferLike} [parameters.secret] + * @param {ArrayBufferLike} [parameters.associatedData] + * @returns {Buffer} + */ +function argon2Sync(algorithm, parameters) { + parameters = check(algorithm, parameters); + + const job = new Argon2Job( + kCryptoJobSync, + parameters.message, + parameters.nonce, + parameters.parallelism, + parameters.tagLength, + parameters.memory, + parameters.passes, + parameters.secret, + parameters.associatedData, + parameters.type); + + const { 0: err, 1: result } = job.run(); + + if (err !== undefined) + throw err; + + return Buffer.from(result); +} + +/** + * @param {'argon2d' | 'argon2i' | 'argon2id'} algorithm + * @param {object} parameters + * @param {ArrayBufferLike} parameters.message + * @param {ArrayBufferLike} parameters.nonce + * @param {number} parameters.parallelism + * @param {number} parameters.tagLength + * @param {number} parameters.memory + * @param {number} parameters.passes + * @param {ArrayBufferLike} [parameters.secret] + * @param {ArrayBufferLike} [parameters.associatedData] + * @returns {object} + */ +function check(algorithm, parameters) { + if (Argon2Job === undefined) + throw new ERR_CRYPTO_ARGON2_NOT_SUPPORTED(); + + validateString(algorithm, 'algorithm'); + validateOneOf(algorithm, 'algorithm', ['argon2d', 'argon2i', 'argon2id']); + + let type; + switch (algorithm) { + case 'argon2d': + type = kTypeArgon2d; + break; + case 'argon2i': + type = kTypeArgon2i; + break; + case 'argon2id': + type = kTypeArgon2id; + break; + default: // unreachable + throw new ERR_CRYPTO_ARGON2_NOT_SUPPORTED(); + } + + validateObject(parameters, 'parameters'); + + const { parallelism, tagLength, memory, passes } = parameters; + const MAX_POSITIVE_UINT_32 = MathPow(2, 32) - 1; + + const message = getArrayBufferOrView(parameters.message, 'parameters.message'); + validateInteger(message.byteLength, 'parameters.message.byteLength', 0, MAX_POSITIVE_UINT_32); + + const nonce = getArrayBufferOrView(parameters.nonce, 'parameters.nonce'); + validateInteger(nonce.byteLength, 'parameters.nonce.byteLength', 8, MAX_POSITIVE_UINT_32); + + validateInteger(parallelism, 'parameters.parallelism', 1, MathPow(2, 24) - 1); + validateInteger(tagLength, 'parameters.tagLength', 4, MAX_POSITIVE_UINT_32); + validateInteger(memory, 'parameters.memory', 8 * parallelism, MAX_POSITIVE_UINT_32); + validateUint32(passes, 'parameters.passes', true); + + let secret; + if (parameters.secret === undefined) { + secret = new Uint8Array(0); + } else { + secret = getArrayBufferOrView(parameters.secret); + validateInteger(secret.byteLength, 'parameters.secret.byteLength', 0, MAX_POSITIVE_UINT_32); + } + + let associatedData; + if (parameters.associatedData === undefined) { + associatedData = new Uint8Array(0); + } else { + associatedData = getArrayBufferOrView(parameters.associatedData); + validateInteger(associatedData.byteLength, 'parameters.associatedData.byteLength', 0, MAX_POSITIVE_UINT_32); + } + + return { message, nonce, secret, associatedData, tagLength, passes, parallelism, memory, type }; +} + +module.exports = { + argon2, + argon2Sync, +}; diff --git a/lib/internal/crypto/cfrg.js b/lib/internal/crypto/cfrg.js index e8af5750a865fd..97272ab1672021 100644 --- a/lib/internal/crypto/cfrg.js +++ b/lib/internal/crypto/cfrg.js @@ -47,6 +47,7 @@ const { PublicKeyObject, createPrivateKey, createPublicKey, + kKeyType, } = require('internal/crypto/keys'); const generateKeyPair = promisify(_generateKeyPair); @@ -343,7 +344,7 @@ function eddsaSignVerify(key, data, algorithm, signature) { const mode = signature === undefined ? kSignJobModeSign : kSignJobModeVerify; const type = mode === kSignJobModeSign ? 'private' : 'public'; - if (key.type !== type) + if (key[kKeyType] !== type) throw lazyDOMException(`Key must be a ${type} key`, 'InvalidAccessError'); return jobPromise(() => new SignJob( diff --git a/lib/internal/crypto/chacha20_poly1305.js b/lib/internal/crypto/chacha20_poly1305.js new file mode 100644 index 00000000000000..bcc778b24d7738 --- /dev/null +++ b/lib/internal/crypto/chacha20_poly1305.js @@ -0,0 +1,191 @@ +'use strict'; + +const { + ArrayBufferIsView, + ArrayBufferPrototypeSlice, + ArrayFrom, + PromiseReject, + SafeSet, + TypedArrayPrototypeSlice, +} = primordials; + +const { + ChaCha20Poly1305CipherJob, + KeyObjectHandle, + kCryptoJobAsync, + kWebCryptoCipherDecrypt, + kWebCryptoCipherEncrypt, +} = internalBinding('crypto'); + +const { + hasAnyNotIn, + jobPromise, + validateKeyOps, + kHandle, + kKeyObject, +} = require('internal/crypto/util'); + +const { + lazyDOMException, + promisify, +} = require('internal/util'); + +const { + InternalCryptoKey, + SecretKeyObject, + createSecretKey, +} = require('internal/crypto/keys'); + +const { + randomBytes: _randomBytes, +} = require('internal/crypto/random'); + +const randomBytes = promisify(_randomBytes); + +function validateKeyLength(length) { + if (length !== 256) + throw lazyDOMException('Invalid key length', 'DataError'); +} + +function c20pCipher(mode, key, data, algorithm) { + let tag; + switch (mode) { + case kWebCryptoCipherDecrypt: { + const slice = ArrayBufferIsView(data) ? + TypedArrayPrototypeSlice : ArrayBufferPrototypeSlice; + + if (data.byteLength < 16) { + return PromiseReject(lazyDOMException( + 'The provided data is too small.', + 'OperationError')); + } + + tag = slice(data, -16); + data = slice(data, 0, -16); + break; + } + case kWebCryptoCipherEncrypt: + tag = 16; + break; + } + + return jobPromise(() => new ChaCha20Poly1305CipherJob( + kCryptoJobAsync, + mode, + key[kKeyObject][kHandle], + data, + algorithm.iv, + tag, + algorithm.additionalData)); +} + +async function c20pGenerateKey(algorithm, extractable, keyUsages) { + const { name } = algorithm; + + const checkUsages = ['encrypt', 'decrypt', 'wrapKey', 'unwrapKey']; + + const usagesSet = new SafeSet(keyUsages); + if (hasAnyNotIn(usagesSet, checkUsages)) { + throw lazyDOMException( + `Unsupported key usage for a ${algorithm.name} key`, + 'SyntaxError'); + } + + const keyData = await randomBytes(32).catch((err) => { + throw lazyDOMException( + 'The operation failed for an operation-specific reason' + + `[${err.message}]`, + { name: 'OperationError', cause: err }); + }); + + return new InternalCryptoKey( + createSecretKey(keyData), + { name }, + ArrayFrom(usagesSet), + extractable); +} + +function c20pImportKey( + algorithm, + format, + keyData, + extractable, + keyUsages) { + const { name } = algorithm; + const checkUsages = ['encrypt', 'decrypt', 'wrapKey', 'unwrapKey']; + + const usagesSet = new SafeSet(keyUsages); + if (hasAnyNotIn(usagesSet, checkUsages)) { + throw lazyDOMException( + `Unsupported key usage for a ${algorithm.name} key`, + 'SyntaxError'); + } + + let keyObject; + switch (format) { + case 'KeyObject': { + keyObject = keyData; + break; + } + case 'raw-secret': { + keyObject = createSecretKey(keyData); + break; + } + case 'jwk': { + if (!keyData.kty) + throw lazyDOMException('Invalid keyData', 'DataError'); + + if (keyData.kty !== 'oct') + throw lazyDOMException('Invalid JWK "kty" Parameter', 'DataError'); + + if (usagesSet.size > 0 && + keyData.use !== undefined && + keyData.use !== 'enc') { + throw lazyDOMException('Invalid JWK "use" Parameter', 'DataError'); + } + + validateKeyOps(keyData.key_ops, usagesSet); + + if (keyData.ext !== undefined && + keyData.ext === false && + extractable === true) { + throw lazyDOMException( + 'JWK "ext" Parameter and extractable mismatch', + 'DataError'); + } + + const handle = new KeyObjectHandle(); + try { + handle.initJwk(keyData); + } catch (err) { + throw lazyDOMException( + 'Invalid keyData', { name: 'DataError', cause: err }); + } + + if (keyData.alg !== undefined && keyData.alg !== 'C20P') { + throw lazyDOMException( + 'JWK "alg" does not match the requested algorithm', + 'DataError'); + } + + keyObject = new SecretKeyObject(handle); + break; + } + default: + return undefined; + } + + validateKeyLength(keyObject.symmetricKeySize * 8); + + return new InternalCryptoKey( + keyObject, + { name }, + keyUsages, + extractable); +} + +module.exports = { + c20pCipher, + c20pGenerateKey, + c20pImportKey, +}; diff --git a/lib/internal/crypto/diffiehellman.js b/lib/internal/crypto/diffiehellman.js index 24ff71fca83f31..1712996f670dbe 100644 --- a/lib/internal/crypto/diffiehellman.js +++ b/lib/internal/crypto/diffiehellman.js @@ -51,6 +51,8 @@ const { const { KeyObject, + kAlgorithm, + kKeyType, } = require('internal/crypto/keys'); const { @@ -325,20 +327,20 @@ let masks; async function ecdhDeriveBits(algorithm, baseKey, length) { const { 'public': key } = algorithm; - if (baseKey.type !== 'private') { + if (baseKey[kKeyType] !== 'private') { throw lazyDOMException( 'baseKey must be a private key', 'InvalidAccessError'); } - if (key.algorithm.name !== baseKey.algorithm.name) { + if (key[kAlgorithm].name !== baseKey[kAlgorithm].name) { throw lazyDOMException( 'The public and private keys must be of the same type', 'InvalidAccessError'); } if ( - key.algorithm.name === 'ECDH' && - key.algorithm.namedCurve !== baseKey.algorithm.namedCurve + key[kAlgorithm].name === 'ECDH' && + key[kAlgorithm].namedCurve !== baseKey[kAlgorithm].namedCurve ) { throw lazyDOMException('Named curve mismatch', 'InvalidAccessError'); } diff --git a/lib/internal/crypto/ec.js b/lib/internal/crypto/ec.js index f4ea317b86ee73..52791412835300 100644 --- a/lib/internal/crypto/ec.js +++ b/lib/internal/crypto/ec.js @@ -41,6 +41,7 @@ const { PublicKeyObject, createPrivateKey, createPublicKey, + kKeyType, } = require('internal/crypto/keys'); const generateKeyPair = promisify(_generateKeyPair); @@ -284,7 +285,7 @@ function ecdsaSignVerify(key, data, { name, hash }, signature) { const mode = signature === undefined ? kSignJobModeSign : kSignJobModeVerify; const type = mode === kSignJobModeSign ? 'private' : 'public'; - if (key.type !== type) + if (key[kKeyType] !== type) throw lazyDOMException(`Key must be a ${type} key`, 'InvalidAccessError'); const hashname = normalizeHashName(hash.name); diff --git a/lib/internal/crypto/hash.js b/lib/internal/crypto/hash.js index 9937dbceb2c4d7..e4d94da1c5ee96 100644 --- a/lib/internal/crypto/hash.js +++ b/lib/internal/crypto/hash.js @@ -211,10 +211,21 @@ async function asyncDigest(algorithm, data) { case 'SHA-384': // Fall through case 'SHA-512': + // Fall through + case 'SHA3-256': + // Fall through + case 'SHA3-384': + // Fall through + case 'SHA3-512': + // Fall through + case 'cSHAKE128': + // Fall through + case 'cSHAKE256': return jobPromise(() => new HashJob( kCryptoJobAsync, normalizeHashName(algorithm.name), - data)); + data, + algorithm.length)); } throw lazyDOMException('Unrecognized algorithm name', 'NotSupportedError'); diff --git a/lib/internal/crypto/hashnames.js b/lib/internal/crypto/hashnames.js index 7af2091d84de39..7a625c47e2f4b2 100644 --- a/lib/internal/crypto/hashnames.js +++ b/lib/internal/crypto/hashnames.js @@ -17,7 +17,7 @@ const kHashContextJwkHmac = 6; // make it easier in the code. const kHashNames = { - sha1: { + 'sha1': { [kHashContextNode]: 'sha1', [kHashContextWebCrypto]: 'SHA-1', [kHashContextJwkRsa]: 'RS1', @@ -25,7 +25,7 @@ const kHashNames = { [kHashContextJwkRsaOaep]: 'RSA-OAEP', [kHashContextJwkHmac]: 'HS1', }, - sha256: { + 'sha256': { [kHashContextNode]: 'sha256', [kHashContextWebCrypto]: 'SHA-256', [kHashContextJwkRsa]: 'RS256', @@ -33,7 +33,7 @@ const kHashNames = { [kHashContextJwkRsaOaep]: 'RSA-OAEP-256', [kHashContextJwkHmac]: 'HS256', }, - sha384: { + 'sha384': { [kHashContextNode]: 'sha384', [kHashContextWebCrypto]: 'SHA-384', [kHashContextJwkRsa]: 'RS384', @@ -41,7 +41,7 @@ const kHashNames = { [kHashContextJwkRsaOaep]: 'RSA-OAEP-384', [kHashContextJwkHmac]: 'HS384', }, - sha512: { + 'sha512': { [kHashContextNode]: 'sha512', [kHashContextWebCrypto]: 'SHA-512', [kHashContextJwkRsa]: 'RS512', @@ -49,6 +49,26 @@ const kHashNames = { [kHashContextJwkRsaOaep]: 'RSA-OAEP-512', [kHashContextJwkHmac]: 'HS512', }, + 'shake128': { + [kHashContextNode]: 'shake128', + [kHashContextWebCrypto]: 'cSHAKE128', + }, + 'shake256': { + [kHashContextNode]: 'shake256', + [kHashContextWebCrypto]: 'cSHAKE256', + }, + 'sha3-256': { + [kHashContextNode]: 'sha3-256', + [kHashContextWebCrypto]: 'SHA3-256', + }, + 'sha3-384': { + [kHashContextNode]: 'sha3-384', + [kHashContextWebCrypto]: 'SHA3-384', + }, + 'sha3-512': { + [kHashContextNode]: 'sha3-512', + [kHashContextWebCrypto]: 'SHA3-512', + }, }; { diff --git a/lib/internal/crypto/hkdf.js b/lib/internal/crypto/hkdf.js index 9b4d1469cc9df5..1a5e9ccd06813e 100644 --- a/lib/internal/crypto/hkdf.js +++ b/lib/internal/crypto/hkdf.js @@ -170,4 +170,5 @@ module.exports = { hkdf, hkdfSync, hkdfDeriveBits, + validateHkdfDeriveBitsLength, }; diff --git a/lib/internal/crypto/kem.js b/lib/internal/crypto/kem.js new file mode 100644 index 00000000000000..43c7bde52ea99f --- /dev/null +++ b/lib/internal/crypto/kem.js @@ -0,0 +1,112 @@ +'use strict'; + +const { + FunctionPrototypeCall, +} = primordials; + +const { + codes: { + ERR_CRYPTO_KEM_NOT_SUPPORTED, + }, +} = require('internal/errors'); + +const { + validateFunction, +} = require('internal/validators'); + +const { + kCryptoJobAsync, + kCryptoJobSync, + KEMDecapsulateJob, + KEMEncapsulateJob, +} = internalBinding('crypto'); + +const { + preparePrivateKey, + preparePublicOrPrivateKey, +} = require('internal/crypto/keys'); + +const { + getArrayBufferOrView, +} = require('internal/crypto/util'); + +function encapsulate(key, callback) { + if (!KEMEncapsulateJob) + throw new ERR_CRYPTO_KEM_NOT_SUPPORTED(); + + if (callback !== undefined) + validateFunction(callback, 'callback'); + + const { + data: keyData, + format: keyFormat, + type: keyType, + passphrase: keyPassphrase, + } = preparePublicOrPrivateKey(key); + + const job = new KEMEncapsulateJob( + callback ? kCryptoJobAsync : kCryptoJobSync, + keyData, + keyFormat, + keyType, + keyPassphrase); + + if (!callback) { + const { 0: err, 1: result } = job.run(); + if (err !== undefined) + throw err; + const { 0: sharedKey, 1: ciphertext } = result; + return { sharedKey, ciphertext }; + } + + job.ondone = (error, result) => { + if (error) return FunctionPrototypeCall(callback, job, error); + const { 0: sharedKey, 1: ciphertext } = result; + FunctionPrototypeCall(callback, job, null, { sharedKey, ciphertext }); + }; + job.run(); +} + +function decapsulate(key, ciphertext, callback) { + if (!KEMDecapsulateJob) + throw new ERR_CRYPTO_KEM_NOT_SUPPORTED(); + + if (callback !== undefined) + validateFunction(callback, 'callback'); + + const { + data: keyData, + format: keyFormat, + type: keyType, + passphrase: keyPassphrase, + } = preparePrivateKey(key); + + ciphertext = getArrayBufferOrView(ciphertext, 'ciphertext'); + + const job = new KEMDecapsulateJob( + callback ? kCryptoJobAsync : kCryptoJobSync, + keyData, + keyFormat, + keyType, + keyPassphrase, + ciphertext); + + if (!callback) { + const { 0: err, 1: result } = job.run(); + if (err !== undefined) + throw err; + + return result; + } + + job.ondone = (error, result) => { + if (error) return FunctionPrototypeCall(callback, job, error); + FunctionPrototypeCall(callback, job, null, result); + }; + job.run(); +} + +module.exports = { + encapsulate, + decapsulate, +}; diff --git a/lib/internal/crypto/keygen.js b/lib/internal/crypto/keygen.js index cb46b72c59a5ca..9c0aded1538f26 100644 --- a/lib/internal/crypto/keygen.js +++ b/lib/internal/crypto/keygen.js @@ -22,6 +22,9 @@ const { EVP_PKEY_ML_DSA_44, EVP_PKEY_ML_DSA_65, EVP_PKEY_ML_DSA_87, + EVP_PKEY_ML_KEM_1024, + EVP_PKEY_ML_KEM_512, + EVP_PKEY_ML_KEM_768, EVP_PKEY_X25519, EVP_PKEY_X448, OPENSSL_EC_NAMED_CURVE, @@ -173,6 +176,9 @@ const ids = { 'ml-dsa-44': EVP_PKEY_ML_DSA_44, 'ml-dsa-65': EVP_PKEY_ML_DSA_65, 'ml-dsa-87': EVP_PKEY_ML_DSA_87, + 'ml-kem-512': EVP_PKEY_ML_KEM_512, + 'ml-kem-768': EVP_PKEY_ML_KEM_768, + 'ml-kem-1024': EVP_PKEY_ML_KEM_1024, }; function createJob(mode, type, options) { @@ -294,6 +300,9 @@ function createJob(mode, type, options) { case 'ml-dsa-44': case 'ml-dsa-65': case 'ml-dsa-87': + case 'ml-kem-512': + case 'ml-kem-768': + case 'ml-kem-1024': { if (ids[type] === undefined) { throw new ERR_INVALID_ARG_VALUE('type', type, 'must be a supported key type'); diff --git a/lib/internal/crypto/keys.js b/lib/internal/crypto/keys.js index 60b4d26d35e967..c8b3d870ac5225 100644 --- a/lib/internal/crypto/keys.js +++ b/lib/internal/crypto/keys.js @@ -1,6 +1,7 @@ 'use strict'; const { + ArrayFrom, ArrayPrototypeSlice, ObjectDefineProperties, ObjectDefineProperty, @@ -81,6 +82,8 @@ const kAlgorithm = Symbol('kAlgorithm'); const kExtractable = Symbol('kExtractable'); const kKeyType = Symbol('kKeyType'); const kKeyUsages = Symbol('kKeyUsages'); +const kCachedAlgorithm = Symbol('kCachedAlgorithm'); +const kCachedKeyUsages = Symbol('kCachedKeyUsages'); // Key input contexts. const kConsumePublic = 0; @@ -196,9 +199,15 @@ const { case 'AES-GCM': // Fall through case 'AES-KW': + // Fall through + case 'AES-OCB': result = require('internal/crypto/aes') .aesImportKey(algorithm, 'KeyObject', this, extractable, keyUsages); break; + case 'ChaCha20-Poly1305': + result = require('internal/crypto/chacha20_poly1305') + .c20pImportKey(algorithm, 'KeyObject', this, extractable, keyUsages); + break; case 'HKDF': // Fall through case 'PBKDF2': @@ -213,7 +222,7 @@ const { throw lazyDOMException('Unrecognized algorithm name', 'NotSupportedError'); } - if (result.usages.length === 0) { + if (result[kKeyUsages].length === 0) { throw lazyDOMException( `Usages cannot be empty when importing a ${result.type} key.`, 'SyntaxError'); @@ -293,11 +302,27 @@ const { result = require('internal/crypto/cfrg') .cfrgImportKey('KeyObject', this, algorithm, extractable, keyUsages); break; + case 'ML-DSA-44': + // Fall through + case 'ML-DSA-65': + // Fall through + case 'ML-DSA-87': + result = require('internal/crypto/ml_dsa') + .mlDsaImportKey('KeyObject', this, algorithm, extractable, keyUsages); + break; + case 'ML-KEM-512': + // Fall through + case 'ML-KEM-768': + // Fall through + case 'ML-KEM-1024': + result = require('internal/crypto/ml_kem') + .mlKemImportKey('KeyObject', this, algorithm, extractable, keyUsages); + break; default: throw lazyDOMException('Unrecognized algorithm name', 'NotSupportedError'); } - if (result.type === 'private' && result.usages.length === 0) { + if (result.type === 'private' && result[kKeyUsages].length === 0) { throw lazyDOMException( `Usages cannot be empty when importing a ${result.type} key.`, 'SyntaxError'); @@ -553,7 +578,7 @@ function getKeyObjectHandleFromJwk(key, ctx) { const handle = new KeyObjectHandle(); const keyType = isPublic ? kKeyTypePublic : kKeyTypePrivate; - if (!handle.initMlDsaRaw(key.alg, keyData, keyType)) { + if (!handle.initPqcRaw(key.alg, keyData, keyType)) { throw new ERR_CRYPTO_INVALID_JWK(); } @@ -723,8 +748,8 @@ function prepareSecretKey(key, encoding, bufferOnly = false) { throw new ERR_CRYPTO_INVALID_KEY_OBJECT_TYPE(key.type, 'secret'); return key[kHandle]; } else if (isCryptoKey(key)) { - if (key.type !== 'secret') - throw new ERR_CRYPTO_INVALID_KEY_OBJECT_TYPE(key.type, 'secret'); + if (key[kKeyType] !== 'secret') + throw new ERR_CRYPTO_INVALID_KEY_OBJECT_TYPE(key[kKeyType], 'secret'); return key[kKeyObject][kHandle]; } } @@ -773,7 +798,7 @@ function createPrivateKey(key) { } function isKeyObject(obj) { - return obj != null && obj[kKeyType] !== undefined; + return obj != null && obj[kKeyType] !== undefined && obj[kKeyObject] === undefined; } // Our implementation of CryptoKey is a simple wrapper around a KeyObject @@ -797,17 +822,21 @@ class CryptoKey { }; return `CryptoKey ${inspect({ - type: this.type, - extractable: this.extractable, - algorithm: this.algorithm, - usages: this.usages, + type: this[kKeyType], + extractable: this[kExtractable], + algorithm: this[kAlgorithm], + usages: this[kKeyUsages], }, opts)}`; } + get [kKeyType]() { + return this[kKeyObject].type; + } + get type() { if (!(this instanceof CryptoKey)) throw new ERR_INVALID_THIS('CryptoKey'); - return this[kKeyObject].type; + return this[kKeyType]; } get extractable() { @@ -819,13 +848,19 @@ class CryptoKey { get algorithm() { if (!(this instanceof CryptoKey)) throw new ERR_INVALID_THIS('CryptoKey'); - return this[kAlgorithm]; + if (!this[kCachedAlgorithm]) { + this[kCachedAlgorithm] ??= { ...this[kAlgorithm] }; + this[kCachedAlgorithm].hash &&= { ...this[kCachedAlgorithm].hash }; + this[kCachedAlgorithm].publicExponent &&= new Uint8Array(this[kCachedAlgorithm].publicExponent); + } + return this[kCachedAlgorithm]; } get usages() { if (!(this instanceof CryptoKey)) throw new ERR_INVALID_THIS('CryptoKey'); - return this[kKeyUsages]; + this[kCachedKeyUsages] ??= ArrayFrom(this[kKeyUsages]); + return this[kCachedKeyUsages]; } } @@ -996,4 +1031,8 @@ module.exports = { isKeyObject, isCryptoKey, importGenericSecretKey, + kAlgorithm, + kExtractable, + kKeyType, + kKeyUsages, }; diff --git a/lib/internal/crypto/mac.js b/lib/internal/crypto/mac.js index 0f9b1f9618d260..ed30a64ba239ea 100644 --- a/lib/internal/crypto/mac.js +++ b/lib/internal/crypto/mac.js @@ -36,6 +36,7 @@ const { InternalCryptoKey, SecretKeyObject, createSecretKey, + kAlgorithm, } = require('internal/crypto/keys'); const generateKey = promisify(_generateKey); @@ -62,23 +63,11 @@ async function hmacGenerateKey(algorithm, extractable, keyUsages) { return new InternalCryptoKey( key, - { name, length, hash: { name: hash.name } }, + { name, length, hash }, ArrayFrom(usageSet), extractable); } -function getAlgorithmName(hash) { - switch (hash) { - case 'SHA-1': // Fall through - case 'SHA-256': // Fall through - case 'SHA-384': // Fall through - case 'SHA-512': // Fall through - return `HS${hash.slice(4)}`; - default: - throw lazyDOMException('Unsupported digest algorithm', 'DataError'); - } -} - function hmacImportKey( format, keyData, @@ -126,7 +115,9 @@ function hmacImportKey( } if (keyData.alg !== undefined) { - if (keyData.alg !== getAlgorithmName(algorithm.hash.name)) + const expected = + normalizeHashName(algorithm.hash.name, normalizeHashName.kContextJwkHmac); + if (expected && keyData.alg !== expected) throw lazyDOMException( 'JWK "alg" does not match the requested algorithm', 'DataError'); @@ -171,7 +162,7 @@ function hmacSignVerify(key, data, algorithm, signature) { return jobPromise(() => new HmacJob( kCryptoJobAsync, mode, - normalizeHashName(key.algorithm.hash.name), + normalizeHashName(key[kAlgorithm].hash.name), key[kKeyObject][kHandle], data, signature)); diff --git a/lib/internal/crypto/ml_dsa.js b/lib/internal/crypto/ml_dsa.js new file mode 100644 index 00000000000000..966f5b0d222fd0 --- /dev/null +++ b/lib/internal/crypto/ml_dsa.js @@ -0,0 +1,317 @@ +'use strict'; + +const { + SafeSet, + Uint8Array, +} = primordials; + +const { Buffer } = require('buffer'); + +const { + KeyObjectHandle, + SignJob, + kCryptoJobAsync, + kKeyTypePrivate, + kKeyTypePublic, + kSignJobModeSign, + kSignJobModeVerify, + kKeyFormatDER, + kWebCryptoKeyFormatRaw, + kWebCryptoKeyFormatPKCS8, + kWebCryptoKeyFormatSPKI, +} = internalBinding('crypto'); + +const { + codes: { + ERR_CRYPTO_INVALID_JWK, + }, +} = require('internal/errors'); + +const { + getUsagesUnion, + hasAnyNotIn, + jobPromise, + validateKeyOps, + kHandle, + kKeyObject, +} = require('internal/crypto/util'); + +const { + lazyDOMException, + promisify, +} = require('internal/util'); + +const { + generateKeyPair: _generateKeyPair, +} = require('internal/crypto/keygen'); + +const { + InternalCryptoKey, + PrivateKeyObject, + PublicKeyObject, + createPrivateKey, + createPublicKey, + kAlgorithm, + kKeyType, +} = require('internal/crypto/keys'); + +const generateKeyPair = promisify(_generateKeyPair); + +function verifyAcceptableMlDsaKeyUse(name, isPublic, usages) { + const checkSet = isPublic ? ['verify'] : ['sign']; + if (hasAnyNotIn(usages, checkSet)) { + throw lazyDOMException( + `Unsupported key usage for a ${name} key`, + 'SyntaxError'); + } +} + +function createMlDsaRawKey(name, keyData, isPublic) { + const handle = new KeyObjectHandle(); + const keyType = isPublic ? kKeyTypePublic : kKeyTypePrivate; + if (!handle.initPqcRaw(name, keyData, keyType)) { + throw lazyDOMException('Invalid keyData', 'DataError'); + } + + return isPublic ? new PublicKeyObject(handle) : new PrivateKeyObject(handle); +} + +async function mlDsaGenerateKey(algorithm, extractable, keyUsages) { + const { name } = algorithm; + + const usageSet = new SafeSet(keyUsages); + if (hasAnyNotIn(usageSet, ['sign', 'verify'])) { + throw lazyDOMException( + `Unsupported key usage for an ${name} key`, + 'SyntaxError'); + } + + const keyPair = await generateKeyPair(name.toLowerCase()).catch((err) => { + throw lazyDOMException( + 'The operation failed for an operation-specific reason', + { name: 'OperationError', cause: err }); + }); + + const publicUsages = getUsagesUnion(usageSet, 'verify'); + const privateUsages = getUsagesUnion(usageSet, 'sign'); + + const keyAlgorithm = { name }; + + const publicKey = + new InternalCryptoKey( + keyPair.publicKey, + keyAlgorithm, + publicUsages, + true); + + const privateKey = + new InternalCryptoKey( + keyPair.privateKey, + keyAlgorithm, + privateUsages, + extractable); + + return { __proto__: null, privateKey, publicKey }; +} + +function mlDsaExportKey(key, format) { + try { + switch (format) { + case kWebCryptoKeyFormatRaw: { + if (key[kKeyType] === 'private') { + return key[kKeyObject][kHandle].rawSeed().buffer; + } + + return key[kKeyObject][kHandle].rawPublicKey().buffer; + } + case kWebCryptoKeyFormatSPKI: { + return key[kKeyObject][kHandle].export(kKeyFormatDER, kWebCryptoKeyFormatSPKI).buffer; + } + case kWebCryptoKeyFormatPKCS8: { + const seed = key[kKeyObject][kHandle].rawSeed(); + const buffer = new Uint8Array(54); + buffer.set([ + 0x30, 0x34, 0x02, 0x01, 0x00, 0x30, 0x0B, 0x06, + 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, + 0x03, 0x00, 0x04, 0x22, 0x80, 0x20, + ], 0); + switch (key[kAlgorithm].name) { + case 'ML-DSA-44': + buffer.set([0x11], 17); + break; + case 'ML-DSA-65': + buffer.set([0x12], 17); + break; + case 'ML-DSA-87': + buffer.set([0x13], 17); + break; + } + buffer.set(seed, 22); + return buffer.buffer; + } + default: + return undefined; + } + } catch (err) { + throw lazyDOMException( + 'The operation failed for an operation-specific reason', + { name: 'OperationError', cause: err }); + } +} + +function mlDsaImportKey( + format, + keyData, + algorithm, + extractable, + keyUsages) { + + const { name } = algorithm; + let keyObject; + const usagesSet = new SafeSet(keyUsages); + switch (format) { + case 'KeyObject': { + verifyAcceptableMlDsaKeyUse(name, keyData.type === 'public', usagesSet); + keyObject = keyData; + break; + } + case 'spki': { + verifyAcceptableMlDsaKeyUse(name, true, usagesSet); + try { + keyObject = createPublicKey({ + key: keyData, + format: 'der', + type: 'spki', + }); + } catch (err) { + throw lazyDOMException( + 'Invalid keyData', { name: 'DataError', cause: err }); + } + break; + } + case 'pkcs8': { + verifyAcceptableMlDsaKeyUse(name, false, usagesSet); + try { + keyObject = createPrivateKey({ + key: keyData, + format: 'der', + type: 'pkcs8', + }); + } catch (err) { + throw lazyDOMException( + 'Invalid keyData', { name: 'DataError', cause: err }); + } + break; + } + case 'jwk': { + if (!keyData.kty) + throw lazyDOMException('Invalid keyData', 'DataError'); + if (keyData.kty !== 'AKP') + throw lazyDOMException('Invalid JWK "kty" Parameter', 'DataError'); + if (keyData.alg !== name) + throw lazyDOMException( + 'JWK "alg" Parameter and algorithm name mismatch', 'DataError'); + const isPublic = keyData.priv === undefined; + + if (usagesSet.size > 0 && keyData.use !== undefined) { + if (keyData.use !== 'sig') + throw lazyDOMException('Invalid JWK "use" Parameter', 'DataError'); + } + + validateKeyOps(keyData.key_ops, usagesSet); + + if (keyData.ext !== undefined && + keyData.ext === false && + extractable === true) { + throw lazyDOMException( + 'JWK "ext" Parameter and extractable mismatch', + 'DataError'); + } + + if (!isPublic && typeof keyData.pub !== 'string') { + throw lazyDOMException('Invalid JWK', 'DataError'); + } + + verifyAcceptableMlDsaKeyUse( + name, + isPublic, + usagesSet); + + try { + const publicKeyObject = createMlDsaRawKey( + name, + Buffer.from(keyData.pub, 'base64url'), + true); + + if (isPublic) { + keyObject = publicKeyObject; + } else { + keyObject = createMlDsaRawKey( + name, + Buffer.from(keyData.priv, 'base64url'), + false); + + if (!createPublicKey(keyObject).equals(publicKeyObject)) { + throw new ERR_CRYPTO_INVALID_JWK(); + } + } + } catch (err) { + throw lazyDOMException('Invalid keyData', { name: 'DataError', cause: err }); + } + break; + } + case 'raw-public': + case 'raw-seed': { + const isPublic = format === 'raw-public'; + verifyAcceptableMlDsaKeyUse(name, isPublic, usagesSet); + + try { + keyObject = createMlDsaRawKey(name, keyData, isPublic); + } catch (err) { + throw lazyDOMException('Invalid keyData', { name: 'DataError', cause: err }); + } + break; + } + default: + return undefined; + } + + if (keyObject.asymmetricKeyType !== name.toLowerCase()) { + throw lazyDOMException('Invalid key type', 'DataError'); + } + + return new InternalCryptoKey( + keyObject, + { name }, + keyUsages, + extractable); +} + +function mlDsaSignVerify(key, data, algorithm, signature) { + const mode = signature === undefined ? kSignJobModeSign : kSignJobModeVerify; + const type = mode === kSignJobModeSign ? 'private' : 'public'; + + if (key[kKeyType] !== type) + throw lazyDOMException(`Key must be a ${type} key`, 'InvalidAccessError'); + + return jobPromise(() => new SignJob( + kCryptoJobAsync, + mode, + key[kKeyObject][kHandle], + undefined, + undefined, + undefined, + data, + undefined, + undefined, + undefined, + undefined, + signature)); +} + +module.exports = { + mlDsaExportKey, + mlDsaImportKey, + mlDsaGenerateKey, + mlDsaSignVerify, +}; diff --git a/lib/internal/crypto/ml_kem.js b/lib/internal/crypto/ml_kem.js new file mode 100644 index 00000000000000..4dfa1fa31a0425 --- /dev/null +++ b/lib/internal/crypto/ml_kem.js @@ -0,0 +1,287 @@ +'use strict'; + +const { + PromiseWithResolvers, + SafeSet, + Uint8Array, +} = primordials; + +const { + kCryptoJobAsync, + KEMDecapsulateJob, + KEMEncapsulateJob, + KeyObjectHandle, + kKeyFormatDER, + kKeyTypePrivate, + kKeyTypePublic, + kWebCryptoKeyFormatPKCS8, + kWebCryptoKeyFormatRaw, + kWebCryptoKeyFormatSPKI, +} = internalBinding('crypto'); + +const { + getUsagesUnion, + hasAnyNotIn, + kHandle, + kKeyObject, +} = require('internal/crypto/util'); + +const { + lazyDOMException, + promisify, +} = require('internal/util'); + +const { + generateKeyPair: _generateKeyPair, +} = require('internal/crypto/keygen'); + +const { + InternalCryptoKey, + PrivateKeyObject, + PublicKeyObject, + createPrivateKey, + createPublicKey, + kAlgorithm, + kKeyType, +} = require('internal/crypto/keys'); + +const generateKeyPair = promisify(_generateKeyPair); + +async function mlKemGenerateKey(algorithm, extractable, keyUsages) { + const { name } = algorithm; + + const usageSet = new SafeSet(keyUsages); + if (hasAnyNotIn(usageSet, ['encapsulateKey', 'encapsulateBits', 'decapsulateKey', 'decapsulateBits'])) { + throw lazyDOMException( + `Unsupported key usage for an ${name} key`, + 'SyntaxError'); + } + + const keyPair = await generateKeyPair(name.toLowerCase()).catch((err) => { + throw lazyDOMException( + 'The operation failed for an operation-specific reason', + { name: 'OperationError', cause: err }); + }); + + const publicUsages = getUsagesUnion(usageSet, 'encapsulateBits', 'encapsulateKey'); + const privateUsages = getUsagesUnion(usageSet, 'decapsulateBits', 'decapsulateKey'); + + const keyAlgorithm = { name }; + + const publicKey = + new InternalCryptoKey( + keyPair.publicKey, + keyAlgorithm, + publicUsages, + true); + + const privateKey = + new InternalCryptoKey( + keyPair.privateKey, + keyAlgorithm, + privateUsages, + extractable); + + return { __proto__: null, privateKey, publicKey }; +} + +function mlKemExportKey(key, format) { + try { + switch (format) { + case kWebCryptoKeyFormatRaw: { + if (key[kKeyType] === 'private') { + return key[kKeyObject][kHandle].rawSeed().buffer; + } + + return key[kKeyObject][kHandle].rawPublicKey().buffer; + } + case kWebCryptoKeyFormatSPKI: { + return key[kKeyObject][kHandle].export(kKeyFormatDER, kWebCryptoKeyFormatSPKI).buffer; + } + case kWebCryptoKeyFormatPKCS8: { + const seed = key[kKeyObject][kHandle].rawSeed(); + const buffer = new Uint8Array(86); + buffer.set([ + 0x30, 0x54, 0x02, 0x01, 0x00, 0x30, 0x0B, 0x06, + 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, + 0x04, 0x00, 0x04, 0x42, 0x80, 0x40, + ], 0); + switch (key[kAlgorithm].name) { + case 'ML-KEM-512': + buffer.set([0x01], 17); + break; + case 'ML-KEM-768': + buffer.set([0x02], 17); + break; + case 'ML-KEM-1024': + buffer.set([0x03], 17); + break; + } + buffer.set(seed, 22); + return buffer.buffer; + } + default: + return undefined; + } + } catch (err) { + throw lazyDOMException( + 'The operation failed for an operation-specific reason', + { name: 'OperationError', cause: err }); + } +} + +function verifyAcceptableMlKemKeyUse(name, isPublic, usages) { + const checkSet = isPublic ? ['encapsulateKey', 'encapsulateBits'] : ['decapsulateKey', 'decapsulateBits']; + if (hasAnyNotIn(usages, checkSet)) { + throw lazyDOMException( + `Unsupported key usage for a ${name} key`, + 'SyntaxError'); + } +} + +function createMlKemRawKey(name, keyData, isPublic) { + const handle = new KeyObjectHandle(); + const keyType = isPublic ? kKeyTypePublic : kKeyTypePrivate; + if (!handle.initPqcRaw(name, keyData, keyType)) { + throw lazyDOMException('Invalid keyData', 'DataError'); + } + + return isPublic ? new PublicKeyObject(handle) : new PrivateKeyObject(handle); +} + +function mlKemImportKey( + format, + keyData, + algorithm, + extractable, + keyUsages) { + + const { name } = algorithm; + let keyObject; + const usagesSet = new SafeSet(keyUsages); + switch (format) { + case 'KeyObject': { + verifyAcceptableMlKemKeyUse(name, keyData.type === 'public', usagesSet); + keyObject = keyData; + break; + } + case 'spki': { + verifyAcceptableMlKemKeyUse(name, true, usagesSet); + try { + keyObject = createPublicKey({ + key: keyData, + format: 'der', + type: 'spki', + }); + } catch (err) { + throw lazyDOMException( + 'Invalid keyData', { name: 'DataError', cause: err }); + } + break; + } + case 'pkcs8': { + verifyAcceptableMlKemKeyUse(name, false, usagesSet); + try { + keyObject = createPrivateKey({ + key: keyData, + format: 'der', + type: 'pkcs8', + }); + } catch (err) { + throw lazyDOMException( + 'Invalid keyData', { name: 'DataError', cause: err }); + } + break; + } + case 'raw-public': + case 'raw-seed': { + const isPublic = format === 'raw-public'; + verifyAcceptableMlKemKeyUse(name, isPublic, usagesSet); + + try { + keyObject = createMlKemRawKey(name, keyData, isPublic); + } catch (err) { + throw lazyDOMException('Invalid keyData', { name: 'DataError', cause: err }); + } + break; + } + default: + return undefined; + } + + if (keyObject.asymmetricKeyType !== name.toLowerCase()) { + throw lazyDOMException('Invalid key type', 'DataError'); + } + + return new InternalCryptoKey( + keyObject, + { name }, + keyUsages, + extractable); +} + +function mlKemEncapsulate(encapsulationKey) { + if (encapsulationKey[kKeyType] !== 'public') { + throw lazyDOMException(`Key must be a public key`, 'InvalidAccessError'); + } + + const { promise, resolve, reject } = PromiseWithResolvers(); + + const job = new KEMEncapsulateJob( + kCryptoJobAsync, + encapsulationKey[kKeyObject][kHandle], + undefined, + undefined, + undefined); + + job.ondone = (error, result) => { + if (error) { + reject(lazyDOMException( + 'The operation failed for an operation-specific reason', + { name: 'OperationError', cause: error })); + } else { + const { 0: sharedKey, 1: ciphertext } = result; + resolve({ sharedKey: sharedKey.buffer, ciphertext: ciphertext.buffer }); + } + }; + job.run(); + + return promise; +} + +function mlKemDecapsulate(decapsulationKey, ciphertext) { + if (decapsulationKey[kKeyType] !== 'private') { + throw lazyDOMException(`Key must be a private key`, 'InvalidAccessError'); + } + + const { promise, resolve, reject } = PromiseWithResolvers(); + + const job = new KEMDecapsulateJob( + kCryptoJobAsync, + decapsulationKey[kKeyObject][kHandle], + undefined, + undefined, + undefined, + ciphertext); + + job.ondone = (error, result) => { + if (error) { + reject(lazyDOMException( + 'The operation failed for an operation-specific reason', + { name: 'OperationError', cause: error })); + } else { + resolve(result.buffer); + } + }; + job.run(); + + return promise; +} + +module.exports = { + mlKemExportKey, + mlKemImportKey, + mlKemEncapsulate, + mlKemDecapsulate, + mlKemGenerateKey, +}; diff --git a/lib/internal/crypto/pbkdf2.js b/lib/internal/crypto/pbkdf2.js index 0dc0d25682d2cd..ebdba7334f6556 100644 --- a/lib/internal/crypto/pbkdf2.js +++ b/lib/internal/crypto/pbkdf2.js @@ -128,4 +128,5 @@ module.exports = { pbkdf2, pbkdf2Sync, pbkdf2DeriveBits, + validatePbkdf2DeriveBitsLength, }; diff --git a/lib/internal/crypto/rsa.js b/lib/internal/crypto/rsa.js index bf6c341c5a723d..ecb3759ba48354 100644 --- a/lib/internal/crypto/rsa.js +++ b/lib/internal/crypto/rsa.js @@ -50,6 +50,8 @@ const { PublicKeyObject, createPublicKey, createPrivateKey, + kAlgorithm, + kKeyType, } = require('internal/crypto/keys'); const { @@ -95,7 +97,7 @@ function rsaOaepCipher(mode, key, data, algorithm) { validateRsaOaepAlgorithm(algorithm); const type = mode === kWebCryptoCipherEncrypt ? 'public' : 'private'; - if (key.type !== type) { + if (key[kKeyType] !== type) { throw lazyDOMException( 'The requested operation is not valid for the provided key', 'InvalidAccessError'); @@ -107,7 +109,7 @@ function rsaOaepCipher(mode, key, data, algorithm) { key[kKeyObject][kHandle], data, kKeyVariantRSA_OAEP, - normalizeHashName(key.algorithm.hash.name), + normalizeHashName(key[kAlgorithm].hash.name), algorithm.label)); } @@ -161,7 +163,7 @@ async function rsaKeyGenerate( name, modulusLength, publicExponent, - hash: { name: hash.name }, + hash, }; let publicUsages; @@ -201,7 +203,7 @@ function rsaExportKey(key, format) { kCryptoJobAsync, format, key[kKeyObject][kHandle], - kRsaVariants[key.algorithm.name])); + kRsaVariants[key[kAlgorithm].name])); } function rsaImportKey( @@ -281,7 +283,7 @@ function rsaImportKey( algorithm.name === 'RSA-PSS' ? normalizeHashName.kContextJwkRsaPss : normalizeHashName.kContextJwkRsaOaep); - if (keyData.alg !== expected) + if (expected && keyData.alg !== expected) throw lazyDOMException( 'JWK "alg" does not match the requested algorithm', 'DataError'); @@ -329,16 +331,16 @@ function rsaSignVerify(key, data, { saltLength }, signature) { const mode = signature === undefined ? kSignJobModeSign : kSignJobModeVerify; const type = mode === kSignJobModeSign ? 'private' : 'public'; - if (key.type !== type) + if (key[kKeyType] !== type) throw lazyDOMException(`Key must be a ${type} key`, 'InvalidAccessError'); return jobPromise(() => { - if (key.algorithm.name === 'RSA-PSS') { + if (key[kAlgorithm].name === 'RSA-PSS') { validateInt32( saltLength, 'algorithm.saltLength', 0, - MathCeil((key.algorithm.modulusLength - 1) / 8) - getDigestSizeInBytes(key.algorithm.hash.name) - 2); + MathCeil((key[kAlgorithm].modulusLength - 1) / 8) - getDigestSizeInBytes(key[kAlgorithm].hash.name) - 2); } return new SignJob( @@ -349,9 +351,9 @@ function rsaSignVerify(key, data, { saltLength }, signature) { undefined, undefined, data, - normalizeHashName(key.algorithm.hash.name), + normalizeHashName(key[kAlgorithm].hash.name), saltLength, - key.algorithm.name === 'RSA-PSS' ? RSA_PKCS1_PSS_PADDING : undefined, + key[kAlgorithm].name === 'RSA-PSS' ? RSA_PKCS1_PSS_PADDING : undefined, undefined, signature); }); diff --git a/lib/internal/crypto/util.js b/lib/internal/crypto/util.js index c7e0c31a4cc609..98d638909ef0e3 100644 --- a/lib/internal/crypto/util.js +++ b/lib/internal/crypto/util.js @@ -33,6 +33,13 @@ const { secureHeapUsed: _secureHeapUsed, getCachedAliases, getOpenSSLSecLevelCrypto: getOpenSSLSecLevel, + EVP_PKEY_ML_DSA_44, + EVP_PKEY_ML_DSA_65, + EVP_PKEY_ML_DSA_87, + EVP_PKEY_ML_KEM_512, + EVP_PKEY_ML_KEM_768, + EVP_PKEY_ML_KEM_1024, + kKeyVariantAES_OCB_128: hasAesOcbMode, } = internalBinding('crypto'); const { getOptionValue } = require('internal/options'); @@ -168,141 +175,272 @@ const kNamedCurveAliases = { 'P-521': 'secp521r1', }; -const kSupportedAlgorithms = { - 'digest': { - 'SHA-1': null, - 'SHA-256': null, - 'SHA-384': null, - 'SHA-512': null, +// Algorithm definitions organized by algorithm name +const kAlgorithmDefinitions = { + 'AES-CBC': { + 'generateKey': 'AesKeyGenParams', + 'exportKey': null, + 'importKey': null, + 'encrypt': 'AesCbcParams', + 'decrypt': 'AesCbcParams', + 'get key length': 'AesDerivedKeyParams', }, - 'generateKey': { - 'RSASSA-PKCS1-v1_5': 'RsaHashedKeyGenParams', - 'RSA-PSS': 'RsaHashedKeyGenParams', - 'RSA-OAEP': 'RsaHashedKeyGenParams', - 'ECDSA': 'EcKeyGenParams', - 'ECDH': 'EcKeyGenParams', - 'AES-CTR': 'AesKeyGenParams', - 'AES-CBC': 'AesKeyGenParams', - 'AES-GCM': 'AesKeyGenParams', - 'AES-KW': 'AesKeyGenParams', - 'HMAC': 'HmacKeyGenParams', - 'Ed25519': null, - 'X25519': null, + 'AES-CTR': { + 'generateKey': 'AesKeyGenParams', + 'exportKey': null, + 'importKey': null, + 'encrypt': 'AesCtrParams', + 'decrypt': 'AesCtrParams', + 'get key length': 'AesDerivedKeyParams', }, - 'exportKey': { - 'RSASSA-PKCS1-v1_5': null, - 'RSA-PSS': null, - 'RSA-OAEP': null, - 'ECDSA': null, - 'ECDH': null, - 'HMAC': null, - 'AES-CTR': null, - 'AES-CBC': null, - 'AES-GCM': null, - 'AES-KW': null, - 'Ed25519': null, - 'X25519': null, + 'AES-GCM': { + 'generateKey': 'AesKeyGenParams', + 'exportKey': null, + 'importKey': null, + 'encrypt': 'AeadParams', + 'decrypt': 'AeadParams', + 'get key length': 'AesDerivedKeyParams', }, - 'sign': { - 'RSASSA-PKCS1-v1_5': null, - 'RSA-PSS': 'RsaPssParams', - 'ECDSA': 'EcdsaParams', - 'HMAC': null, - 'Ed25519': null, + 'AES-KW': { + 'generateKey': 'AesKeyGenParams', + 'exportKey': null, + 'importKey': null, + 'get key length': 'AesDerivedKeyParams', + 'wrapKey': null, + 'unwrapKey': null, }, - 'verify': { - 'RSASSA-PKCS1-v1_5': null, - 'RSA-PSS': 'RsaPssParams', - 'ECDSA': 'EcdsaParams', - 'HMAC': null, - 'Ed25519': null, + 'AES-OCB': { + 'generateKey': 'AesKeyGenParams', + 'exportKey': null, + 'importKey': null, + 'encrypt': 'AeadParams', + 'decrypt': 'AeadParams', + 'get key length': 'AesDerivedKeyParams', }, - 'importKey': { - 'RSASSA-PKCS1-v1_5': 'RsaHashedImportParams', - 'RSA-PSS': 'RsaHashedImportParams', - 'RSA-OAEP': 'RsaHashedImportParams', - 'ECDSA': 'EcKeyImportParams', - 'ECDH': 'EcKeyImportParams', - 'HMAC': 'HmacImportParams', - 'HKDF': null, - 'PBKDF2': null, - 'AES-CTR': null, - 'AES-CBC': null, - 'AES-GCM': null, - 'AES-KW': null, - 'Ed25519': null, - 'X25519': null, + 'ChaCha20-Poly1305': { + 'generateKey': null, + 'exportKey': null, + 'importKey': null, + 'encrypt': 'AeadParams', + 'decrypt': 'AeadParams', + 'get key length': null, }, - 'deriveBits': { - 'HKDF': 'HkdfParams', - 'PBKDF2': 'Pbkdf2Params', - 'ECDH': 'EcdhKeyDeriveParams', - 'X25519': 'EcdhKeyDeriveParams', + 'cSHAKE128': { 'digest': 'CShakeParams' }, + 'cSHAKE256': { 'digest': 'CShakeParams' }, + 'ECDH': { + 'generateKey': 'EcKeyGenParams', + 'exportKey': null, + 'importKey': 'EcKeyImportParams', + 'deriveBits': 'EcdhKeyDeriveParams', }, - 'encrypt': { - 'RSA-OAEP': 'RsaOaepParams', - 'AES-CBC': 'AesCbcParams', - 'AES-GCM': 'AesGcmParams', - 'AES-CTR': 'AesCtrParams', + 'ECDSA': { + 'generateKey': 'EcKeyGenParams', + 'exportKey': null, + 'importKey': 'EcKeyImportParams', + 'sign': 'EcdsaParams', + 'verify': 'EcdsaParams', }, - 'decrypt': { - 'RSA-OAEP': 'RsaOaepParams', - 'AES-CBC': 'AesCbcParams', - 'AES-GCM': 'AesGcmParams', - 'AES-CTR': 'AesCtrParams', + 'Ed25519': { + 'generateKey': null, + 'exportKey': null, + 'importKey': null, + 'sign': null, + 'verify': null, }, - 'get key length': { - 'AES-CBC': 'AesDerivedKeyParams', - 'AES-CTR': 'AesDerivedKeyParams', - 'AES-GCM': 'AesDerivedKeyParams', - 'AES-KW': 'AesDerivedKeyParams', - 'HMAC': 'HmacImportParams', - 'HKDF': null, - 'PBKDF2': null, + 'Ed448': { + 'generateKey': null, + 'exportKey': null, + 'importKey': null, + 'sign': 'Ed448Params', + 'verify': 'Ed448Params', }, - 'wrapKey': { - 'AES-KW': null, + 'HKDF': { + 'importKey': null, + 'deriveBits': 'HkdfParams', + 'get key length': null, }, - 'unwrapKey': { - 'AES-KW': null, + 'HMAC': { + 'generateKey': 'HmacKeyGenParams', + 'exportKey': null, + 'importKey': 'HmacImportParams', + 'sign': null, + 'verify': null, + 'get key length': 'HmacImportParams', }, -}; - -const experimentalAlgorithms = ObjectEntries({ - 'X448': { - generateKey: null, - importKey: null, - deriveBits: 'EcdhKeyDeriveParams', - exportKey: null, + 'ML-DSA-44': { + 'generateKey': null, + 'exportKey': null, + 'importKey': null, + 'sign': 'ContextParams', + 'verify': 'ContextParams', }, - 'Ed448': { - generateKey: null, - sign: 'Ed448Params', - verify: 'Ed448Params', - importKey: null, - exportKey: null, + 'ML-DSA-65': { + 'generateKey': null, + 'exportKey': null, + 'importKey': null, + 'sign': 'ContextParams', + 'verify': 'ContextParams', }, -}); + 'ML-DSA-87': { + 'generateKey': null, + 'exportKey': null, + 'importKey': null, + 'sign': 'ContextParams', + 'verify': 'ContextParams', + }, + 'ML-KEM-512': { + 'generateKey': null, + 'exportKey': null, + 'importKey': null, + 'encapsulate': null, + 'decapsulate': null, + }, + 'ML-KEM-768': { + 'generateKey': null, + 'exportKey': null, + 'importKey': null, + 'encapsulate': null, + 'decapsulate': null, + }, + 'ML-KEM-1024': { + 'generateKey': null, + 'exportKey': null, + 'importKey': null, + 'encapsulate': null, + 'decapsulate': null, + }, + 'PBKDF2': { + 'importKey': null, + 'deriveBits': 'Pbkdf2Params', + 'get key length': null, + }, + 'RSA-OAEP': { + 'generateKey': 'RsaHashedKeyGenParams', + 'exportKey': null, + 'importKey': 'RsaHashedImportParams', + 'encrypt': 'RsaOaepParams', + 'decrypt': 'RsaOaepParams', + }, + 'RSA-PSS': { + 'generateKey': 'RsaHashedKeyGenParams', + 'exportKey': null, + 'importKey': 'RsaHashedImportParams', + 'sign': 'RsaPssParams', + 'verify': 'RsaPssParams', + }, + 'RSASSA-PKCS1-v1_5': { + 'generateKey': 'RsaHashedKeyGenParams', + 'exportKey': null, + 'importKey': 'RsaHashedImportParams', + 'sign': null, + 'verify': null, + }, + 'SHA-1': { 'digest': null }, + 'SHA-256': { 'digest': null }, + 'SHA-384': { 'digest': null }, + 'SHA-512': { 'digest': null }, + 'SHA3-256': { 'digest': null }, + 'SHA3-384': { 'digest': null }, + 'SHA3-512': { 'digest': null }, + 'X25519': { + 'generateKey': null, + 'exportKey': null, + 'importKey': null, + 'deriveBits': 'EcdhKeyDeriveParams', + }, + 'X448': { + 'generateKey': null, + 'exportKey': null, + 'importKey': null, + 'deriveBits': 'EcdhKeyDeriveParams', + }, +}; + +// Conditionally supported algorithms +const conditionalAlgorithms = { + 'AES-KW': !process.features.openssl_is_boringssl, + 'AES-OCB': !!hasAesOcbMode, + 'ChaCha20-Poly1305': !process.features.openssl_is_boringssl || + ArrayPrototypeIncludes(getCiphers(), 'chacha20-poly1305'), + 'cSHAKE128': !process.features.openssl_is_boringssl || + ArrayPrototypeIncludes(getHashes(), 'shake128'), + 'cSHAKE256': !process.features.openssl_is_boringssl || + ArrayPrototypeIncludes(getHashes(), 'shake256'), + 'Ed448': !process.features.openssl_is_boringssl, + 'ML-DSA-44': !!EVP_PKEY_ML_DSA_44, + 'ML-DSA-65': !!EVP_PKEY_ML_DSA_65, + 'ML-DSA-87': !!EVP_PKEY_ML_DSA_87, + 'ML-KEM-512': !!EVP_PKEY_ML_KEM_512, + 'ML-KEM-768': !!EVP_PKEY_ML_KEM_768, + 'ML-KEM-1024': !!EVP_PKEY_ML_KEM_1024, + 'SHA3-256': !process.features.openssl_is_boringssl || + ArrayPrototypeIncludes(getHashes(), 'sha3-256'), + 'SHA3-384': !process.features.openssl_is_boringssl || + ArrayPrototypeIncludes(getHashes(), 'sha3-384'), + 'SHA3-512': !process.features.openssl_is_boringssl || + ArrayPrototypeIncludes(getHashes(), 'sha3-512'), + 'X448': !process.features.openssl_is_boringssl, +}; -for (let i = 0; i < experimentalAlgorithms.length; i++) { - const name = experimentalAlgorithms[i][0]; - const ops = ObjectEntries(experimentalAlgorithms[i][1]); - for (let j = 0; j < ops.length; j++) { - const { 0: op, 1: dict } = ops[j]; - ObjectDefineProperty(kSupportedAlgorithms[op], name, { - get() { - emitExperimentalWarning(`The ${name} Web Crypto API algorithm`); - return dict; - }, - __proto__: null, - enumerable: true, - }); +// Experimental algorithms +const experimentalAlgorithms = [ + 'AES-OCB', + 'ChaCha20-Poly1305', + 'cSHAKE128', + 'cSHAKE256', + 'Ed448', + 'ML-DSA-44', + 'ML-DSA-65', + 'ML-DSA-87', + 'ML-KEM-512', + 'ML-KEM-768', + 'ML-KEM-1024', + 'SHA3-256', + 'SHA3-384', + 'SHA3-512', + 'X448', +]; + +// Transform the algorithm definitions into the operation-keyed structure +function createSupportedAlgorithms(algorithmDefs) { + const result = {}; + + for (const { 0: algorithmName, 1: operations } of ObjectEntries(algorithmDefs)) { + // Skip algorithms that are conditionally not supported + if (ObjectPrototypeHasOwnProperty(conditionalAlgorithms, algorithmName) && + !conditionalAlgorithms[algorithmName]) { + continue; + } + + for (const { 0: operation, 1: dict } of ObjectEntries(operations)) { + result[operation] ||= {}; + + // Add experimental warnings for experimental algorithms + if (ArrayPrototypeIncludes(experimentalAlgorithms, algorithmName)) { + ObjectDefineProperty(result[operation], algorithmName, { + get() { + emitExperimentalWarning(`The ${algorithmName} Web Crypto API algorithm`); + return dict; + }, + __proto__: null, + enumerable: true, + }); + } else { + result[operation][algorithmName] = dict; + } + } } + + return result; } +const kSupportedAlgorithms = createSupportedAlgorithms(kAlgorithmDefinitions); + const simpleAlgorithmDictionaries = { - AesGcmParams: { iv: 'BufferSource', additionalData: 'BufferSource' }, - RsaHashedKeyGenParams: { hash: 'HashAlgorithmIdentifier' }, + AeadParams: { iv: 'BufferSource', additionalData: 'BufferSource' }, + // publicExponent is not strictly a BufferSource but it is a Uint8Array that we normalize + // this way + RsaHashedKeyGenParams: { hash: 'HashAlgorithmIdentifier', publicExponent: 'BufferSource' }, EcKeyGenParams: {}, HmacKeyGenParams: { hash: 'HashAlgorithmIdentifier' }, RsaPssParams: {}, @@ -314,10 +452,15 @@ const simpleAlgorithmDictionaries = { info: 'BufferSource', }, Ed448Params: { context: 'BufferSource' }, + ContextParams: { context: 'BufferSource' }, Pbkdf2Params: { hash: 'HashAlgorithmIdentifier', salt: 'BufferSource' }, RsaOaepParams: { label: 'BufferSource' }, RsaHashedImportParams: { hash: 'HashAlgorithmIdentifier' }, EcKeyImportParams: {}, + CShakeParams: { + functionName: 'BufferSource', + customization: 'BufferSource', + }, }; function validateMaxBufferLength(data, name) { @@ -526,15 +669,30 @@ function getBlockSize(name) { // Fall through case 'SHA-512': return 1024; + case 'SHA3-256': + // Fall through + case 'SHA3-384': + // Fall through + case 'SHA3-512': + // This interaction is not defined for now. + // https://github.com/WICG/webcrypto-modern-algos/issues/23 + throw lazyDOMException('Explicit algorithm length member is required', 'NotSupportedError'); } } function getDigestSizeInBytes(name) { switch (name) { - case 'SHA-1': return 20; - case 'SHA-256': return 32; - case 'SHA-384': return 48; - case 'SHA-512': return 64; + case 'SHA-1': + return 20; + case 'SHA-256': // Fall through + case 'SHA3-256': + return 32; + case 'SHA-384': // Fall through + case 'SHA3-384': + return 48; + case 'SHA-512': // Fall through + case 'SHA3-512': + return 64; } } diff --git a/lib/internal/crypto/webcrypto.js b/lib/internal/crypto/webcrypto.js index 82bdc29f50a4d4..60f9de5fbfcb28 100644 --- a/lib/internal/crypto/webcrypto.js +++ b/lib/internal/crypto/webcrypto.js @@ -8,6 +8,8 @@ const { ReflectApply, ReflectConstruct, StringPrototypeRepeat, + StringPrototypeSlice, + StringPrototypeStartsWith, SymbolToStringTag, } = primordials; @@ -29,8 +31,13 @@ const { } = require('internal/errors'); const { + createPublicKey, CryptoKey, importGenericSecretKey, + kAlgorithm, + kKeyUsages, + kExtractable, + kKeyType, } = require('internal/crypto/keys'); const { @@ -47,6 +54,7 @@ const { } = require('internal/crypto/util'); const { + emitExperimentalWarning, kEnumerableProperty, lazyDOMException, } = require('internal/util'); @@ -147,20 +155,45 @@ async function generateKey( // Fall through case 'AES-GCM': // Fall through + case 'AES-OCB': + // Fall through case 'AES-KW': resultType = 'CryptoKey'; result = await require('internal/crypto/aes') .aesGenerateKey(algorithm, extractable, keyUsages); break; + case 'ChaCha20-Poly1305': + resultType = 'CryptoKey'; + result = await require('internal/crypto/chacha20_poly1305') + .c20pGenerateKey(algorithm, extractable, keyUsages); + break; + case 'ML-DSA-44': + // Fall through + case 'ML-DSA-65': + // Fall through + case 'ML-DSA-87': + resultType = 'CryptoKeyPair'; + result = await require('internal/crypto/ml_dsa') + .mlDsaGenerateKey(algorithm, extractable, keyUsages); + break; + case 'ML-KEM-512': + // Fall through + case 'ML-KEM-768': + // Fall through + case 'ML-KEM-1024': + resultType = 'CryptoKeyPair'; + result = await require('internal/crypto/ml_kem') + .mlKemGenerateKey(algorithm, extractable, keyUsages); + break; default: throw lazyDOMException('Unrecognized algorithm name', 'NotSupportedError'); } if ( (resultType === 'CryptoKey' && - (result.type === 'secret' || result.type === 'private') && - result.usages.length === 0) || - (resultType === 'CryptoKeyPair' && result.privateKey.usages.length === 0) + (result[kKeyType] === 'secret' || result[kKeyType] === 'private') && + result[kKeyUsages].length === 0) || + (resultType === 'CryptoKeyPair' && result.privateKey[kKeyUsages].length === 0) ) { throw lazyDOMException( 'Usages cannot be empty when creating a key.', @@ -192,12 +225,12 @@ async function deriveBits(algorithm, baseKey, length = null) { } algorithm = normalizeAlgorithm(algorithm, 'deriveBits'); - if (!ArrayPrototypeIncludes(baseKey.usages, 'deriveBits')) { + if (!ArrayPrototypeIncludes(baseKey[kKeyUsages], 'deriveBits')) { throw lazyDOMException( 'baseKey does not have deriveBits usage', 'InvalidAccessError'); } - if (baseKey.algorithm.name !== algorithm.name) + if (baseKey[kAlgorithm].name !== algorithm.name) throw lazyDOMException('Key algorithm mismatch', 'InvalidAccessError'); switch (algorithm.name) { case 'X25519': @@ -222,6 +255,7 @@ function getKeyLength({ name, length, hash }) { case 'AES-CTR': case 'AES-CBC': case 'AES-GCM': + case 'AES-OCB': case 'AES-KW': if (length !== 128 && length !== 192 && length !== 256) throw lazyDOMException('Invalid key length', 'OperationError'); @@ -240,6 +274,8 @@ function getKeyLength({ name, length, hash }) { case 'HKDF': case 'PBKDF2': return null; + case 'ChaCha20-Poly1305': + return 256; } } @@ -277,12 +313,12 @@ async function deriveKey( algorithm = normalizeAlgorithm(algorithm, 'deriveBits'); derivedKeyAlgorithm = normalizeAlgorithm(derivedKeyAlgorithm, 'importKey'); - if (!ArrayPrototypeIncludes(baseKey.usages, 'deriveKey')) { + if (!ArrayPrototypeIncludes(baseKey[kKeyUsages], 'deriveKey')) { throw lazyDOMException( 'baseKey does not have deriveKey usage', 'InvalidAccessError'); } - if (baseKey.algorithm.name !== algorithm.name) + if (baseKey[kAlgorithm].name !== algorithm.name) throw lazyDOMException('Key algorithm mismatch', 'InvalidAccessError'); const length = getKeyLength(normalizeAlgorithm(arguments[2], 'get key length')); @@ -311,12 +347,12 @@ async function deriveKey( return ReflectApply( importKey, this, - ['raw', bits, derivedKeyAlgorithm, extractable, keyUsages], + ['raw-secret', bits, derivedKeyAlgorithm, extractable, keyUsages], ); } async function exportKeySpki(key) { - switch (key.algorithm.name) { + switch (key[kAlgorithm].name) { case 'RSASSA-PKCS1-v1_5': // Fall through case 'RSA-PSS': @@ -338,13 +374,27 @@ async function exportKeySpki(key) { case 'X448': return require('internal/crypto/cfrg') .cfrgExportKey(key, kWebCryptoKeyFormatSPKI); + case 'ML-DSA-44': + // Fall through + case 'ML-DSA-65': + // Fall through + case 'ML-DSA-87': + return require('internal/crypto/ml_dsa') + .mlDsaExportKey(key, kWebCryptoKeyFormatSPKI); + case 'ML-KEM-512': + // Fall through + case 'ML-KEM-768': + // Fall through + case 'ML-KEM-1024': + return require('internal/crypto/ml_kem') + .mlKemExportKey(key, kWebCryptoKeyFormatSPKI); default: return undefined; } } async function exportKeyPkcs8(key) { - switch (key.algorithm.name) { + switch (key[kAlgorithm].name) { case 'RSASSA-PKCS1-v1_5': // Fall through case 'RSA-PSS': @@ -366,13 +416,27 @@ async function exportKeyPkcs8(key) { case 'X448': return require('internal/crypto/cfrg') .cfrgExportKey(key, kWebCryptoKeyFormatPKCS8); + case 'ML-DSA-44': + // Fall through + case 'ML-DSA-65': + // Fall through + case 'ML-DSA-87': + return require('internal/crypto/ml_dsa') + .mlDsaExportKey(key, kWebCryptoKeyFormatPKCS8); + case 'ML-KEM-512': + // Fall through + case 'ML-KEM-768': + // Fall through + case 'ML-KEM-1024': + return require('internal/crypto/ml_kem') + .mlKemExportKey(key, kWebCryptoKeyFormatPKCS8); default: return undefined; } } -async function exportKeyRawPublic(key) { - switch (key.algorithm.name) { +async function exportKeyRawPublic(key, format) { + switch (key[kAlgorithm].name) { case 'ECDSA': // Fall through case 'ECDH': @@ -387,13 +451,58 @@ async function exportKeyRawPublic(key) { case 'X448': return require('internal/crypto/cfrg') .cfrgExportKey(key, kWebCryptoKeyFormatRaw); + case 'ML-DSA-44': + // Fall through + case 'ML-DSA-65': + // Fall through + case 'ML-DSA-87': { + // ML-DSA keys don't recognize "raw" + if (format !== 'raw-public') { + return undefined; + } + return require('internal/crypto/ml_dsa') + .mlDsaExportKey(key, kWebCryptoKeyFormatRaw); + } + case 'ML-KEM-512': + // Fall through + case 'ML-KEM-768': + // Fall through + case 'ML-KEM-1024': { + // ML-KEM keys don't recognize "raw" + if (format !== 'raw-public') { + return undefined; + } + return require('internal/crypto/ml_kem') + .mlKemExportKey(key, kWebCryptoKeyFormatRaw); + } + default: + return undefined; + } +} + +async function exportKeyRawSeed(key) { + switch (key[kAlgorithm].name) { + case 'ML-DSA-44': + // Fall through + case 'ML-DSA-65': + // Fall through + case 'ML-DSA-87': + return require('internal/crypto/ml_dsa') + .mlDsaExportKey(key, kWebCryptoKeyFormatRaw); + case 'ML-KEM-512': + // Fall through + case 'ML-KEM-768': + // Fall through + case 'ML-KEM-1024': + return require('internal/crypto/ml_kem') + .mlKemExportKey(key, kWebCryptoKeyFormatRaw); default: return undefined; } } -async function exportKeyRawSecret(key) { - switch (key.algorithm.name) { +async function exportKeyRawSecret(key, format) { + switch (key[kAlgorithm].name) { case 'AES-CTR': // Fall through case 'AES-CBC': @@ -404,6 +513,13 @@ async function exportKeyRawSecret(key) { // Fall through case 'HMAC': return key[kKeyObject][kHandle].export().buffer; + case 'AES-OCB': + // Fall through + case 'ChaCha20-Poly1305': + if (format === 'raw-secret') { + return key[kKeyObject][kHandle].export().buffer; + } + return undefined; default: return undefined; } @@ -411,25 +527,31 @@ async function exportKeyRawSecret(key) { async function exportKeyJWK(key) { const parameters = { - key_ops: key.usages, - ext: key.extractable, + key_ops: key[kKeyUsages], + ext: key[kExtractable], }; - switch (key.algorithm.name) { - case 'RSASSA-PKCS1-v1_5': - parameters.alg = normalizeHashName( - key.algorithm.hash.name, + switch (key[kAlgorithm].name) { + case 'RSASSA-PKCS1-v1_5': { + const alg = normalizeHashName( + key[kAlgorithm].hash.name, normalizeHashName.kContextJwkRsa); + if (alg) parameters.alg = alg; break; - case 'RSA-PSS': - parameters.alg = normalizeHashName( - key.algorithm.hash.name, + } + case 'RSA-PSS': { + const alg = normalizeHashName( + key[kAlgorithm].hash.name, normalizeHashName.kContextJwkRsaPss); + if (alg) parameters.alg = alg; break; - case 'RSA-OAEP': - parameters.alg = normalizeHashName( - key.algorithm.hash.name, + } + case 'RSA-OAEP': { + const alg = normalizeHashName( + key[kAlgorithm].hash.name, normalizeHashName.kContextJwkRsaOaep); + if (alg) parameters.alg = alg; break; + } case 'ECDSA': // Fall through case 'ECDH': @@ -437,11 +559,17 @@ async function exportKeyJWK(key) { case 'X25519': // Fall through case 'X448': + // Fall through + case 'ML-DSA-44': + // Fall through + case 'ML-DSA-65': + // Fall through + case 'ML-DSA-87': break; case 'Ed25519': // Fall through case 'Ed448': - parameters.alg = key.algorithm.name; + parameters.alg = key[kAlgorithm].name; break; case 'AES-CTR': // Fall through @@ -449,15 +577,22 @@ async function exportKeyJWK(key) { // Fall through case 'AES-GCM': // Fall through + case 'AES-OCB': + // Fall through case 'AES-KW': parameters.alg = require('internal/crypto/aes') - .getAlgorithmName(key.algorithm.name, key.algorithm.length); + .getAlgorithmName(key[kAlgorithm].name, key[kAlgorithm].length); break; - case 'HMAC': - parameters.alg = normalizeHashName( - key.algorithm.hash.name, + case 'ChaCha20-Poly1305': + parameters.alg = 'C20P'; + break; + case 'HMAC': { + const alg = normalizeHashName( + key[kAlgorithm].hash.name, normalizeHashName.kContextJwkHmac); + if (alg) parameters.alg = alg; break; + } default: return undefined; } @@ -481,25 +616,25 @@ async function exportKey(format, key) { }); try { - normalizeAlgorithm(key.algorithm, 'exportKey'); + normalizeAlgorithm(key[kAlgorithm], 'exportKey'); } catch { throw lazyDOMException( - `${key.algorithm.name} key export is not supported`, 'NotSupportedError'); + `${key[kAlgorithm].name} key export is not supported`, 'NotSupportedError'); } - if (!key.extractable) + if (!key[kExtractable]) throw lazyDOMException('key is not extractable', 'InvalidAccessException'); let result; switch (format) { case 'spki': { - if (key.type === 'public') { + if (key[kKeyType] === 'public') { result = await exportKeySpki(key); } break; } case 'pkcs8': { - if (key.type === 'private') { + if (key[kKeyType] === 'private') { result = await exportKeyPkcs8(key); } break; @@ -508,15 +643,29 @@ async function exportKey(format, key) { result = await exportKeyJWK(key); break; } - case 'raw': { - if (key.type === 'secret') { - result = await exportKeyRawSecret(key); - break; + case 'raw-secret': { + if (key[kKeyType] === 'secret') { + result = await exportKeyRawSecret(key, format); } - - if (key.type === 'public') { - result = await exportKeyRawPublic(key); - break; + break; + } + case 'raw-public': { + if (key[kKeyType] === 'public') { + result = await exportKeyRawPublic(key, format); + } + break; + } + case 'raw-seed': { + if (key[kKeyType] === 'private') { + result = await exportKeyRawSeed(key); + } + break; + } + case 'raw': { + if (key[kKeyType] === 'secret') { + result = await exportKeyRawSecret(key, format); + } else if (key[kKeyType] === 'public') { + result = await exportKeyRawPublic(key, format); } break; } @@ -524,13 +673,23 @@ async function exportKey(format, key) { if (!result) { throw lazyDOMException( - `Unable to export ${key.algorithm.name} ${key.type} key using ${format} format`, + `Unable to export ${key[kAlgorithm].name} ${key[kKeyType]} key using ${format} format`, 'NotSupportedError'); } return result; } +function aliasKeyFormat(format) { + switch (format) { + case 'raw-public': + case 'raw-secret': + return 'raw'; + default: + return format; + } +} + async function importKey( format, keyData, @@ -572,12 +731,14 @@ async function importKey( case 'RSA-PSS': // Fall through case 'RSA-OAEP': + format = aliasKeyFormat(format); result = require('internal/crypto/rsa') .rsaImportKey(format, keyData, algorithm, extractable, keyUsages); break; case 'ECDSA': // Fall through case 'ECDH': + format = aliasKeyFormat(format); result = require('internal/crypto/ec') .ecImportKey(format, keyData, algorithm, extractable, keyUsages); break; @@ -588,10 +749,12 @@ async function importKey( case 'X25519': // Fall through case 'X448': + format = aliasKeyFormat(format); result = require('internal/crypto/cfrg') .cfrgImportKey(format, keyData, algorithm, extractable, keyUsages); break; case 'HMAC': + format = aliasKeyFormat(format); result = require('internal/crypto/mac') .hmacImportKey(format, keyData, algorithm, extractable, keyUsages); break; @@ -602,12 +765,22 @@ async function importKey( case 'AES-GCM': // Fall through case 'AES-KW': + // Fall through + case 'AES-OCB': + if (algorithm.name !== 'AES-OCB') { + format = aliasKeyFormat(format); + } result = require('internal/crypto/aes') .aesImportKey(algorithm, format, keyData, extractable, keyUsages); break; + case 'ChaCha20-Poly1305': + result = require('internal/crypto/chacha20_poly1305') + .c20pImportKey(algorithm, format, keyData, extractable, keyUsages); + break; case 'HKDF': // Fall through case 'PBKDF2': + format = aliasKeyFormat(format); result = importGenericSecretKey( algorithm, format, @@ -615,6 +788,22 @@ async function importKey( extractable, keyUsages); break; + case 'ML-DSA-44': + // Fall through + case 'ML-DSA-65': + // Fall through + case 'ML-DSA-87': + result = require('internal/crypto/ml_dsa') + .mlDsaImportKey(format, keyData, algorithm, extractable, keyUsages); + break; + case 'ML-KEM-512': + // Fall through + case 'ML-KEM-768': + // Fall through + case 'ML-KEM-1024': + result = require('internal/crypto/ml_kem') + .mlKemImportKey(format, keyData, algorithm, extractable, keyUsages); + break; } if (!result) { @@ -623,7 +812,7 @@ async function importKey( 'NotSupportedError'); } - if ((result.type === 'secret' || result.type === 'private') && result.usages.length === 0) { + if ((result.type === 'secret' || result.type === 'private') && result[kKeyUsages].length === 0) { throw lazyDOMException( `Usages cannot be empty when importing a ${result.type} key.`, 'SyntaxError'); @@ -771,8 +960,8 @@ function signVerify(algorithm, key, data, signature) { } algorithm = normalizeAlgorithm(algorithm, usage); - if (!ArrayPrototypeIncludes(key.usages, usage) || - algorithm.name !== key.algorithm.name) { + if (!ArrayPrototypeIncludes(key[kKeyUsages], usage) || + algorithm.name !== key[kAlgorithm].name) { throw lazyDOMException( `Unable to use this key to ${usage}`, 'InvalidAccessError'); @@ -796,6 +985,13 @@ function signVerify(algorithm, key, data, signature) { case 'HMAC': return require('internal/crypto/mac') .hmacSignVerify(key, data, algorithm, signature); + case 'ML-DSA-44': + // Fall through + case 'ML-DSA-65': + // Fall through + case 'ML-DSA-87': + return require('internal/crypto/ml_dsa') + .mlDsaSignVerify(key, data, algorithm, signature); } throw lazyDOMException('Unrecognized algorithm name', 'NotSupportedError'); } @@ -854,8 +1050,8 @@ async function cipherOrWrap(mode, algorithm, key, data, op) { // in this case. Both Firefox and Chrome throw simple TypeErrors here. // The key algorithm and cipher algorithm must match, and the // key must have the proper usage. - if (key.algorithm.name !== algorithm.name || - !ArrayPrototypeIncludes(key.usages, op)) { + if (key[kAlgorithm].name !== algorithm.name || + !ArrayPrototypeIncludes(key[kKeyUsages], op)) { throw lazyDOMException( 'The requested operation is not valid for the provided key', 'InvalidAccessError'); @@ -875,8 +1071,13 @@ async function cipherOrWrap(mode, algorithm, key, data, op) { case 'AES-CBC': // Fall through case 'AES-GCM': + // Fall through + case 'AES-OCB': return require('internal/crypto/aes') .aesCipher(mode, key, data, algorithm); + case 'ChaCha20-Poly1305': + return require('internal/crypto/chacha20_poly1305') + .c20pCipher(mode, key, data, algorithm); case 'AES-KW': if (op === 'wrapKey' || op === 'unwrapKey') { return require('internal/crypto/aes') @@ -932,6 +1133,255 @@ async function decrypt(algorithm, key, data) { return cipherOrWrap(kWebCryptoCipherDecrypt, algorithm, key, data, 'decrypt'); } +// Implements https://wicg.github.io/webcrypto-modern-algos/#SubtleCrypto-method-getPublicKey +async function getPublicKey(key, keyUsages) { + emitExperimentalWarning('The getPublicKey Web Crypto API method'); + if (this !== subtle) throw new ERR_INVALID_THIS('SubtleCrypto'); + + webidl ??= require('internal/crypto/webidl'); + const prefix = "Failed to execute 'getPublicKey' on 'SubtleCrypto'"; + webidl.requiredArguments(arguments.length, 2, { prefix }); + key = webidl.converters.CryptoKey(key, { + prefix, + context: '1st argument', + }); + keyUsages = webidl.converters['sequence'](keyUsages, { + prefix, + context: '2nd argument', + }); + + if (key[kKeyType] !== 'private') + throw lazyDOMException('key must be a private key', + key[kKeyType] === 'secret' ? 'NotSupportedError' : 'InvalidAccessError'); + + const keyObject = createPublicKey(key[kKeyObject]); + + return keyObject.toCryptoKey(key[kAlgorithm], true, keyUsages); +} + +async function encapsulateBits(encapsulationAlgorithm, encapsulationKey) { + emitExperimentalWarning('The encapsulateBits Web Crypto API method'); + if (this !== subtle) throw new ERR_INVALID_THIS('SubtleCrypto'); + + webidl ??= require('internal/crypto/webidl'); + const prefix = "Failed to execute 'encapsulateBits' on 'SubtleCrypto'"; + webidl.requiredArguments(arguments.length, 2, { prefix }); + encapsulationAlgorithm = webidl.converters.AlgorithmIdentifier(encapsulationAlgorithm, { + prefix, + context: '1st argument', + }); + encapsulationKey = webidl.converters.CryptoKey(encapsulationKey, { + prefix, + context: '2nd argument', + }); + + const normalizedEncapsulationAlgorithm = normalizeAlgorithm(encapsulationAlgorithm, 'encapsulate'); + + if (normalizedEncapsulationAlgorithm.name !== encapsulationKey[kAlgorithm].name) { + throw lazyDOMException( + 'key algorithm mismatch', + 'InvalidAccessError'); + } + + if (!ArrayPrototypeIncludes(encapsulationKey[kKeyUsages], 'encapsulateBits')) { + throw lazyDOMException( + 'encapsulationKey does not have encapsulateBits usage', + 'InvalidAccessError'); + } + + switch (encapsulationKey[kAlgorithm].name) { + case 'ML-KEM-512': + case 'ML-KEM-768': + case 'ML-KEM-1024': + return require('internal/crypto/ml_kem') + .mlKemEncapsulate(encapsulationKey); + } + + throw lazyDOMException('Unrecognized algorithm name', 'NotSupportedError'); +} + +async function encapsulateKey(encapsulationAlgorithm, encapsulationKey, sharedKeyAlgorithm, extractable, usages) { + emitExperimentalWarning('The encapsulateKey Web Crypto API method'); + if (this !== subtle) throw new ERR_INVALID_THIS('SubtleCrypto'); + + webidl ??= require('internal/crypto/webidl'); + const prefix = "Failed to execute 'encapsulateKey' on 'SubtleCrypto'"; + webidl.requiredArguments(arguments.length, 5, { prefix }); + encapsulationAlgorithm = webidl.converters.AlgorithmIdentifier(encapsulationAlgorithm, { + prefix, + context: '1st argument', + }); + encapsulationKey = webidl.converters.CryptoKey(encapsulationKey, { + prefix, + context: '2nd argument', + }); + sharedKeyAlgorithm = webidl.converters.AlgorithmIdentifier(sharedKeyAlgorithm, { + prefix, + context: '3rd argument', + }); + extractable = webidl.converters.boolean(extractable, { + prefix, + context: '4th argument', + }); + usages = webidl.converters['sequence'](usages, { + prefix, + context: '5th argument', + }); + + const normalizedEncapsulationAlgorithm = normalizeAlgorithm(encapsulationAlgorithm, 'encapsulate'); + const normalizedSharedKeyAlgorithm = normalizeAlgorithm(sharedKeyAlgorithm, 'importKey'); + + if (normalizedEncapsulationAlgorithm.name !== encapsulationKey[kAlgorithm].name) { + throw lazyDOMException( + 'key algorithm mismatch', + 'InvalidAccessError'); + } + + if (!ArrayPrototypeIncludes(encapsulationKey[kKeyUsages], 'encapsulateKey')) { + throw lazyDOMException( + 'encapsulationKey does not have encapsulateKey usage', + 'InvalidAccessError'); + } + + let encapsulateBits; + switch (encapsulationKey[kAlgorithm].name) { + case 'ML-KEM-512': + case 'ML-KEM-768': + case 'ML-KEM-1024': + encapsulateBits = await require('internal/crypto/ml_kem') + .mlKemEncapsulate(encapsulationKey); + break; + default: + throw lazyDOMException('Unrecognized algorithm name', 'NotSupportedError'); + } + + const sharedKey = await ReflectApply( + importKey, + this, + ['raw-secret', encapsulateBits.sharedKey, normalizedSharedKeyAlgorithm, extractable, usages], + ); + + const encapsulatedKey = { + ciphertext: encapsulateBits.ciphertext, + sharedKey, + }; + + return encapsulatedKey; +} + +async function decapsulateBits(decapsulationAlgorithm, decapsulationKey, ciphertext) { + emitExperimentalWarning('The decapsulateBits Web Crypto API method'); + if (this !== subtle) throw new ERR_INVALID_THIS('SubtleCrypto'); + + webidl ??= require('internal/crypto/webidl'); + const prefix = "Failed to execute 'decapsulateBits' on 'SubtleCrypto'"; + webidl.requiredArguments(arguments.length, 3, { prefix }); + decapsulationAlgorithm = webidl.converters.AlgorithmIdentifier(decapsulationAlgorithm, { + prefix, + context: '1st argument', + }); + decapsulationKey = webidl.converters.CryptoKey(decapsulationKey, { + prefix, + context: '2nd argument', + }); + ciphertext = webidl.converters.BufferSource(ciphertext, { + prefix, + context: '3rd argument', + }); + + const normalizedDecapsulationAlgorithm = normalizeAlgorithm(decapsulationAlgorithm, 'decapsulate'); + + if (normalizedDecapsulationAlgorithm.name !== decapsulationKey[kAlgorithm].name) { + throw lazyDOMException( + 'key algorithm mismatch', + 'InvalidAccessError'); + } + + if (!ArrayPrototypeIncludes(decapsulationKey[kKeyUsages], 'decapsulateBits')) { + throw lazyDOMException( + 'decapsulationKey does not have decapsulateBits usage', + 'InvalidAccessError'); + } + + switch (decapsulationKey[kAlgorithm].name) { + case 'ML-KEM-512': + case 'ML-KEM-768': + case 'ML-KEM-1024': + return require('internal/crypto/ml_kem') + .mlKemDecapsulate(decapsulationKey, ciphertext); + } + + throw lazyDOMException('Unrecognized algorithm name', 'NotSupportedError'); +} + +async function decapsulateKey( + decapsulationAlgorithm, decapsulationKey, ciphertext, sharedKeyAlgorithm, extractable, usages, +) { + emitExperimentalWarning('The decapsulateKey Web Crypto API method'); + if (this !== subtle) throw new ERR_INVALID_THIS('SubtleCrypto'); + + webidl ??= require('internal/crypto/webidl'); + const prefix = "Failed to execute 'decapsulateKey' on 'SubtleCrypto'"; + webidl.requiredArguments(arguments.length, 6, { prefix }); + decapsulationAlgorithm = webidl.converters.AlgorithmIdentifier(decapsulationAlgorithm, { + prefix, + context: '1st argument', + }); + decapsulationKey = webidl.converters.CryptoKey(decapsulationKey, { + prefix, + context: '2nd argument', + }); + ciphertext = webidl.converters.BufferSource(ciphertext, { + prefix, + context: '3rd argument', + }); + sharedKeyAlgorithm = webidl.converters.AlgorithmIdentifier(sharedKeyAlgorithm, { + prefix, + context: '4th argument', + }); + extractable = webidl.converters.boolean(extractable, { + prefix, + context: '5th argument', + }); + usages = webidl.converters['sequence'](usages, { + prefix, + context: '6th argument', + }); + + const normalizedDecapsulationAlgorithm = normalizeAlgorithm(decapsulationAlgorithm, 'decapsulate'); + const normalizedSharedKeyAlgorithm = normalizeAlgorithm(sharedKeyAlgorithm, 'importKey'); + + if (normalizedDecapsulationAlgorithm.name !== decapsulationKey[kAlgorithm].name) { + throw lazyDOMException( + 'key algorithm mismatch', + 'InvalidAccessError'); + } + + if (!ArrayPrototypeIncludes(decapsulationKey[kKeyUsages], 'decapsulateKey')) { + throw lazyDOMException( + 'decapsulationKey does not have decapsulateKey usage', + 'InvalidAccessError'); + } + + let decapsulatedBits; + switch (decapsulationKey[kAlgorithm].name) { + case 'ML-KEM-512': + case 'ML-KEM-768': + case 'ML-KEM-1024': + decapsulatedBits = await require('internal/crypto/ml_kem') + .mlKemDecapsulate(decapsulationKey, ciphertext); + break; + default: + throw lazyDOMException('Unrecognized algorithm name', 'NotSupportedError'); + } + + return ReflectApply( + importKey, + this, + ['raw-secret', decapsulatedBits, normalizedSharedKeyAlgorithm, extractable, usages], + ); +} + // The SubtleCrypto and Crypto classes are defined as part of the // Web Crypto API standard: https://www.w3.org/TR/WebCryptoAPI/ @@ -939,7 +1389,228 @@ class SubtleCrypto { constructor() { throw new ERR_ILLEGAL_CONSTRUCTOR(); } + + // Implements https://wicg.github.io/webcrypto-modern-algos/#SubtleCrypto-method-supports + static supports(operation, algorithm, lengthOrAdditionalAlgorithm = null) { + emitExperimentalWarning('The supports Web Crypto API method'); + if (this !== SubtleCrypto) throw new ERR_INVALID_THIS('SubtleCrypto constructor'); + webidl ??= require('internal/crypto/webidl'); + const prefix = "Failed to execute 'supports' on 'SubtleCrypto'"; + webidl.requiredArguments(arguments.length, 2, { prefix }); + + operation = webidl.converters.DOMString(operation, { + prefix, + context: '1st argument', + }); + algorithm = webidl.converters.AlgorithmIdentifier(algorithm, { + prefix, + context: '2nd argument', + }); + + switch (operation) { + case 'decapsulateBits': + case 'decapsulateKey': + case 'decrypt': + case 'deriveBits': + case 'deriveKey': + case 'digest': + case 'encapsulateBits': + case 'encapsulateKey': + case 'encrypt': + case 'exportKey': + case 'generateKey': + case 'getPublicKey': + case 'importKey': + case 'sign': + case 'unwrapKey': + case 'verify': + case 'wrapKey': + break; + default: + return false; + } + + let length; + let additionalAlgorithm; + if (operation === 'deriveKey') { + additionalAlgorithm = webidl.converters.AlgorithmIdentifier(lengthOrAdditionalAlgorithm, { + prefix, + context: '3rd argument', + }); + + if (!check('importKey', additionalAlgorithm)) { + return false; + } + + try { + length = getKeyLength(normalizeAlgorithm(additionalAlgorithm, 'get key length')); + } catch { + return false; + } + + operation = 'deriveBits'; + } else if (operation === 'wrapKey') { + additionalAlgorithm = webidl.converters.AlgorithmIdentifier(lengthOrAdditionalAlgorithm, { + prefix, + context: '3rd argument', + }); + + if (!check('exportKey', additionalAlgorithm)) { + return false; + } + } else if (operation === 'unwrapKey') { + additionalAlgorithm = webidl.converters.AlgorithmIdentifier(lengthOrAdditionalAlgorithm, { + prefix, + context: '3rd argument', + }); + + if (!check('importKey', additionalAlgorithm)) { + return false; + } + } else if (operation === 'deriveBits') { + length = lengthOrAdditionalAlgorithm; + if (length !== null) { + length = webidl.converters['unsigned long'](length, { + prefix, + context: '3rd argument', + }); + } + } else if (operation === 'getPublicKey') { + let normalizedAlgorithm; + try { + normalizedAlgorithm = normalizeAlgorithm(algorithm, 'exportKey'); + } catch { + return false; + } + + switch (StringPrototypeSlice(normalizedAlgorithm.name, 0, 2)) { + case 'ML': // ML-DSA-*, ML-KEM-* + case 'SL': // SLH-DSA-* + case 'RS': // RSA-OAEP, RSA-PSS, RSASSA-PKCS1-v1_5 + case 'EC': // ECDSA, ECDH + case 'Ed': // Ed* + case 'X2': // X25519 + case 'X4': // X448 + return true; + default: + return false; + } + } else if (operation === 'encapsulateKey' || operation === 'decapsulateKey') { + additionalAlgorithm = webidl.converters.AlgorithmIdentifier(lengthOrAdditionalAlgorithm, { + prefix, + context: '3rd argument', + }); + + let normalizedAdditionalAlgorithm; + try { + normalizedAdditionalAlgorithm = normalizeAlgorithm(additionalAlgorithm, 'importKey'); + } catch { + return false; + } + + switch (normalizedAdditionalAlgorithm.name) { + case 'AES-OCB': + case 'AES-KW': + case 'AES-GCM': + case 'AES-CTR': + case 'AES-CBC': + case 'ChaCha20-Poly1305': + case 'HKDF': + case 'PBKDF2': + case 'Argon2i': + case 'Argon2d': + case 'Argon2id': + break; + case 'HMAC': + case 'KMAC128': + case 'KMAC256': + if (normalizedAdditionalAlgorithm.length === undefined || normalizedAdditionalAlgorithm.length === 256) { + break; + } + return false; + default: + return false; + } + } + + return check(operation, algorithm, length); + } } + +function check(op, alg, length) { + if (op === 'encapsulateBits' || op === 'encapsulateKey') { + op = 'encapsulate'; + } + + if (op === 'decapsulateBits' || op === 'decapsulateKey') { + op = 'decapsulate'; + } + + let normalizedAlgorithm; + try { + normalizedAlgorithm = normalizeAlgorithm(alg, op); + } catch { + if (op === 'wrapKey') { + return check('encrypt', alg); + } + + if (op === 'unwrapKey') { + return check('decrypt', alg); + } + + return false; + } + + switch (op) { + case 'decapsulate': + case 'decrypt': + case 'digest': + case 'encapsulate': + case 'encrypt': + case 'exportKey': + case 'importKey': + case 'sign': + case 'unwrapKey': + case 'verify': + case 'wrapKey': + return true; + case 'deriveBits': { + if (normalizedAlgorithm.name === 'HKDF') { + try { + require('internal/crypto/hkdf').validateHkdfDeriveBitsLength(length); + } catch { + return false; + } + } + + if (normalizedAlgorithm.name === 'PBKDF2') { + try { + require('internal/crypto/pbkdf2').validatePbkdf2DeriveBitsLength(length); + } catch { + return false; + } + } + + return true; + } + case 'generateKey': { + if ( + normalizedAlgorithm.name === 'HMAC' && + normalizedAlgorithm.length === undefined && + StringPrototypeStartsWith(normalizedAlgorithm.hash.name, 'SHA3-') + ) { + return false; + } + + return true; + } + default: { + const assert = require('internal/assert'); + assert.fail('Unreachable code'); + } + } +} + const subtle = ReflectConstruct(function() {}, [], SubtleCrypto); class Crypto { @@ -1083,6 +1754,41 @@ ObjectDefineProperties( writable: true, value: unwrapKey, }, + getPublicKey: { + __proto__: null, + enumerable: true, + configurable: true, + writable: true, + value: getPublicKey, + }, + encapsulateBits: { + __proto__: null, + enumerable: true, + configurable: true, + writable: true, + value: encapsulateBits, + }, + encapsulateKey: { + __proto__: null, + enumerable: true, + configurable: true, + writable: true, + value: encapsulateKey, + }, + decapsulateBits: { + __proto__: null, + enumerable: true, + configurable: true, + writable: true, + value: decapsulateBits, + }, + decapsulateKey: { + __proto__: null, + enumerable: true, + configurable: true, + writable: true, + value: decapsulateKey, + }, }); module.exports = { diff --git a/lib/internal/crypto/webidl.js b/lib/internal/crypto/webidl.js index cba02279977e4b..1bec09b17437a0 100644 --- a/lib/internal/crypto/webidl.js +++ b/lib/internal/crypto/webidl.js @@ -23,6 +23,8 @@ const { ObjectPrototypeIsPrototypeOf, SafeArrayIterator, String, + StringPrototypeStartsWith, + StringPrototypeToLowerCase, TypedArrayPrototypeGetBuffer, TypedArrayPrototypeGetSymbolToStringTag, } = primordials; @@ -192,6 +194,16 @@ converters.object = (V, opts) => { const isNonSharedArrayBuffer = isArrayBuffer; +function ensureSHA(V, label) { + if ( + typeof V === 'string' ? + !StringPrototypeStartsWith(StringPrototypeToLowerCase(V), 'sha') : + V.name?.toLowerCase?.().startsWith('sha') === false + ) + throw lazyDOMException( + `Only SHA hashes are supported in ${label}`, 'NotSupportedError'); +} + converters.Uint8Array = (V, opts = kEmptyObject) => { if (!ArrayBufferIsView(V) || TypedArrayPrototypeGetSymbolToStringTag(V) !== 'Uint8Array') { @@ -332,6 +344,10 @@ converters.AlgorithmIdentifier = (V, opts) => { converters.KeyFormat = createEnumConverter('KeyFormat', [ 'raw', + 'raw-public', + 'raw-seed', + 'raw-secret', + 'raw-private', 'pkcs8', 'spki', 'jwk', @@ -346,6 +362,10 @@ converters.KeyUsage = createEnumConverter('KeyUsage', [ 'deriveBits', 'wrapKey', 'unwrapKey', + 'encapsulateBits', + 'decapsulateBits', + 'encapsulateKey', + 'decapsulateKey', ]); converters['sequence'] = createSequenceConverter(converters.KeyUsage); @@ -389,6 +409,7 @@ converters.RsaHashedKeyGenParams = createDictionaryConverter( { key: 'hash', converter: converters.HashAlgorithmIdentifier, + validator: (V, dict) => ensureSHA(V, 'RsaHashedKeyGenParams'), required: true, }, ]); @@ -399,6 +420,7 @@ converters.RsaHashedImportParams = createDictionaryConverter( { key: 'hash', converter: converters.HashAlgorithmIdentifier, + validator: (V, dict) => ensureSHA(V, 'RsaHashedImportParams'), required: true, }, ]); @@ -445,6 +467,7 @@ converters.HmacKeyGenParams = createDictionaryConverter( { key: 'hash', converter: converters.HashAlgorithmIdentifier, + validator: (V, dict) => ensureSHA(V, 'HmacKeyGenParams'), required: true, }, { @@ -464,6 +487,15 @@ function validateHmacKeyAlgorithm(length) { throw lazyDOMException('Unsupported algorithm.length', 'NotSupportedError'); } +function validateZeroLength(parameterName) { + return (V, dict) => { + if (V.byteLength) { + throw lazyDOMException( + `Non zero-length ${parameterName} is not supported.`, 'NotSupportedError'); + } + }; +} + converters.RsaPssParams = createDictionaryConverter( 'RsaPssParams', [ ...new SafeArrayIterator(dictAlgorithm), @@ -490,6 +522,7 @@ converters.EcdsaParams = createDictionaryConverter( { key: 'hash', converter: converters.HashAlgorithmIdentifier, + validator: (V, dict) => ensureSHA(V, 'EcdsaParams'), required: true, }, ]); @@ -500,6 +533,7 @@ converters.HmacImportParams = createDictionaryConverter( { key: 'hash', converter: converters.HashAlgorithmIdentifier, + validator: (V, dict) => ensureSHA(V, 'HmacImportParams'), required: true, }, { @@ -545,6 +579,8 @@ converters.JsonWebKey = createDictionaryConverter( simpleDomStringKey('dp'), simpleDomStringKey('dq'), simpleDomStringKey('qi'), + simpleDomStringKey('pub'), + simpleDomStringKey('priv'), { key: 'oth', converter: converters['sequence'], @@ -558,6 +594,7 @@ converters.HkdfParams = createDictionaryConverter( { key: 'hash', converter: converters.HashAlgorithmIdentifier, + validator: (V, dict) => ensureSHA(V, 'HkdfParams'), required: true, }, { @@ -572,12 +609,40 @@ converters.HkdfParams = createDictionaryConverter( }, ]); +converters.CShakeParams = createDictionaryConverter( + 'CShakeParams', [ + ...new SafeArrayIterator(dictAlgorithm), + { + key: 'length', + converter: (V, opts) => + converters['unsigned long'](V, { ...opts, enforceRange: true }), + validator: (V, opts) => { + // The Web Crypto spec allows for SHAKE output length that are not multiples of + // 8. We don't. + if (V % 8) + throw lazyDOMException('Unsupported CShakeParams length', 'NotSupportedError'); + }, + required: true, + }, + { + key: 'functionName', + converter: converters.BufferSource, + validator: validateZeroLength('CShakeParams.functionName'), + }, + { + key: 'customization', + converter: converters.BufferSource, + validator: validateZeroLength('CShakeParams.customization'), + }, + ]); + converters.Pbkdf2Params = createDictionaryConverter( 'Pbkdf2Params', [ ...new SafeArrayIterator(dictAlgorithm), { key: 'hash', converter: converters.HashAlgorithmIdentifier, + validator: (V, dict) => ensureSHA(V, 'Pbkdf2Params'), required: true, }, { @@ -620,13 +685,29 @@ converters.AesCbcParams = createDictionaryConverter( }, ]); -converters.AesGcmParams = createDictionaryConverter( - 'AesGcmParams', [ +converters.AeadParams = createDictionaryConverter( + 'AeadParams', [ ...new SafeArrayIterator(dictAlgorithm), { key: 'iv', converter: converters.BufferSource, - validator: (V, dict) => validateMaxBufferLength(V, 'algorithm.iv'), + validator: (V, dict) => { + switch (StringPrototypeToLowerCase(dict.name)) { + case 'chacha20-poly1305': + validateByteLength(V, 'algorithm.iv', 12); + break; + case 'aes-gcm': + validateMaxBufferLength(V, 'algorithm.iv'); + break; + case 'aes-ocb': + if (V.byteLength > 15) { + throw lazyDOMException( + 'AES-OCB algorithm.iv must be no more than 15 bytes', + 'OperationError'); + } + break; + } + }, required: true, }, { @@ -634,10 +715,28 @@ converters.AesGcmParams = createDictionaryConverter( converter: (V, opts) => converters.octet(V, { ...opts, enforceRange: true }), validator: (V, dict) => { - if (!ArrayPrototypeIncludes([32, 64, 96, 104, 112, 120, 128], V)) { - throw lazyDOMException( - `${V} is not a valid AES-GCM tag length`, - 'OperationError'); + switch (StringPrototypeToLowerCase(dict.name)) { + case 'chacha20-poly1305': + if (V !== 128) { + throw lazyDOMException( + `${V} is not a valid ChaCha20-Poly1305 tag length`, + 'OperationError'); + } + break; + case 'aes-gcm': + if (!ArrayPrototypeIncludes([32, 64, 96, 104, 112, 120, 128], V)) { + throw lazyDOMException( + `${V} is not a valid AES-GCM tag length`, + 'OperationError'); + } + break; + case 'aes-ocb': + if (!ArrayPrototypeIncludes([64, 96, 128], V)) { + throw lazyDOMException( + `${V} is not a valid AES-OCB tag length`, + 'OperationError'); + } + break; } }, }, @@ -685,29 +784,26 @@ converters.EcdhKeyDeriveParams = createDictionaryConverter( throw lazyDOMException( 'algorithm.public must be a public key', 'InvalidAccessError'); - if (V.algorithm.name.toUpperCase() !== dict.name.toUpperCase()) + if (StringPrototypeToLowerCase(V.algorithm.name) !== StringPrototypeToLowerCase(dict.name)) throw lazyDOMException( - `algorithm.public must be an ${dict.name.toUpperCase()} key`, + 'key algorithm mismatch', 'InvalidAccessError'); }, required: true, }, ]); -converters.Ed448Params = createDictionaryConverter( - 'Ed448Params', [ - ...new SafeArrayIterator(dictAlgorithm), - { - key: 'context', - converter: converters.BufferSource, - validator: (V, dict) => { - if (V.byteLength) - throw lazyDOMException( - 'Non zero-length context is not supported.', 'NotSupportedError'); +for (const name of ['Ed448Params', 'ContextParams']) { + converters[name] = createDictionaryConverter( + name, [ + ...new SafeArrayIterator(dictAlgorithm), + { + key: 'context', + converter: converters.BufferSource, + validator: validateZeroLength(`${name}.context`), }, - required: false, - }, - ]); + ]); +} module.exports = { converters, diff --git a/lib/internal/errors.js b/lib/internal/errors.js index ade2acdc662312..8a6f5b26f5391c 100644 --- a/lib/internal/errors.js +++ b/lib/internal/errors.js @@ -1150,6 +1150,7 @@ E('ERR_CONSOLE_WRITABLE_STREAM', 'Console expects a writable stream instance for %s', TypeError); E('ERR_CONSTRUCT_CALL_REQUIRED', 'Class constructor %s cannot be invoked without `new`', TypeError); E('ERR_CONTEXT_NOT_INITIALIZED', 'context used is not initialized', Error); +E('ERR_CRYPTO_ARGON2_NOT_SUPPORTED', 'Argon2 algorithm not supported', Error); E('ERR_CRYPTO_CUSTOM_ENGINE_NOT_SUPPORTED', 'Custom engines not supported by this OpenSSL', Error); E('ERR_CRYPTO_ECDH_INVALID_FORMAT', 'Invalid ECDH format: %s', TypeError); @@ -1170,6 +1171,7 @@ E('ERR_CRYPTO_INVALID_JWK', 'Invalid JWK data', TypeError); E('ERR_CRYPTO_INVALID_KEY_OBJECT_TYPE', 'Invalid key object type %s, expected %s.', TypeError); E('ERR_CRYPTO_INVALID_STATE', 'Invalid state for operation %s', Error); +E('ERR_CRYPTO_KEM_NOT_SUPPORTED', 'KEM is not supported', Error); E('ERR_CRYPTO_PBKDF2_ERROR', 'PBKDF2 error', Error); E('ERR_CRYPTO_SCRYPT_NOT_SUPPORTED', 'Scrypt algorithm not supported', Error); // Switch to TypeError. The current implementation does not seem right. diff --git a/lib/internal/http.js b/lib/internal/http.js index 4f250a2e70a20f..aa3ec354dabf4a 100644 --- a/lib/internal/http.js +++ b/lib/internal/http.js @@ -112,7 +112,9 @@ class ProxyConfig { const { host, hostname, port, protocol, username, password } = new URL(proxyUrl); this.href = proxyUrl; // Full URL of the proxy server. this.host = host; // Full host including port, e.g. 'localhost:8080'. - this.hostname = hostname.replace(/^\[|\]$/g, ''); // Trim off the brackets from IPv6 addresses. + // Trim off the brackets from IPv6 addresses. As it's parsed from a valid URL, an opening + // "[" Must already have a matching "]" at the end. + this.hostname = hostname[0] === '[' ? hostname.slice(1, -1) : hostname; this.port = port ? NumberParseInt(port, 10) : (protocol === 'https:' ? 443 : 80); this.protocol = protocol; // Protocol of the proxy server, e.g. 'http:' or 'https:'. diff --git a/lib/internal/http2/core.js b/lib/internal/http2/core.js index 6bf2edd1487d49..4462fa639317f0 100644 --- a/lib/internal/http2/core.js +++ b/lib/internal/http2/core.js @@ -760,7 +760,7 @@ const deprecateWeight = deprecateProperty('weight', // When a ClientHttp2Session is first created, the socket may not yet be // connected. If request() is called during this time, the actual request // will be deferred until the socket is ready to go. -function requestOnConnect(headersList, headersParam, options) { +function requestOnConnect(headersList, options) { const session = this[kSession]; // At this point, the stream should have already been destroyed during @@ -824,7 +824,7 @@ function requestOnConnect(headersList, headersParam, options) { if (onClientStreamStartChannel.hasSubscribers) { onClientStreamStartChannel.publish({ stream: this, - headers: headersParam, + headers: this.sentHeaders, }); } } @@ -1888,7 +1888,7 @@ class ClientHttp2Session extends Http2Session { } } - const onConnect = reqAsync.bind(requestOnConnect.bind(stream, headersList, headersParam, options)); + const onConnect = reqAsync.bind(requestOnConnect.bind(stream, headersList, options)); if (this.connecting) { if (this[kPendingRequestCalls] !== null) { this[kPendingRequestCalls].push(onConnect); @@ -1906,7 +1906,7 @@ class ClientHttp2Session extends Http2Session { if (onClientStreamCreatedChannel.hasSubscribers) { onClientStreamCreatedChannel.publish({ stream, - headers: headersParam, + headers: stream.sentHeaders, }); } @@ -2541,8 +2541,31 @@ function callStreamClose(stream) { stream.close(); } -function processHeaders(oldHeaders, options) { - assertIsObject(oldHeaders, 'headers'); +function prepareResponseHeaders(stream, headersParam, options) { + let headers; + let statusCode; + + if (ArrayIsArray(headersParam)) { + ({ + headers, + statusCode, + } = prepareResponseHeadersArray(headersParam, options)); + stream[kRawHeaders] = headers; + } else { + ({ + headers, + statusCode, + } = prepareResponseHeadersObject(headersParam, options)); + stream[kSentHeaders] = headers; + } + + const headersList = buildNgHeaderString(headers, assertValidPseudoHeaderResponse); + + return { headers, headersList, statusCode }; +} + +function prepareResponseHeadersObject(oldHeaders, options) { + assertIsObject(oldHeaders, 'headers', ['Object', 'Array']); const headers = { __proto__: null }; if (oldHeaders !== null && oldHeaders !== undefined) { @@ -2563,6 +2586,44 @@ function processHeaders(oldHeaders, options) { headers[HTTP2_HEADER_DATE] ??= utcDate(); } + validatePreparedResponseHeaders(headers, statusCode); + + return { + headers, + statusCode: headers[HTTP2_HEADER_STATUS], + }; +} + +function prepareResponseHeadersArray(headers, options) { + let statusCode; + let isDateSet = false; + + for (let i = 0; i < headers.length; i += 2) { + const header = headers[i].toLowerCase(); + const value = headers[i + 1]; + + if (header === HTTP2_HEADER_STATUS) { + statusCode = value | 0; + } else if (header === HTTP2_HEADER_DATE) { + isDateSet = true; + } + } + + if (!statusCode) { + statusCode = HTTP_STATUS_OK; + headers.unshift(HTTP2_HEADER_STATUS, statusCode); + } + + if (!isDateSet && (options.sendDate == null || options.sendDate)) { + headers.push(HTTP2_HEADER_DATE, utcDate()); + } + + validatePreparedResponseHeaders(headers, statusCode); + + return { headers, statusCode }; +} + +function validatePreparedResponseHeaders(headers, statusCode) { // This is intentionally stricter than the HTTP/1 implementation, which // allows values between 100 and 999 (inclusive) in order to allow for // backwards compatibility with non-spec compliant code. With HTTP/2, @@ -2570,16 +2631,13 @@ function processHeaders(oldHeaders, options) { // This will have an impact on the compatibility layer for anyone using // non-standard, non-compliant status codes. if (statusCode < 200 || statusCode > 599) - throw new ERR_HTTP2_STATUS_INVALID(headers[HTTP2_HEADER_STATUS]); + throw new ERR_HTTP2_STATUS_INVALID(statusCode); const neverIndex = headers[kSensitiveHeaders]; if (neverIndex !== undefined && !ArrayIsArray(neverIndex)) throw new ERR_INVALID_ARG_VALUE('headers[http2.neverIndex]', neverIndex); - - return headers; } - function onFileUnpipe() { const stream = this.sink[kOwner]; if (stream.ownsFd) @@ -2882,7 +2940,7 @@ class ServerHttp2Stream extends Http2Stream { } // Initiate a response on this Http2Stream - respond(headers, options) { + respond(headersParam, options) { if (this.destroyed || this.closed) throw new ERR_HTTP2_INVALID_STREAM(); if (this.headersSent) @@ -2907,15 +2965,16 @@ class ServerHttp2Stream extends Http2Stream { state.flags |= STREAM_FLAGS_HAS_TRAILERS; } - headers = processHeaders(headers, options); - const headersList = buildNgHeaderString(headers, assertValidPseudoHeaderResponse); - this[kSentHeaders] = headers; + const { + headers, + headersList, + statusCode, + } = prepareResponseHeaders(this, headersParam, options); state.flags |= STREAM_FLAGS_HEADERS_SENT; // Close the writable side if the endStream option is set or status // is one of known codes with no payload, or it's a head request - const statusCode = headers[HTTP2_HEADER_STATUS] | 0; if (!!options.endStream || statusCode === HTTP_STATUS_NO_CONTENT || statusCode === HTTP_STATUS_RESET_CONTENT || @@ -2945,7 +3004,7 @@ class ServerHttp2Stream extends Http2Stream { // regular file, here the fd is passed directly. If the underlying // mechanism is not able to read from the fd, then the stream will be // reset with an error code. - respondWithFD(fd, headers, options) { + respondWithFD(fd, headersParam, options) { if (this.destroyed || this.closed) throw new ERR_HTTP2_INVALID_STREAM(); if (this.headersSent) @@ -2982,8 +3041,11 @@ class ServerHttp2Stream extends Http2Stream { this[kUpdateTimer](); this.ownsFd = false; - headers = processHeaders(headers, options); - const statusCode = headers[HTTP2_HEADER_STATUS] |= 0; + const { + headers, + statusCode, + } = prepareResponseHeadersObject(headersParam, options); + // Payload/DATA frames are not permitted in these cases if (statusCode === HTTP_STATUS_NO_CONTENT || statusCode === HTTP_STATUS_RESET_CONTENT || @@ -3011,7 +3073,7 @@ class ServerHttp2Stream extends Http2Stream { // giving the user an opportunity to verify the details and set additional // headers. If statCheck returns false, the operation is aborted and no // file details are sent. - respondWithFile(path, headers, options) { + respondWithFile(path, headersParam, options) { if (this.destroyed || this.closed) throw new ERR_HTTP2_INVALID_STREAM(); if (this.headersSent) @@ -3042,8 +3104,11 @@ class ServerHttp2Stream extends Http2Stream { this[kUpdateTimer](); this.ownsFd = true; - headers = processHeaders(headers, options); - const statusCode = headers[HTTP2_HEADER_STATUS] |= 0; + const { + headers, + statusCode, + } = prepareResponseHeadersObject(headersParam, options); + // Payload/DATA frames are not permitted in these cases if (statusCode === HTTP_STATUS_NO_CONTENT || statusCode === HTTP_STATUS_RESET_CONTENT || diff --git a/lib/internal/http2/util.js b/lib/internal/http2/util.js index 19cbc08f8a9c7d..77e2386c1bf4aa 100644 --- a/lib/internal/http2/util.js +++ b/lib/internal/http2/util.js @@ -690,7 +690,6 @@ function prepareRequestHeadersArray(headers, session) { const headersList = buildNgHeaderString( rawHeaders, assertValidPseudoHeader, - headers[kSensitiveHeaders], ); return { @@ -755,14 +754,14 @@ const kNoHeaderFlags = StringFromCharCode(NGHTTP2_NV_FLAG_NONE); * @returns {[string, number]} */ function buildNgHeaderString(arrayOrMap, - assertValuePseudoHeader = assertValidPseudoHeader, - sensitiveHeaders = arrayOrMap[kSensitiveHeaders]) { + assertValuePseudoHeader = assertValidPseudoHeader) { let headers = ''; let pseudoHeaders = ''; let count = 0; const singles = new SafeSet(); - const neverIndex = (sensitiveHeaders || emptyArray).map((v) => v.toLowerCase()); + const sensitiveHeaders = arrayOrMap[kSensitiveHeaders] || emptyArray; + const neverIndex = sensitiveHeaders.map((v) => v.toLowerCase()); function processHeader(key, value) { key = key.toLowerCase(); diff --git a/lib/internal/inspector/network_undici.js b/lib/internal/inspector/network_undici.js index 95be848910f3da..b4031eedcc88d1 100644 --- a/lib/internal/inspector/network_undici.js +++ b/lib/internal/inspector/network_undici.js @@ -206,6 +206,39 @@ function onClientResponseFinish({ request }) { }); } +// TODO: Move Network.webSocketCreated to the actual creation time of the WebSocket. +// undici:websocket:open fires when the connection is established, but this results +// in an inaccurate stack trace. +function onWebSocketOpen({ websocket }) { + websocket[kInspectorRequestId] = getNextRequestId(); + const url = websocket.url.toString(); + Network.webSocketCreated({ + requestId: websocket[kInspectorRequestId], + url, + }); + // TODO: Use handshake response data from undici diagnostics when available. + // https://github.com/nodejs/undici/pull/4396 + Network.webSocketHandshakeResponseReceived({ + requestId: websocket[kInspectorRequestId], + timestamp: getMonotonicTime(), + response: { + status: 101, + statusText: 'Switching Protocols', + headers: {}, + }, + }); +} + +function onWebSocketClose({ websocket }) { + if (typeof websocket[kInspectorRequestId] !== 'string') { + return; + } + Network.webSocketClosed({ + requestId: websocket[kInspectorRequestId], + timestamp: getMonotonicTime(), + }); +} + function enable() { dc.subscribe('undici:request:create', onClientRequestStart); dc.subscribe('undici:request:error', onClientRequestError); @@ -214,6 +247,8 @@ function enable() { dc.subscribe('undici:request:bodyChunkSent', onClientRequestBodyChunkSent); dc.subscribe('undici:request:bodySent', onClientRequestBodySent); dc.subscribe('undici:request:bodyChunkReceived', onClientRequestBodyChunkReceived); + dc.subscribe('undici:websocket:open', onWebSocketOpen); + dc.subscribe('undici:websocket:close', onWebSocketClose); } function disable() { @@ -224,6 +259,8 @@ function disable() { dc.unsubscribe('undici:request:bodyChunkSent', onClientRequestBodyChunkSent); dc.unsubscribe('undici:request:bodySent', onClientRequestBodySent); dc.unsubscribe('undici:request:bodyChunkReceived', onClientRequestBodyChunkReceived); + dc.unsubscribe('undici:websocket:open', onWebSocketOpen); + dc.unsubscribe('undici:websocket:close', onWebSocketClose); } module.exports = { diff --git a/lib/internal/modules/esm/loader.js b/lib/internal/modules/esm/loader.js index bc36fa2e26b8d0..0443838c8a4ba5 100644 --- a/lib/internal/modules/esm/loader.js +++ b/lib/internal/modules/esm/loader.js @@ -65,6 +65,8 @@ let debug = require('internal/util/debuglog').debuglog('esm', (fn) => { debug = fn; }); +const { isPromise } = require('internal/util/types'); + /** * @typedef {import('./hooks.js').HooksProxy} HooksProxy * @typedef {import('./module_job.js').ModuleJobBase} ModuleJobBase @@ -471,6 +473,10 @@ class ModuleLoader { const resolvedImportAttributes = resolveResult.importAttributes ?? importAttributes; let job = this.loadCache.get(url, resolvedImportAttributes.type); if (job !== undefined) { + // TODO(node:55782): this race may stop happening when the ESM resolution and loading become synchronous. + if (!job.module) { + assert.fail(getRaceMessage(url, parentURL)); + } // This module is being evaluated, which means it's imported in a previous link // in a cycle. if (job.module.getStatus() === kEvaluating) { @@ -537,7 +543,7 @@ class ModuleLoader { * matching translators. * @param {ModuleSource} source Source of the module to be translated. * @param {boolean} isMain Whether the module to be translated is the entry point. - * @returns {ModuleWrap | Promise} + * @returns {ModuleWrap} */ #translate(url, format, source, isMain) { this.validateLoadResult(url, format); @@ -547,7 +553,9 @@ class ModuleLoader { throw new ERR_UNKNOWN_MODULE_FORMAT(format, url); } - return FunctionPrototypeCall(translator, this, url, source, isMain); + const result = FunctionPrototypeCall(translator, this, url, source, isMain); + assert(result instanceof ModuleWrap); + return result; } /** @@ -586,15 +594,21 @@ class ModuleLoader { /** * Load a module and translate it into a ModuleWrap for ordinary imported ESM. - * This is run asynchronously. + * This may be run asynchronously if there are asynchronous module loader hooks registered. * @param {string} url URL of the module to be translated. * @param {object} loadContext See {@link load} * @param {boolean} isMain Whether the module to be translated is the entry point. - * @returns {Promise} + * @returns {Promise|ModuleWrap} */ - async loadAndTranslate(url, loadContext, isMain) { - const { format, source } = await this.load(url, loadContext); - return this.#translate(url, format, source, isMain); + loadAndTranslate(url, loadContext, isMain) { + const maybePromise = this.load(url, loadContext); + const afterLoad = ({ format, source }) => { + return this.#translate(url, format, source, isMain); + }; + if (isPromise(maybePromise)) { + return maybePromise.then(afterLoad); + } + return afterLoad(maybePromise); } /** diff --git a/lib/internal/modules/esm/module_job.js b/lib/internal/modules/esm/module_job.js index 268d8154457295..a3b36add4fbe6a 100644 --- a/lib/internal/modules/esm/module_job.js +++ b/lib/internal/modules/esm/module_job.js @@ -37,6 +37,7 @@ const { }, } = internalBinding('util'); const { decorateErrorStack, kEmptyObject } = require('internal/util'); +const { isPromise } = require('internal/util/types'); const { getSourceMapsSupport, } = require('internal/source_map/source_map_cache'); @@ -138,12 +139,11 @@ class ModuleJob extends ModuleJobBase { this.#loader = loader; // Expose the promise to the ModuleWrap directly for linking below. - if (isForRequireInImportedCJS) { - this.module = moduleOrModulePromise; - assert(this.module instanceof ModuleWrap); - this.modulePromise = PromiseResolve(this.module); - } else { + if (isPromise(moduleOrModulePromise)) { this.modulePromise = moduleOrModulePromise; + } else { + this.module = moduleOrModulePromise; + this.modulePromise = PromiseResolve(moduleOrModulePromise); } if (this.phase === kEvaluationPhase) { diff --git a/lib/internal/modules/esm/translators.js b/lib/internal/modules/esm/translators.js index b6ac42302a126b..98bdb384269ced 100644 --- a/lib/internal/modules/esm/translators.js +++ b/lib/internal/modules/esm/translators.js @@ -494,18 +494,14 @@ translators.set('json', function jsonStrategy(url, source) { * >} [[Instance]] slot proxy for WebAssembly Module Record */ const wasmInstances = new SafeWeakMap(); -translators.set('wasm', async function(url, source) { +translators.set('wasm', function(url, source) { assertBufferSource(source, false, 'load'); debug(`Translating WASMModule ${url}`); let compiled; try { - // TODO(joyeecheung): implement a translator that just uses - // compiled = new WebAssembly.Module(source) to compile it - // synchronously. - compiled = await WebAssembly.compile(source, { - // The ESM Integration auto-enables Wasm JS builtins by default when available. + compiled = new WebAssembly.Module(source, { builtins: ['js-string'], }); } catch (err) { diff --git a/lib/internal/net.js b/lib/internal/net.js index 4e6accb57a4297..d380d8a41982e2 100644 --- a/lib/internal/net.js +++ b/lib/internal/net.js @@ -93,8 +93,8 @@ function isLoopback(host) { return ( hostLower === 'localhost' || hostLower.startsWith('127.') || - hostLower.startsWith('[::1]') || - hostLower.startsWith('[0:0:0:0:0:0:0:1]') + hostLower === '[::1]' || + hostLower === '[0:0:0:0:0:0:0:1]' ); } diff --git a/lib/internal/options.js b/lib/internal/options.js index fef0d61d143335..be1dc6a842c526 100644 --- a/lib/internal/options.js +++ b/lib/internal/options.js @@ -11,6 +11,7 @@ const { const { getCLIOptionsValues, getCLIOptionsInfo, + getOptionsAsFlags, getEmbedderOptions: getEmbedderOptionsFromBinding, getEnvOptionsInputType, getNamespaceOptionsInputType, @@ -21,6 +22,7 @@ let warnOnAllowUnauthorized = true; let optionsDict; let cliInfo; let embedderOptions; +let optionsFlags; // getCLIOptionsValues() would serialize the option values from C++ land. // It would error if the values are queried before bootstrap is @@ -34,6 +36,10 @@ function getCLIOptionsInfoFromBinding() { return cliInfo ??= getCLIOptionsInfo(); } +function getOptionsAsFlagsFromBinding() { + return optionsFlags ??= getOptionsAsFlags(); +} + function getEmbedderOptions() { return embedderOptions ??= getEmbedderOptionsFromBinding(); } @@ -156,6 +162,7 @@ function getAllowUnauthorized() { module.exports = { getCLIOptionsInfo: getCLIOptionsInfoFromBinding, getOptionValue, + getOptionsAsFlagsFromBinding, getAllowUnauthorized, getEmbedderOptions, generateConfigJsonSchema, diff --git a/lib/internal/test_runner/harness.js b/lib/internal/test_runner/harness.js index fe5915dec13483..bd4b35f1d64033 100644 --- a/lib/internal/test_runner/harness.js +++ b/lib/internal/test_runner/harness.js @@ -26,7 +26,10 @@ const { reporterScope, shouldColorizeTestFiles, setupGlobalSetupTeardownFunctions, + parsePreviousRuns, } = require('internal/test_runner/utils'); +const { PassThrough, compose } = require('stream'); +const { reportReruns } = require('internal/test_runner/reporter/rerun'); const { queueMicrotask } = require('internal/process/task_queues'); const { TIMEOUT_MAX } = require('internal/timers'); const { clearInterval, setInterval } = require('timers'); @@ -69,6 +72,7 @@ function createTestTree(rootTestOptions, globalOptions) { shouldColorizeTestFiles: shouldColorizeTestFiles(globalOptions.destinations), teardown: null, snapshotManager: null, + previousRuns: null, isFilteringByName, isFilteringByOnly, async runBootstrap() { @@ -203,6 +207,25 @@ function collectCoverage(rootTest, coverage) { return summary; } +function setupFailureStateFile(rootTest, globalOptions) { + if (!globalOptions.rerunFailuresFilePath) { + return; + } + rootTest.harness.previousRuns = parsePreviousRuns(globalOptions.rerunFailuresFilePath); + if (rootTest.harness.previousRuns === null) { + rootTest.diagnostic(`Warning: The rerun failures file at ` + + `${globalOptions.rerunFailuresFilePath} is not a valid rerun file. ` + + 'The test runner will not be able to rerun failed tests.'); + rootTest.harness.success = false; + process.exitCode = kGenericUserError; + return; + } + if (!process.env.NODE_TEST_CONTEXT) { + const reporter = reportReruns(rootTest.harness.previousRuns, globalOptions); + compose(rootTest.reporter, reporter).pipe(new PassThrough()); + } +} + function setupProcessState(root, globalOptions) { const hook = createHook({ __proto__: null, @@ -230,6 +253,9 @@ function setupProcessState(root, globalOptions) { const rejectionHandler = createProcessEventHandler('unhandledRejection', root); const coverage = configureCoverage(root, globalOptions); + + setupFailureStateFile(root, globalOptions); + const exitHandler = async (kill) => { if (root.subtests.length === 0 && (root.hooks.before.length > 0 || root.hooks.after.length > 0)) { // Run global before/after hooks in case there are no tests diff --git a/lib/internal/test_runner/reporter/junit.js b/lib/internal/test_runner/reporter/junit.js index 0b2efe119fedee..24bbd9ffc3e1cc 100644 --- a/lib/internal/test_runner/reporter/junit.js +++ b/lib/internal/test_runner/reporter/junit.js @@ -58,7 +58,7 @@ function isFailure(node) { } function isSkipped(node) { - return (node?.children && ArrayPrototypeSome(node.children, (c) => c.tag === 'skipped')) || node?.attrs?.failures; + return (node?.children && ArrayPrototypeSome(node.children, (c) => c.tag === 'skipped')) || node?.attrs?.skipped; } module.exports = async function* junitReporter(source) { diff --git a/lib/internal/test_runner/reporter/rerun.js b/lib/internal/test_runner/reporter/rerun.js new file mode 100644 index 00000000000000..1862dafc04ea71 --- /dev/null +++ b/lib/internal/test_runner/reporter/rerun.js @@ -0,0 +1,40 @@ +'use strict'; + +const { + ArrayPrototypePush, + JSONStringify, +} = primordials; +const { relative } = require('path'); +const { writeFileSync } = require('fs'); + +function reportReruns(previousRuns, globalOptions) { + return async function reporter(source) { + const obj = { __proto__: null }; + const disambiguator = { __proto__: null }; + + for await (const { type, data } of source) { + if (type === 'test:pass') { + let identifier = `${relative(globalOptions.cwd, data.file)}:${data.line}:${data.column}`; + if (disambiguator[identifier] !== undefined) { + identifier += `:(${disambiguator[identifier]})`; + disambiguator[identifier] += 1; + } else { + disambiguator[identifier] = 1; + } + obj[identifier] = { + __proto__: null, + name: data.name, + passed_on_attempt: data.details.passed_on_attempt ?? data.details.attempt, + }; + } + } + + ArrayPrototypePush(previousRuns, obj); + writeFileSync(globalOptions.rerunFailuresFilePath, JSONStringify(previousRuns, null, 2), 'utf8'); + }; +}; + +module.exports = { + __proto__: null, + reportReruns, +}; diff --git a/lib/internal/test_runner/runner.js b/lib/internal/test_runner/runner.js index 958ad1e060fbbe..1340b817a57e5b 100644 --- a/lib/internal/test_runner/runner.js +++ b/lib/internal/test_runner/runner.js @@ -35,7 +35,7 @@ const { spawn } = require('child_process'); const { finished } = require('internal/streams/end-of-stream'); const { resolve, sep, isAbsolute } = require('path'); const { DefaultDeserializer, DefaultSerializer } = require('v8'); -const { getOptionValue } = require('internal/options'); +const { getOptionValue, getOptionsAsFlagsFromBinding } = require('internal/options'); const { Interface } = require('internal/readline/interface'); const { deserializeError } = require('internal/error_serdes'); const { Buffer } = require('buffer'); @@ -148,40 +148,45 @@ function getRunArgs(path, { forceExit, only, argv: suppliedArgs, execArgv, + rerunFailuresFilePath, root: { timeout }, cwd }) { - const argv = ArrayPrototypeFilter(process.execArgv, filterExecArgv); + const processNodeOptions = getOptionsAsFlagsFromBinding(); + const runArgs = ArrayPrototypeFilter(processNodeOptions, filterExecArgv); if (forceExit === true) { - ArrayPrototypePush(argv, '--test-force-exit'); + ArrayPrototypePush(runArgs, '--test-force-exit'); } if (isUsingInspector()) { - ArrayPrototypePush(argv, `--inspect-port=${getInspectPort(inspectPort)}`); + ArrayPrototypePush(runArgs, `--inspect-port=${getInspectPort(inspectPort)}`); } if (testNamePatterns != null) { - ArrayPrototypeForEach(testNamePatterns, (pattern) => ArrayPrototypePush(argv, `--test-name-pattern=${pattern}`)); + ArrayPrototypeForEach(testNamePatterns, (pattern) => ArrayPrototypePush(runArgs, `--test-name-pattern=${pattern}`)); } if (testSkipPatterns != null) { - ArrayPrototypeForEach(testSkipPatterns, (pattern) => ArrayPrototypePush(argv, `--test-skip-pattern=${pattern}`)); + ArrayPrototypeForEach(testSkipPatterns, (pattern) => ArrayPrototypePush(runArgs, `--test-skip-pattern=${pattern}`)); } if (only === true) { - ArrayPrototypePush(argv, '--test-only'); + ArrayPrototypePush(runArgs, '--test-only'); } if (timeout != null) { - ArrayPrototypePush(argv, `--test-timeout=${timeout}`); + ArrayPrototypePush(runArgs, `--test-timeout=${timeout}`); + } + if (rerunFailuresFilePath) { + ArrayPrototypePush(runArgs, `--test-rerun-failures=${rerunFailuresFilePath}`); } - ArrayPrototypePushApply(argv, execArgv); + ArrayPrototypePushApply(runArgs, execArgv); if (path === kIsolatedProcessName) { - ArrayPrototypePush(argv, '--test'); - ArrayPrototypePushApply(argv, ArrayPrototypeSlice(process.argv, 1)); + ArrayPrototypePush(runArgs, '--test'); + ArrayPrototypePushApply(runArgs, ArrayPrototypeSlice(process.argv, 1)); } else { - ArrayPrototypePush(argv, path); + ArrayPrototypePush(runArgs, path); } - ArrayPrototypePushApply(argv, suppliedArgs); + ArrayPrototypePushApply(runArgs, suppliedArgs); - return argv; + return runArgs; } const serializer = new DefaultSerializer(); @@ -587,6 +592,7 @@ function run(options = kEmptyObject) { execArgv = [], argv = [], cwd = process.cwd(), + rerunFailuresFilePath, } = options; if (files != null) { @@ -619,6 +625,10 @@ function run(options = kEmptyObject) { ); } + if (rerunFailuresFilePath) { + validatePath(rerunFailuresFilePath, 'options.rerunFailuresFilePath'); + } + if (shard != null) { validateObject(shard, 'options.shard'); // Avoid re-evaluating the shard object in case it's a getter @@ -701,6 +711,7 @@ function run(options = kEmptyObject) { coverage, coverageExcludeGlobs, coverageIncludeGlobs, + rerunFailuresFilePath, lineCoverage: lineCoverage, branchCoverage: branchCoverage, functionCoverage: functionCoverage, @@ -734,6 +745,7 @@ function run(options = kEmptyObject) { isolation, argv, execArgv, + rerunFailuresFilePath, }; if (isolation === 'process') { diff --git a/lib/internal/test_runner/test.js b/lib/internal/test_runner/test.js index 7c9eabb292fde4..8e189b62cad817 100644 --- a/lib/internal/test_runner/test.js +++ b/lib/internal/test_runner/test.js @@ -71,6 +71,7 @@ const { } = require('timers'); const { TIMEOUT_MAX } = require('internal/timers'); const { fileURLToPath } = require('internal/url'); +const { relative } = require('path'); const { availableParallelism } = require('os'); const { innerOk } = require('internal/assert/utils'); const { bigint: hrtime } = process.hrtime; @@ -290,6 +291,10 @@ class TestContext { return this.#test.passed; } + get attempt() { + return this.#test.attempt ?? 0; + } + diagnostic(message) { this.#test.diagnostic(message); } @@ -537,6 +542,7 @@ class Test extends AsyncResource { this.childNumber = 0; this.timeout = kDefaultTimeout; this.entryFile = entryFile; + this.testDisambiguator = new SafeMap(); } else { const nesting = parent.parent === null ? parent.nesting : parent.nesting + 1; @@ -646,6 +652,8 @@ class Test extends AsyncResource { this.endTime = null; this.passed = false; this.error = null; + this.attempt = undefined; + this.passedAttempt = undefined; this.message = typeof skip === 'string' ? skip : typeof todo === 'string' ? todo : null; this.activeSubtests = 0; @@ -690,6 +698,23 @@ class Test extends AsyncResource { this.loc.file = fileURLToPath(this.loc.file); } } + + if (this.loc != null && this.root.harness.previousRuns != null) { + let testIdentifier = `${relative(this.config.cwd, this.loc.file)}:${this.loc.line}:${this.loc.column}`; + const disambiguator = this.root.testDisambiguator.get(testIdentifier); + if (disambiguator !== undefined) { + testIdentifier += `:(${disambiguator})`; + this.root.testDisambiguator.set(testIdentifier, disambiguator + 1); + } else { + this.root.testDisambiguator.set(testIdentifier, 1); + } + this.attempt = this.root.harness.previousRuns.length; + const previousAttempt = this.root.harness.previousRuns[this.attempt - 1]?.[testIdentifier]?.passed_on_attempt; + if (previousAttempt != null) { + this.passedAttempt = previousAttempt; + this.fn = noop; + } + } } applyFilters() { @@ -1329,6 +1354,12 @@ class Test extends AsyncResource { if (!this.passed) { details.error = this.error; } + if (this.attempt !== undefined) { + details.attempt = this.attempt; + } + if (this.passedAttempt !== undefined) { + details.passed_on_attempt = this.passedAttempt; + } return { __proto__: null, details, directive }; } @@ -1502,6 +1533,8 @@ class Suite extends Test { } function getFullName(test) { + if (test === test.root) return test.name; + let fullName = test.name; for (let t = test.parent; t !== t.root; t = t.parent) { diff --git a/lib/internal/test_runner/utils.js b/lib/internal/test_runner/utils.js index 44789451d18335..dabfccf6c4f4a4 100644 --- a/lib/internal/test_runner/utils.js +++ b/lib/internal/test_runner/utils.js @@ -8,6 +8,7 @@ const { ArrayPrototypePush, ArrayPrototypeReduce, ArrayPrototypeSome, + JSONParse, MathFloor, MathMax, MathMin, @@ -28,7 +29,7 @@ const { const { AsyncResource } = require('async_hooks'); const { relative, sep, resolve } = require('path'); -const { createWriteStream } = require('fs'); +const { createWriteStream, readFileSync } = require('fs'); const { pathToFileURL } = require('internal/url'); const { getOptionValue } = require('internal/options'); const { green, yellow, red, white, shouldColorize } = require('internal/util/colors'); @@ -150,6 +151,20 @@ function shouldColorizeTestFiles(destinations) { }); } +function parsePreviousRuns(rerunFailuresFilePath) { + let data; + try { + data = readFileSync(rerunFailuresFilePath, 'utf8'); + } catch (err) { + if (err.code === 'ENOENT') { + data = '[]'; + } else { + throw err; + } + } + return JSONParse(data); +} + async function getReportersMap(reporters, destinations) { return SafePromiseAllReturnArrayLike(reporters, async (name, i) => { const destination = kBuiltinDestinations.get(destinations[i]) ?? @@ -202,6 +217,7 @@ function parseCommandLine() { const updateSnapshots = getOptionValue('--test-update-snapshots'); const watch = getOptionValue('--watch'); const timeout = getOptionValue('--test-timeout') || Infinity; + const rerunFailuresFilePath = getOptionValue('--test-rerun-failures'); const isChildProcess = process.env.NODE_TEST_CONTEXT === 'child'; const isChildProcessV8 = process.env.NODE_TEST_CONTEXT === 'child-v8'; let globalSetupPath; @@ -308,9 +324,12 @@ function parseCommandLine() { validateInteger(functionCoverage, '--test-coverage-functions', 0, 100); } + if (rerunFailuresFilePath) { + validatePath(rerunFailuresFilePath, '--test-rerun-failures'); + } + const setup = reporterScope.bind(async (rootReporter) => { const reportersMap = await getReportersMap(reporters, destinations); - for (let i = 0; i < reportersMap.length; i++) { const { reporter, destination } = reportersMap[i]; compose(rootReporter, reporter).pipe(destination); @@ -343,6 +362,7 @@ function parseCommandLine() { timeout, updateSnapshots, watch, + rerunFailuresFilePath, }; return globalTestOptions; @@ -637,4 +657,5 @@ module.exports = { shouldColorizeTestFiles, getCoverageReport, setupGlobalSetupTeardownFunctions, + parsePreviousRuns, }; diff --git a/lib/internal/util/comparisons.js b/lib/internal/util/comparisons.js index 178c6aceeff7d7..398d6b43494fe6 100644 --- a/lib/internal/util/comparisons.js +++ b/lib/internal/util/comparisons.js @@ -135,8 +135,6 @@ const kIsArray = 1; const kIsSet = 2; const kIsMap = 3; -let kKeyObject; - // Check if they have the same source and flags function areSimilarRegExps(a, b) { return a.source === b.source && @@ -395,11 +393,12 @@ function objectComparisonStart(val1, val2, mode, memos) { return false; } } else if (isCryptoKey(val1)) { - kKeyObject ??= require('internal/crypto/util').kKeyObject; + const { kKeyObject } = require('internal/crypto/util'); + const { kExtractable, kAlgorithm, kKeyUsages } = require('internal/crypto/keys'); if (!isCryptoKey(val2) || - val1.extractable !== val2.extractable || - !innerDeepEqual(val1.algorithm, val2.algorithm, mode, memos) || - !innerDeepEqual(val1.usages, val2.usages, mode, memos) || + val1[kExtractable] !== val2[kExtractable] || + !innerDeepEqual(val1[kAlgorithm], val2[kAlgorithm], mode, memos) || + !innerDeepEqual(val1[kKeyUsages], val2[kKeyUsages], mode, memos) || !innerDeepEqual(val1[kKeyObject], val2[kKeyObject], mode, memos) ) { return false; diff --git a/lib/internal/util/inspect.js b/lib/internal/util/inspect.js index 78318243e65eca..bc25cfdd3899b6 100644 --- a/lib/internal/util/inspect.js +++ b/lib/internal/util/inspect.js @@ -1,6 +1,8 @@ 'use strict'; const { + AggregateError, + AggregateErrorPrototype, Array, ArrayBuffer, ArrayBufferPrototype, @@ -74,6 +76,8 @@ const { ObjectSetPrototypeOf, Promise, PromisePrototype, + RangeError, + RangeErrorPrototype, ReflectApply, ReflectOwnKeys, RegExp, @@ -114,6 +118,8 @@ const { SymbolPrototypeValueOf, SymbolToPrimitive, SymbolToStringTag, + TypeError, + TypeErrorPrototype, TypedArray, TypedArrayPrototype, TypedArrayPrototypeGetLength, @@ -244,7 +250,6 @@ const keyStrRegExp = /^[a-zA-Z_][a-zA-Z_0-9]*$/; const numberRegExp = /^(0|[1-9][0-9]*)$/; const coreModuleRegExp = /^ {4}at (?:[^/\\(]+ \(|)node:(.+):\d+:\d+\)?$/; -const nodeModulesRegExp = /[/\\]node_modules[/\\](.+?)(?=[/\\])/g; const classRegExp = /^(\s+[^(]*?)\s*{/; // eslint-disable-next-line node-core/no-unescaped-regexp-dot @@ -636,7 +641,12 @@ const wellKnownPrototypes = new SafeMap() .set(RegExpPrototype, { name: 'RegExp', constructor: RegExp }) .set(DatePrototype, { name: 'Date', constructor: Date }) .set(DataViewPrototype, { name: 'DataView', constructor: DataView }) + .set(ErrorPrototype, { name: 'Error', constructor: Error }) + .set(AggregateErrorPrototype, { name: 'AggregateError', constructor: AggregateError }) + .set(RangeErrorPrototype, { name: 'RangeError', constructor: RangeError }) + .set(TypeErrorPrototype, { name: 'TypeError', constructor: TypeError }) + .set(BooleanPrototype, { name: 'Boolean', constructor: Boolean }) .set(NumberPrototype, { name: 'Number', constructor: Number }) .set(StringPrototype, { name: 'String', constructor: String }) @@ -1412,16 +1422,45 @@ function removeDuplicateErrorKeys(ctx, keys, err, stack) { function markNodeModules(ctx, line) { let tempLine = ''; - let nodeModule; - let pos = 0; - while ((nodeModule = nodeModulesRegExp.exec(line)) !== null) { - // '/node_modules/'.length === 14 - tempLine += StringPrototypeSlice(line, pos, nodeModule.index + 14); - tempLine += ctx.stylize(nodeModule[1], 'module'); - pos = nodeModule.index + nodeModule[0].length; - } - if (pos !== 0) { - line = tempLine + StringPrototypeSlice(line, pos); + let lastPos = 0; + let searchFrom = 0; + + while (true) { + const nodeModulePosition = StringPrototypeIndexOf(line, 'node_modules', searchFrom); + if (nodeModulePosition === -1) { + break; + } + + // Ensure it's a path segment: must have a path separator before and after + const separator = line[nodeModulePosition - 1]; + const after = line[nodeModulePosition + 12]; // 'node_modules'.length === 12 + + if ((after !== '/' && after !== '\\') || (separator !== '/' && separator !== '\\')) { + // Not a proper segment; continue searching + searchFrom = nodeModulePosition + 1; + continue; + } + + const moduleStart = nodeModulePosition + 13; // Include trailing separator + + // Append up to and including '/node_modules/' + tempLine += StringPrototypeSlice(line, lastPos, moduleStart); + + let moduleEnd = StringPrototypeIndexOf(line, separator, moduleStart); + if (line[moduleStart] === '@') { + // Namespaced modules have an extra slash: @namespace/package + moduleEnd = StringPrototypeIndexOf(line, separator, moduleEnd + 1); + } + + const nodeModule = StringPrototypeSlice(line, moduleStart, moduleEnd); + tempLine += ctx.stylize(nodeModule, 'module'); + + lastPos = moduleEnd; + searchFrom = moduleEnd; + } + + if (lastPos !== 0) { + line = tempLine + StringPrototypeSlice(line, lastPos); } return line; } diff --git a/lib/internal/webstreams/compression.js b/lib/internal/webstreams/compression.js index b33b7134096c8b..836c02f7341f70 100644 --- a/lib/internal/webstreams/compression.js +++ b/lib/internal/webstreams/compression.js @@ -28,6 +28,7 @@ const formatConverter = createEnumConverter('CompressionFormat', [ 'deflate', 'deflate-raw', 'gzip', + 'brotli', ]); /** @@ -40,7 +41,7 @@ class CompressionStream { #transform; /** - * @param {'deflate'|'deflate-raw'|'gzip'} format + * @param {'deflate'|'deflate-raw'|'gzip'|'brotli'} format */ constructor(format) { format = formatConverter(format, { @@ -57,6 +58,9 @@ class CompressionStream { case 'gzip': this.#handle = lazyZlib().createGzip(); break; + case 'brotli': + this.#handle = lazyZlib().createBrotliCompress(); + break; } this.#transform = newReadableWritablePairFromDuplex(this.#handle); } @@ -90,7 +94,7 @@ class DecompressionStream { #transform; /** - * @param {'deflate'|'deflate-raw'|'gzip'} format + * @param {'deflate'|'deflate-raw'|'gzip'|'brotli'} format */ constructor(format) { format = formatConverter(format, { @@ -111,6 +115,9 @@ class DecompressionStream { rejectGarbageAfterEnd: true, }); break; + case 'brotli': + this.#handle = lazyZlib().createBrotliDecompress(); + break; } this.#transform = newReadableWritablePairFromDuplex(this.#handle); diff --git a/node.gyp b/node.gyp index bcf01a6b1b5634..51863d37ab7e47 100644 --- a/node.gyp +++ b/node.gyp @@ -324,7 +324,9 @@ ], 'node_crypto_sources': [ 'src/crypto/crypto_aes.cc', + 'src/crypto/crypto_argon2.cc', 'src/crypto/crypto_bio.cc', + 'src/crypto/crypto_chacha20_poly1305.cc', 'src/crypto/crypto_common.cc', 'src/crypto/crypto_dsa.cc', 'src/crypto/crypto_hkdf.cc', @@ -335,6 +337,7 @@ 'src/crypto/crypto_context.cc', 'src/crypto/crypto_ec.cc', 'src/crypto/crypto_ml_dsa.cc', + 'src/crypto/crypto_kem.cc', 'src/crypto/crypto_hmac.cc', 'src/crypto/crypto_random.cc', 'src/crypto/crypto_rsa.cc', @@ -348,6 +351,7 @@ 'src/crypto/crypto_scrypt.cc', 'src/crypto/crypto_tls.cc', 'src/crypto/crypto_x509.cc', + 'src/crypto/crypto_argon2.h', 'src/crypto/crypto_bio.h', 'src/crypto/crypto_clienthello-inl.h', 'src/crypto/crypto_dh.h', @@ -973,11 +977,11 @@ 'variables': { 'mkssldef_flags': [ # Categories to export. - '-CAES,BF,BIO,DES,DH,DSA,EC,ECDH,ECDSA,ENGINE,EVP,HMAC,MD4,MD5,' - 'PSK,RC2,RC4,RSA,SHA,SHA0,SHA1,SHA256,SHA512,SOCK,STDIO,TLSEXT,' - 'UI,FP_API,TLS1_METHOD,TLS1_1_METHOD,TLS1_2_METHOD,SCRYPT,OCSP,' - 'NEXTPROTONEG,RMD160,CAST,DEPRECATEDIN_1_1_0,DEPRECATEDIN_1_2_0,' - 'DEPRECATEDIN_3_0', + '-CAES,ARGON2,BF,BIO,DES,DH,DSA,EC,ECDH,ECDSA,ENGINE,EVP,HMAC,' + 'MD4,MD5,PSK,RC2,RC4,RSA,SHA,SHA0,SHA1,SHA256,SHA512,SOCK,STDIO,' + 'TLSEXT,UI,FP_API,TLS1_METHOD,TLS1_1_METHOD,TLS1_2_METHOD,' + 'SCRYPT,OCSP,NEXTPROTONEG,RMD160,CAST,DEPRECATEDIN_1_1_0,' + 'DEPRECATEDIN_1_2_0,DEPRECATEDIN_3_0', # Defines. '-DWIN32', # Symbols to filter from the export list. diff --git a/src/api/environment.cc b/src/api/environment.cc index 798f35cb5c6148..be2745fb44759a 100644 --- a/src/api/environment.cc +++ b/src/api/environment.cc @@ -532,8 +532,19 @@ NODE_EXTERN std::unique_ptr GetInspectorParentHandle( NODE_EXTERN std::unique_ptr GetInspectorParentHandle( Environment* env, ThreadId thread_id, const char* url, const char* name) { - CHECK_NOT_NULL(env); + if (url == nullptr) url = ""; if (name == nullptr) name = ""; + std::string_view url_view(url); + std::string_view name_view(name); + return GetInspectorParentHandle(env, thread_id, url_view, name_view); +} + +NODE_EXTERN std::unique_ptr GetInspectorParentHandle( + Environment* env, + ThreadId thread_id, + std::string_view url, + std::string_view name) { + CHECK_NOT_NULL(env); CHECK_NE(thread_id.id, static_cast(-1)); if (!env->should_create_inspector()) { return nullptr; diff --git a/src/async_wrap.cc b/src/async_wrap.cc index 30490b90e77b64..d9b2c76ede38c0 100644 --- a/src/async_wrap.cc +++ b/src/async_wrap.cc @@ -107,6 +107,18 @@ void AsyncWrap::EmitPromiseResolve(Environment* env, double async_id) { env->async_hooks_promise_resolve_function()); } +void AsyncWrap::EmitTraceAsyncStart() const { + if (*TRACE_EVENT_API_GET_CATEGORY_GROUP_ENABLED( + TRACING_CATEGORY_NODE1(async_hooks))) { + tracing::AsyncWrapArgs data(env()->execution_async_id(), + get_trigger_async_id()); + TRACE_EVENT_NESTABLE_ASYNC_BEGIN1(TRACING_CATEGORY_NODE1(async_hooks), + provider_names[provider_type()], + static_cast(get_async_id()), + "data", + tracing::CastTracedValue(data)); + } +} void AsyncWrap::EmitTraceEventBefore() { switch (provider_type()) { @@ -601,27 +613,7 @@ void AsyncWrap::AsyncReset(Local resource, double execution_async_id) { } } - switch (provider_type()) { -#define V(PROVIDER) \ - case PROVIDER_ ## PROVIDER: \ - if (*TRACE_EVENT_API_GET_CATEGORY_GROUP_ENABLED( \ - TRACING_CATEGORY_NODE1(async_hooks))) { \ - auto data = tracing::TracedValue::Create(); \ - data->SetInteger("executionAsyncId", \ - static_cast(env()->execution_async_id())); \ - data->SetInteger("triggerAsyncId", \ - static_cast(get_trigger_async_id())); \ - TRACE_EVENT_NESTABLE_ASYNC_BEGIN1( \ - TRACING_CATEGORY_NODE1(async_hooks), \ - #PROVIDER, static_cast(get_async_id()), \ - "data", std::move(data)); \ - } \ - break; - NODE_ASYNC_PROVIDER_TYPES(V) -#undef V - default: - UNREACHABLE(); - } + EmitTraceAsyncStart(); context_frame_.Reset(isolate, async_context_frame::current(isolate)); diff --git a/src/async_wrap.h b/src/async_wrap.h index adb0c64bbb8eb8..0eec5f3f1a7a5b 100644 --- a/src/async_wrap.h +++ b/src/async_wrap.h @@ -86,20 +86,21 @@ namespace node { V(ZLIB) #if HAVE_OPENSSL -#define NODE_ASYNC_CRYPTO_PROVIDER_TYPES(V) \ - V(CHECKPRIMEREQUEST) \ - V(PBKDF2REQUEST) \ - V(KEYPAIRGENREQUEST) \ - V(KEYGENREQUEST) \ - V(KEYEXPORTREQUEST) \ - V(CIPHERREQUEST) \ - V(DERIVEBITSREQUEST) \ - V(HASHREQUEST) \ - V(RANDOMBYTESREQUEST) \ - V(RANDOMPRIMEREQUEST) \ - V(SCRYPTREQUEST) \ - V(SIGNREQUEST) \ - V(TLSWRAP) \ +#define NODE_ASYNC_CRYPTO_PROVIDER_TYPES(V) \ + V(CHECKPRIMEREQUEST) \ + V(PBKDF2REQUEST) \ + V(KEYPAIRGENREQUEST) \ + V(KEYGENREQUEST) \ + V(KEYEXPORTREQUEST) \ + V(ARGON2REQUEST) \ + V(CIPHERREQUEST) \ + V(DERIVEBITSREQUEST) \ + V(HASHREQUEST) \ + V(RANDOMBYTESREQUEST) \ + V(RANDOMPRIMEREQUEST) \ + V(SCRYPTREQUEST) \ + V(SIGNREQUEST) \ + V(TLSWRAP) \ V(VERIFYREQUEST) #else #define NODE_ASYNC_CRYPTO_PROVIDER_TYPES(V) @@ -178,6 +179,7 @@ class AsyncWrap : public BaseObject { void EmitDestroy(bool from_gc = false); + void EmitTraceAsyncStart() const; void EmitTraceEventBefore(); static void EmitTraceEventAfter(ProviderType type, double async_id); void EmitTraceEventDestroy(); diff --git a/src/crypto/README.md b/src/crypto/README.md index 884869b725d54f..3f4400f595d356 100644 --- a/src/crypto/README.md +++ b/src/crypto/README.md @@ -33,6 +33,7 @@ following table: | File (\*.h/\*.cc) | Description | | -------------------- | -------------------------------------------------------------------------- | | `crypto_aes` | AES Cipher support. | +| `crypto_argon2` | Argon2 key / bit generation implementation. | | `crypto_cipher` | General Encryption/Decryption utilities. | | `crypto_clienthello` | TLS/SSL client hello parser implementation. Used during SSL/TLS handshake. | | `crypto_context` | Implementation of the `SecureContext` object. | diff --git a/src/crypto/crypto_aes.cc b/src/crypto/crypto_aes.cc index 48c2fbde65dc31..b5495e59737eb6 100644 --- a/src/crypto/crypto_aes.cc +++ b/src/crypto/crypto_aes.cc @@ -61,7 +61,8 @@ WebCryptoCipherStatus AES_Cipher(Environment* env, return WebCryptoCipherStatus::FAILED; } - if (params.cipher.isGcmMode() && !ctx.setIvLength(params.iv.size())) { + if ((params.cipher.isGcmMode() || params.cipher.isOcbMode()) && + !ctx.setIvLength(params.iv.size())) { return WebCryptoCipherStatus::FAILED; } @@ -76,11 +77,20 @@ WebCryptoCipherStatus AES_Cipher(Environment* env, size_t tag_len = 0; - if (params.cipher.isGcmMode()) { + if (params.cipher.isGcmMode() || params.cipher.isOcbMode()) { switch (cipher_mode) { case kWebCryptoCipherDecrypt: { // If in decrypt mode, the auth tag must be set in the params.tag. CHECK(params.tag); + + // For OCB mode, we need to set the auth tag length before setting the + // tag + if (params.cipher.isOcbMode()) { + if (!ctx.setAeadTagLength(params.tag.size())) { + return WebCryptoCipherStatus::FAILED; + } + } + ncrypto::Buffer buffer = { .data = params.tag.data(), .len = params.tag.size(), @@ -91,12 +101,19 @@ WebCryptoCipherStatus AES_Cipher(Environment* env, break; } case kWebCryptoCipherEncrypt: { - // In decrypt mode, we grab the tag length here. We'll use it to + // In encrypt mode, we grab the tag length here. We'll use it to // ensure that that allocated buffer has enough room for both the // final block and the auth tag. Unlike our other AES-GCM implementation // in CipherBase, in WebCrypto, the auth tag is concatenated to the end // of the generated ciphertext and returned in the same ArrayBuffer. tag_len = params.length; + + // For OCB mode, we need to set the auth tag length + if (params.cipher.isOcbMode()) { + if (!ctx.setAeadTagLength(tag_len)) { + return WebCryptoCipherStatus::FAILED; + } + } break; } default: @@ -112,8 +129,8 @@ WebCryptoCipherStatus AES_Cipher(Environment* env, .data = params.additional_data.data(), .len = params.additional_data.size(), }; - if (params.cipher.isGcmMode() && params.additional_data.size() && - !ctx.update(buffer, nullptr, &out_len)) { + if ((params.cipher.isGcmMode() || params.cipher.isOcbMode()) && + params.additional_data.size() && !ctx.update(buffer, nullptr, &out_len)) { return WebCryptoCipherStatus::FAILED; } @@ -147,9 +164,9 @@ WebCryptoCipherStatus AES_Cipher(Environment* env, } total += out_len; - // If using AES_GCM, grab the generated auth tag and append + // If using AES_GCM or AES_OCB, grab the generated auth tag and append // it to the end of the ciphertext. - if (encrypt && params.cipher.isGcmMode()) { + if (encrypt && (params.cipher.isGcmMode() || params.cipher.isOcbMode())) { if (!ctx.getAeadTag(tag_len, ptr + total)) { return WebCryptoCipherStatus::FAILED; } @@ -492,7 +509,7 @@ Maybe AESCipherTraits::AdditionalConfig( if (!ValidateCounter(env, args[offset + 2], params)) { return Nothing(); } - } else if (params->cipher.isGcmMode()) { + } else if (params->cipher.isGcmMode() || params->cipher.isOcbMode()) { if (!ValidateAuthTag(env, mode, cipher_mode, args[offset + 2], params) || !ValidateAdditionalData(env, mode, args[offset + 3], params)) { return Nothing(); @@ -502,9 +519,18 @@ Maybe AESCipherTraits::AdditionalConfig( UseDefaultIV(params); } - if (params->iv.size() < static_cast(params->cipher.getIvLength())) { - THROW_ERR_CRYPTO_INVALID_IV(env); - return Nothing(); + // For OCB mode, allow variable IV lengths (1-15 bytes) + if (params->cipher.isOcbMode()) { + if (params->iv.size() == 0 || params->iv.size() > 15) { + THROW_ERR_CRYPTO_INVALID_IV(env); + return Nothing(); + } + } else { + // For other modes, check against the cipher's expected IV length + if (params->iv.size() < static_cast(params->cipher.getIvLength())) { + THROW_ERR_CRYPTO_INVALID_IV(env); + return Nothing(); + } } return JustVoid(); diff --git a/src/crypto/crypto_aes.h b/src/crypto/crypto_aes.h index 74cfdb80818287..401ef70a5eba9f 100644 --- a/src/crypto/crypto_aes.h +++ b/src/crypto/crypto_aes.h @@ -12,7 +12,7 @@ namespace node::crypto { constexpr unsigned kNoAuthTagLength = static_cast(-1); -#define VARIANTS(V) \ +#define VARIANTS_COMMON(V) \ V(CTR_128, AES_CTR_Cipher, ncrypto::Cipher::AES_128_CTR) \ V(CTR_192, AES_CTR_Cipher, ncrypto::Cipher::AES_192_CTR) \ V(CTR_256, AES_CTR_Cipher, ncrypto::Cipher::AES_256_CTR) \ @@ -26,6 +26,19 @@ constexpr unsigned kNoAuthTagLength = static_cast(-1); V(KW_192, AES_Cipher, ncrypto::Cipher::AES_192_KW) \ V(KW_256, AES_Cipher, ncrypto::Cipher::AES_256_KW) +#if OPENSSL_VERSION_MAJOR >= 3 +#define VARIANTS_OCB(V) \ + V(OCB_128, AES_Cipher, ncrypto::Cipher::AES_128_OCB) \ + V(OCB_192, AES_Cipher, ncrypto::Cipher::AES_192_OCB) \ + V(OCB_256, AES_Cipher, ncrypto::Cipher::AES_256_OCB) +#else +#define VARIANTS_OCB(V) +#endif + +#define VARIANTS(V) \ + VARIANTS_COMMON(V) \ + VARIANTS_OCB(V) + enum class AESKeyVariant { #define V(name, _, __) name, VARIANTS(V) diff --git a/src/crypto/crypto_argon2.cc b/src/crypto/crypto_argon2.cc new file mode 100644 index 00000000000000..f8792ae466d6b6 --- /dev/null +++ b/src/crypto/crypto_argon2.cc @@ -0,0 +1,172 @@ +#include "crypto/crypto_argon2.h" +#include "async_wrap-inl.h" +#include "threadpoolwork-inl.h" + +#if OPENSSL_VERSION_NUMBER >= 0x30200000L +#ifndef OPENSSL_NO_ARGON2 +#include + +namespace node::crypto { + +using v8::FunctionCallbackInfo; +using v8::JustVoid; +using v8::Local; +using v8::Maybe; +using v8::MaybeLocal; +using v8::Nothing; +using v8::Object; +using v8::Uint32; +using v8::Value; + +Argon2Config::Argon2Config(Argon2Config&& other) noexcept + : mode{other.mode}, + pass{std::move(other.pass)}, + salt{std::move(other.salt)}, + secret{std::move(other.secret)}, + ad{std::move(other.ad)}, + type{other.type}, + iter{other.iter}, + lanes{other.lanes}, + memcost{other.memcost}, + keylen{other.keylen} {} + +Argon2Config& Argon2Config::operator=(Argon2Config&& other) noexcept { + if (&other == this) return *this; + this->~Argon2Config(); + return *new (this) Argon2Config(std::move(other)); +} + +void Argon2Config::MemoryInfo(MemoryTracker* tracker) const { + if (mode == kCryptoJobAsync) { + tracker->TrackFieldWithSize("pass", pass.size()); + tracker->TrackFieldWithSize("salt", salt.size()); + tracker->TrackFieldWithSize("secret", secret.size()); + tracker->TrackFieldWithSize("ad", ad.size()); + } +} + +MaybeLocal Argon2Traits::EncodeOutput(Environment* env, + const Argon2Config& config, + ByteSource* out) { + return out->ToArrayBuffer(env); +} + +Maybe Argon2Traits::AdditionalConfig( + CryptoJobMode mode, + const FunctionCallbackInfo& args, + unsigned int offset, + Argon2Config* config) { + Environment* env = Environment::GetCurrent(args); + + config->mode = mode; + + ArrayBufferOrViewContents pass(args[offset]); + ArrayBufferOrViewContents salt(args[offset + 1]); + ArrayBufferOrViewContents secret(args[offset + 6]); + ArrayBufferOrViewContents ad(args[offset + 7]); + + if (!pass.CheckSizeInt32()) [[unlikely]] { + THROW_ERR_OUT_OF_RANGE(env, "pass is too large"); + return Nothing(); + } + + if (!salt.CheckSizeInt32()) [[unlikely]] { + THROW_ERR_OUT_OF_RANGE(env, "salt is too large"); + return Nothing(); + } + + if (!secret.CheckSizeInt32()) [[unlikely]] { + THROW_ERR_OUT_OF_RANGE(env, "secret is too large"); + return Nothing(); + } + + if (!ad.CheckSizeInt32()) [[unlikely]] { + THROW_ERR_OUT_OF_RANGE(env, "ad is too large"); + return Nothing(); + } + + const bool isAsync = mode == kCryptoJobAsync; + config->pass = isAsync ? pass.ToCopy() : pass.ToByteSource(); + config->salt = isAsync ? salt.ToCopy() : salt.ToByteSource(); + config->secret = isAsync ? secret.ToCopy() : secret.ToByteSource(); + config->ad = isAsync ? ad.ToCopy() : ad.ToByteSource(); + + CHECK(args[offset + 2]->IsUint32()); // lanes + CHECK(args[offset + 3]->IsUint32()); // keylen + CHECK(args[offset + 4]->IsUint32()); // memcost + CHECK(args[offset + 5]->IsUint32()); // iter + CHECK(args[offset + 8]->IsUint32()); // type + + config->lanes = args[offset + 2].As()->Value(); + config->keylen = args[offset + 3].As()->Value(); + config->memcost = args[offset + 4].As()->Value(); + config->iter = args[offset + 5].As()->Value(); + config->type = + static_cast(args[offset + 8].As()->Value()); + + if (!ncrypto::argon2(config->pass, + config->salt, + config->lanes, + config->keylen, + config->memcost, + config->iter, + config->version, + config->secret, + config->ad, + config->type)) { + THROW_ERR_CRYPTO_INVALID_ARGON2_PARAMS(env); + return Nothing(); + } + + return JustVoid(); +} + +bool Argon2Traits::DeriveBits(Environment* env, + const Argon2Config& config, + ByteSource* out, + CryptoJobMode mode) { + // If the config.length is zero-length, just return an empty buffer. + // It's useless, yes, but allowed via the API. + if (config.keylen == 0) { + *out = ByteSource(); + return true; + } + + // Both the pass and salt may be zero-length at this point + auto dp = ncrypto::argon2(config.pass, + config.salt, + config.lanes, + config.keylen, + config.memcost, + config.iter, + config.version, + config.secret, + config.ad, + config.type); + + if (!dp) return false; + DCHECK(!dp.isSecure()); + *out = ByteSource::Allocated(dp.release()); + return true; +} + +static constexpr auto kTypeArgon2d = ncrypto::Argon2Type::ARGON2D; +static constexpr auto kTypeArgon2i = ncrypto::Argon2Type::ARGON2I; +static constexpr auto kTypeArgon2id = ncrypto::Argon2Type::ARGON2ID; + +void Argon2::Initialize(Environment* env, Local target) { + Argon2Job::Initialize(env, target); + + NODE_DEFINE_CONSTANT(target, kTypeArgon2d); + NODE_DEFINE_CONSTANT(target, kTypeArgon2i); + NODE_DEFINE_CONSTANT(target, kTypeArgon2id); +} + +void Argon2::RegisterExternalReferences(ExternalReferenceRegistry* registry) { + Argon2Job::RegisterExternalReferences(registry); +} + +} // namespace node::crypto + +#endif +#endif diff --git a/src/crypto/crypto_argon2.h b/src/crypto/crypto_argon2.h new file mode 100644 index 00000000000000..4ba51435d88d1d --- /dev/null +++ b/src/crypto/crypto_argon2.h @@ -0,0 +1,86 @@ +#ifndef SRC_CRYPTO_CRYPTO_ARGON2_H_ +#define SRC_CRYPTO_CRYPTO_ARGON2_H_ + +#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS + +#include "crypto/crypto_util.h" + +namespace node::crypto { +#if !defined(OPENSSL_NO_ARGON2) && OPENSSL_VERSION_NUMBER >= 0x30200000L + +// Argon2 is a password-based key derivation algorithm +// defined in https://datatracker.ietf.org/doc/html/rfc9106 + +// It takes as input a password, a salt value, and a +// handful of additional parameters that control the +// cost of the operation. In this case, the higher +// the cost, the better the result. The length parameter +// defines the number of bytes that are generated. + +// The salt must be as random as possible and should be +// at least 16 bytes in length. + +struct Argon2Config final : public MemoryRetainer { + CryptoJobMode mode; + ByteSource pass; + ByteSource salt; + ByteSource secret; + ByteSource ad; + ncrypto::Argon2Type type; + uint32_t iter; + uint32_t lanes; + uint32_t memcost; + uint32_t version = 0x13; + uint32_t keylen; + + Argon2Config() = default; + + explicit Argon2Config(Argon2Config&& other) noexcept; + + Argon2Config& operator=(Argon2Config&& other) noexcept; + + void MemoryInfo(MemoryTracker* tracker) const override; + SET_MEMORY_INFO_NAME(Argon2Config) + SET_SELF_SIZE(Argon2Config) +}; + +struct Argon2Traits final { + using AdditionalParameters = Argon2Config; + static constexpr const char* JobName = "Argon2Job"; + static constexpr AsyncWrap::ProviderType Provider = + AsyncWrap::PROVIDER_ARGON2REQUEST; + + static v8::Maybe AdditionalConfig( + CryptoJobMode mode, + const v8::FunctionCallbackInfo& args, + unsigned int offset, + Argon2Config* config); + + static bool DeriveBits(Environment* env, + const Argon2Config& config, + ByteSource* out, + CryptoJobMode mode); + + static v8::MaybeLocal EncodeOutput(Environment* env, + const Argon2Config& config, + ByteSource* out); +}; + +using Argon2Job = DeriveBitsJob; + +namespace Argon2 { +void Initialize(Environment* env, v8::Local target); +void RegisterExternalReferences(ExternalReferenceRegistry* registry); +} // namespace Argon2 + +#else +// If there is no Argon2 support, Argon2Job becomes a non-op +struct Argon2Job { + static void Initialize(Environment* env, v8::Local target) {} +}; +#endif + +} // namespace node::crypto + +#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS +#endif // SRC_CRYPTO_CRYPTO_ARGON2_H_ diff --git a/src/crypto/crypto_chacha20_poly1305.cc b/src/crypto/crypto_chacha20_poly1305.cc new file mode 100644 index 00000000000000..bfe904c49ad771 --- /dev/null +++ b/src/crypto/crypto_chacha20_poly1305.cc @@ -0,0 +1,322 @@ +#include "crypto/crypto_chacha20_poly1305.h" +#include "async_wrap-inl.h" +#include "base_object-inl.h" +#include "crypto/crypto_cipher.h" +#include "crypto/crypto_keys.h" +#include "crypto/crypto_util.h" +#include "env-inl.h" +#include "memory_tracker-inl.h" +#include "threadpoolwork-inl.h" +#include "v8.h" + +#include + +namespace node { + +using ncrypto::Cipher; +using ncrypto::CipherCtxPointer; +using ncrypto::DataPointer; +using v8::FunctionCallbackInfo; +using v8::JustVoid; +using v8::Local; +using v8::Maybe; +using v8::Nothing; +using v8::Object; +using v8::Value; + +namespace crypto { +namespace { +constexpr size_t kChaCha20Poly1305KeySize = 32; +constexpr size_t kChaCha20Poly1305IvSize = 12; +constexpr size_t kChaCha20Poly1305TagSize = 16; + +bool ValidateIV(Environment* env, + CryptoJobMode mode, + Local value, + ChaCha20Poly1305CipherConfig* params) { + ArrayBufferOrViewContents iv(value); + if (!iv.CheckSizeInt32()) [[unlikely]] { + THROW_ERR_OUT_OF_RANGE(env, "iv is too large"); + return false; + } + + if (iv.size() != kChaCha20Poly1305IvSize) { + THROW_ERR_CRYPTO_INVALID_IV(env); + return false; + } + + if (mode == kCryptoJobAsync) { + params->iv = iv.ToCopy(); + } else { + params->iv = iv.ToByteSource(); + } + + return true; +} + +bool ValidateAuthTag(Environment* env, + CryptoJobMode mode, + WebCryptoCipherMode cipher_mode, + Local value, + ChaCha20Poly1305CipherConfig* params) { + switch (cipher_mode) { + case kWebCryptoCipherDecrypt: { + if (!IsAnyBufferSource(value)) { + THROW_ERR_CRYPTO_INVALID_TAG_LENGTH( + env, "Authentication tag must be a buffer"); + return false; + } + + ArrayBufferOrViewContents tag(value); + if (!tag.CheckSizeInt32()) [[unlikely]] { + THROW_ERR_OUT_OF_RANGE(env, "tag is too large"); + return false; + } + + if (tag.size() != kChaCha20Poly1305TagSize) { + THROW_ERR_CRYPTO_INVALID_TAG_LENGTH( + env, "Invalid authentication tag length"); + return false; + } + + if (mode == kCryptoJobAsync) { + params->tag = tag.ToCopy(); + } else { + params->tag = tag.ToByteSource(); + } + break; + } + case kWebCryptoCipherEncrypt: { + // For encryption, the value should be the tag length (passed from + // JavaScript) We expect it to be the tag size constant for + // ChaCha20-Poly1305 + if (!value->IsUint32()) { + THROW_ERR_CRYPTO_INVALID_TAG_LENGTH(env, "Tag length must be a number"); + return false; + } + + uint32_t tag_length = value.As()->Value(); + if (tag_length != kChaCha20Poly1305TagSize) { + THROW_ERR_CRYPTO_INVALID_TAG_LENGTH( + env, "Invalid tag length for ChaCha20-Poly1305"); + return false; + } + // Tag is generated during encryption, not provided + break; + } + default: + UNREACHABLE(); + } + + return true; +} + +bool ValidateAdditionalData(Environment* env, + CryptoJobMode mode, + Local value, + ChaCha20Poly1305CipherConfig* params) { + if (IsAnyBufferSource(value)) { + ArrayBufferOrViewContents additional_data(value); + if (!additional_data.CheckSizeInt32()) [[unlikely]] { + THROW_ERR_OUT_OF_RANGE(env, "additional data is too large"); + return false; + } + + if (mode == kCryptoJobAsync) { + params->additional_data = additional_data.ToCopy(); + } else { + params->additional_data = additional_data.ToByteSource(); + } + } + + return true; +} +} // namespace + +ChaCha20Poly1305CipherConfig::ChaCha20Poly1305CipherConfig( + ChaCha20Poly1305CipherConfig&& other) noexcept + : mode(other.mode), + cipher(other.cipher), + iv(std::move(other.iv)), + additional_data(std::move(other.additional_data)), + tag(std::move(other.tag)) {} + +ChaCha20Poly1305CipherConfig& ChaCha20Poly1305CipherConfig::operator=( + ChaCha20Poly1305CipherConfig&& other) noexcept { + if (&other == this) return *this; + this->~ChaCha20Poly1305CipherConfig(); + return *new (this) ChaCha20Poly1305CipherConfig(std::move(other)); +} + +void ChaCha20Poly1305CipherConfig::MemoryInfo(MemoryTracker* tracker) const { + // If mode is sync, then the data in each of these properties + // is not owned by the ChaCha20Poly1305CipherConfig, so we ignore it. + if (mode == kCryptoJobAsync) { + tracker->TrackFieldWithSize("iv", iv.size()); + tracker->TrackFieldWithSize("additional_data", additional_data.size()); + tracker->TrackFieldWithSize("tag", tag.size()); + } +} + +Maybe ChaCha20Poly1305CipherTraits::AdditionalConfig( + CryptoJobMode mode, + const FunctionCallbackInfo& args, + unsigned int offset, + WebCryptoCipherMode cipher_mode, + ChaCha20Poly1305CipherConfig* params) { + Environment* env = Environment::GetCurrent(args); + + params->mode = mode; + params->cipher = ncrypto::Cipher::CHACHA20_POLY1305; + + if (!params->cipher) { + THROW_ERR_CRYPTO_UNKNOWN_CIPHER(env); + return Nothing(); + } + + // IV parameter (required) + if (!ValidateIV(env, mode, args[offset], params)) { + return Nothing(); + } + + // Authentication tag parameter (only for decryption) or tag length (for + // encryption) + if (static_cast(args.Length()) > offset + 1) { + if (!ValidateAuthTag(env, mode, cipher_mode, args[offset + 1], params)) { + return Nothing(); + } + } + + // Additional authenticated data parameter (optional) + if (static_cast(args.Length()) > offset + 2) { + if (!ValidateAdditionalData(env, mode, args[offset + 2], params)) { + return Nothing(); + } + } + + return JustVoid(); +} + +WebCryptoCipherStatus ChaCha20Poly1305CipherTraits::DoCipher( + Environment* env, + const KeyObjectData& key_data, + WebCryptoCipherMode cipher_mode, + const ChaCha20Poly1305CipherConfig& params, + const ByteSource& in, + ByteSource* out) { + CHECK_EQ(key_data.GetKeyType(), kKeyTypeSecret); + + // Validate key size + if (key_data.GetSymmetricKeySize() != kChaCha20Poly1305KeySize) { + return WebCryptoCipherStatus::INVALID_KEY_TYPE; + } + + auto ctx = CipherCtxPointer::New(); + CHECK(ctx); + + const bool encrypt = cipher_mode == kWebCryptoCipherEncrypt; + + if (!ctx.init(params.cipher, encrypt)) { + return WebCryptoCipherStatus::FAILED; + } + + if (!ctx.setKeyLength(key_data.GetSymmetricKeySize()) || + !ctx.init( + Cipher(), + encrypt, + reinterpret_cast(key_data.GetSymmetricKey()), + params.iv.data())) { + return WebCryptoCipherStatus::FAILED; + } + + size_t tag_len = 0; + + switch (cipher_mode) { + case kWebCryptoCipherDecrypt: { + if (params.tag.size() != kChaCha20Poly1305TagSize) { + return WebCryptoCipherStatus::FAILED; + } + if (!ctx.setAeadTag(ncrypto::Buffer{ + .data = params.tag.data(), + .len = params.tag.size(), + })) { + return WebCryptoCipherStatus::FAILED; + } + break; + } + case kWebCryptoCipherEncrypt: { + tag_len = kChaCha20Poly1305TagSize; + break; + } + default: + UNREACHABLE(); + } + + size_t total = 0; + int buf_len = in.size() + ctx.getBlockSize() + tag_len; + int out_len; + + // Process additional authenticated data if present + ncrypto::Buffer buffer = { + .data = params.additional_data.data(), + .len = params.additional_data.size(), + }; + if (params.additional_data.size() && !ctx.update(buffer, nullptr, &out_len)) { + return WebCryptoCipherStatus::FAILED; + } + + auto buf = DataPointer::Alloc(buf_len); + auto ptr = static_cast(buf.get()); + + // Process the input data + buffer = { + .data = in.data(), + .len = in.size(), + }; + if (in.empty()) { + if (!ctx.update({}, ptr, &out_len)) { + return WebCryptoCipherStatus::FAILED; + } + } else if (!ctx.update(buffer, ptr, &out_len)) { + return WebCryptoCipherStatus::FAILED; + } + + total += out_len; + CHECK_LE(out_len, buf_len); + out_len = ctx.getBlockSize(); + if (!ctx.update({}, ptr + total, &out_len, true)) { + return WebCryptoCipherStatus::FAILED; + } + total += out_len; + + // If encrypting, grab the generated auth tag and append it to the ciphertext + if (encrypt) { + if (!ctx.getAeadTag(kChaCha20Poly1305TagSize, ptr + total)) { + return WebCryptoCipherStatus::FAILED; + } + total += kChaCha20Poly1305TagSize; + } + + if (total == 0) { + *out = ByteSource(); + return WebCryptoCipherStatus::OK; + } + + // Size down to the actual used space + buf = buf.resize(total); + *out = ByteSource::Allocated(buf.release()); + + return WebCryptoCipherStatus::OK; +} + +void ChaCha20Poly1305::Initialize(Environment* env, Local target) { + ChaCha20Poly1305CryptoJob::Initialize(env, target); +} + +void ChaCha20Poly1305::RegisterExternalReferences( + ExternalReferenceRegistry* registry) { + ChaCha20Poly1305CryptoJob::RegisterExternalReferences(registry); +} + +} // namespace crypto +} // namespace node diff --git a/src/crypto/crypto_chacha20_poly1305.h b/src/crypto/crypto_chacha20_poly1305.h new file mode 100644 index 00000000000000..5b4d5cde2c3929 --- /dev/null +++ b/src/crypto/crypto_chacha20_poly1305.h @@ -0,0 +1,64 @@ +#ifndef SRC_CRYPTO_CRYPTO_CHACHA20_POLY1305_H_ +#define SRC_CRYPTO_CRYPTO_CHACHA20_POLY1305_H_ + +#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS + +#include "crypto/crypto_cipher.h" +#include "crypto/crypto_keys.h" +#include "crypto/crypto_util.h" +#include "env.h" +#include "v8.h" + +namespace node::crypto { +constexpr unsigned kChaCha20Poly1305AuthTagLength = 16; + +struct ChaCha20Poly1305CipherConfig final : public MemoryRetainer { + CryptoJobMode mode; + ncrypto::Cipher cipher; + ByteSource iv; + ByteSource additional_data; + ByteSource tag; + + ChaCha20Poly1305CipherConfig() = default; + + ChaCha20Poly1305CipherConfig(ChaCha20Poly1305CipherConfig&& other) noexcept; + + ChaCha20Poly1305CipherConfig& operator=( + ChaCha20Poly1305CipherConfig&& other) noexcept; + + void MemoryInfo(MemoryTracker* tracker) const override; + SET_MEMORY_INFO_NAME(ChaCha20Poly1305CipherConfig) + SET_SELF_SIZE(ChaCha20Poly1305CipherConfig) +}; + +struct ChaCha20Poly1305CipherTraits final { + static constexpr const char* JobName = "ChaCha20Poly1305CipherJob"; + + using AdditionalParameters = ChaCha20Poly1305CipherConfig; + + static v8::Maybe AdditionalConfig( + CryptoJobMode mode, + const v8::FunctionCallbackInfo& args, + unsigned int offset, + WebCryptoCipherMode cipher_mode, + ChaCha20Poly1305CipherConfig* config); + + static WebCryptoCipherStatus DoCipher( + Environment* env, + const KeyObjectData& key_data, + WebCryptoCipherMode cipher_mode, + const ChaCha20Poly1305CipherConfig& params, + const ByteSource& in, + ByteSource* out); +}; + +using ChaCha20Poly1305CryptoJob = CipherJob; + +namespace ChaCha20Poly1305 { +void Initialize(Environment* env, v8::Local target); +void RegisterExternalReferences(ExternalReferenceRegistry* registry); +} // namespace ChaCha20Poly1305 +} // namespace node::crypto + +#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS +#endif // SRC_CRYPTO_CRYPTO_CHACHA20_POLY1305_H_ diff --git a/src/crypto/crypto_context.cc b/src/crypto/crypto_context.cc index f59603891f289e..d5b175ac7172da 100644 --- a/src/crypto/crypto_context.cc +++ b/src/crypto/crypto_context.cc @@ -814,6 +814,23 @@ static std::vector& GetSystemStoreCACertificates() { return system_store_certs; } +static void LoadSystemCACertificates(void* data) { + GetSystemStoreCACertificates(); +} + +static uv_thread_t system_ca_thread; +static bool system_ca_thread_started = false; +int LoadSystemCACertificatesOffThread() { + // This is only run once during the initialization of the process, so + // it is safe to use a static thread here. + int r = + uv_thread_create(&system_ca_thread, LoadSystemCACertificates, nullptr); + if (r == 0) { + system_ca_thread_started = true; + } + return r; +} + static std::vector InitializeExtraCACertificates() { std::vector extra_certs; unsigned long err = LoadCertsFromFile( // NOLINT(runtime/int) @@ -925,6 +942,10 @@ void CleanupCachedRootCertificates() { X509_free(cert); } } + if (system_ca_thread_started) { + uv_thread_join(&system_ca_thread); + system_ca_thread_started = false; + } } void GetBundledRootCertificates(const FunctionCallbackInfo& args) { diff --git a/src/crypto/crypto_kem.cc b/src/crypto/crypto_kem.cc new file mode 100644 index 00000000000000..d6227bb66c6dc1 --- /dev/null +++ b/src/crypto/crypto_kem.cc @@ -0,0 +1,262 @@ +#include "crypto/crypto_kem.h" + +#if OPENSSL_VERSION_MAJOR >= 3 + +#include "async_wrap-inl.h" +#include "base_object-inl.h" +#include "crypto/crypto_keys.h" +#include "crypto/crypto_util.h" +#include "env-inl.h" +#include "memory_tracker-inl.h" +#include "node_buffer.h" +#include "threadpoolwork-inl.h" +#include "v8.h" + +namespace node { + +using ncrypto::EVPKeyPointer; +using v8::Array; +using v8::FunctionCallbackInfo; +using v8::Local; +using v8::Maybe; +using v8::MaybeLocal; +using v8::Nothing; +using v8::Object; +using v8::Value; + +namespace crypto { + +KEMConfiguration::KEMConfiguration(KEMConfiguration&& other) noexcept + : job_mode(other.job_mode), + mode(other.mode), + key(std::move(other.key)), + ciphertext(std::move(other.ciphertext)) {} + +KEMConfiguration& KEMConfiguration::operator=( + KEMConfiguration&& other) noexcept { + if (&other == this) return *this; + this->~KEMConfiguration(); + return *new (this) KEMConfiguration(std::move(other)); +} + +void KEMConfiguration::MemoryInfo(MemoryTracker* tracker) const { + tracker->TrackField("key", key); + if (job_mode == kCryptoJobAsync) { + tracker->TrackFieldWithSize("ciphertext", ciphertext.size()); + } +} + +namespace { + +bool DoKEMEncapsulate(Environment* env, + const EVPKeyPointer& public_key, + ByteSource* out, + CryptoJobMode mode) { + auto result = ncrypto::KEM::Encapsulate(public_key); + if (!result) { + if (mode == kCryptoJobSync) { + THROW_ERR_CRYPTO_OPERATION_FAILED(env, "Failed to perform encapsulation"); + } + return false; + } + + // Pack the result: [ciphertext_len][shared_key_len][ciphertext][shared_key] + size_t ciphertext_len = result->ciphertext.size(); + size_t shared_key_len = result->shared_key.size(); + size_t total_len = + sizeof(uint32_t) + sizeof(uint32_t) + ciphertext_len + shared_key_len; + + auto data = ncrypto::DataPointer::Alloc(total_len); + if (!data) { + if (mode == kCryptoJobSync) { + THROW_ERR_CRYPTO_OPERATION_FAILED(env, + "Failed to allocate output buffer"); + } + return false; + } + + unsigned char* ptr = static_cast(data.get()); + + // Write size headers + *reinterpret_cast(ptr) = static_cast(ciphertext_len); + *reinterpret_cast(ptr + sizeof(uint32_t)) = + static_cast(shared_key_len); + + // Write ciphertext and shared key data + unsigned char* ciphertext_ptr = ptr + 2 * sizeof(uint32_t); + unsigned char* shared_key_ptr = ciphertext_ptr + ciphertext_len; + + std::memcpy(ciphertext_ptr, result->ciphertext.get(), ciphertext_len); + std::memcpy(shared_key_ptr, result->shared_key.get(), shared_key_len); + + *out = ByteSource::Allocated(data.release()); + return true; +} + +bool DoKEMDecapsulate(Environment* env, + const EVPKeyPointer& private_key, + const ByteSource& ciphertext, + ByteSource* out, + CryptoJobMode mode) { + ncrypto::Buffer ciphertext_buf{ciphertext.data(), + ciphertext.size()}; + auto shared_key = ncrypto::KEM::Decapsulate(private_key, ciphertext_buf); + if (!shared_key) { + if (mode == kCryptoJobSync) { + THROW_ERR_CRYPTO_OPERATION_FAILED(env, "Failed to perform decapsulation"); + } + return false; + } + + *out = ByteSource::Allocated(shared_key.release()); + return true; +} + +} // anonymous namespace + +// KEMEncapsulateTraits implementation +Maybe KEMEncapsulateTraits::AdditionalConfig( + CryptoJobMode mode, + const FunctionCallbackInfo& args, + unsigned int offset, + KEMConfiguration* params) { + params->job_mode = mode; + params->mode = KEMMode::Encapsulate; + + unsigned int key_offset = offset; + auto public_key_data = + KeyObjectData::GetPublicOrPrivateKeyFromJs(args, &key_offset); + if (!public_key_data) { + return Nothing(); + } + params->key = std::move(public_key_data); + + return v8::JustVoid(); +} + +bool KEMEncapsulateTraits::DeriveBits(Environment* env, + const KEMConfiguration& params, + ByteSource* out, + CryptoJobMode mode) { + Mutex::ScopedLock lock(params.key.mutex()); + const auto& public_key = params.key.GetAsymmetricKey(); + + return DoKEMEncapsulate(env, public_key, out, mode); +} + +MaybeLocal KEMEncapsulateTraits::EncodeOutput( + Environment* env, const KEMConfiguration& params, ByteSource* out) { + // The output contains: + // [ciphertext_len][shared_key_len][ciphertext][shared_key] + const unsigned char* data = out->data(); + + uint32_t ciphertext_len = *reinterpret_cast(data); + uint32_t shared_key_len = + *reinterpret_cast(data + sizeof(uint32_t)); + + const unsigned char* ciphertext_ptr = data + 2 * sizeof(uint32_t); + const unsigned char* shared_key_ptr = ciphertext_ptr + ciphertext_len; + + MaybeLocal ciphertext_buf = + node::Buffer::Copy(env->isolate(), + reinterpret_cast(ciphertext_ptr), + ciphertext_len); + + MaybeLocal shared_key_buf = + node::Buffer::Copy(env->isolate(), + reinterpret_cast(shared_key_ptr), + shared_key_len); + + Local ciphertext_obj; + Local shared_key_obj; + if (!ciphertext_buf.ToLocal(&ciphertext_obj) || + !shared_key_buf.ToLocal(&shared_key_obj)) { + return MaybeLocal(); + } + + // Return an array [sharedKey, ciphertext]. + Local result = Array::New(env->isolate(), 2); + if (result->Set(env->context(), 0, shared_key_obj).IsNothing() || + result->Set(env->context(), 1, ciphertext_obj).IsNothing()) { + return MaybeLocal(); + } + + return result; +} + +// KEMDecapsulateTraits implementation +Maybe KEMDecapsulateTraits::AdditionalConfig( + CryptoJobMode mode, + const FunctionCallbackInfo& args, + unsigned int offset, + KEMConfiguration* params) { + Environment* env = Environment::GetCurrent(args); + + params->job_mode = mode; + params->mode = KEMMode::Decapsulate; + + unsigned int key_offset = offset; + auto private_key_data = + KeyObjectData::GetPrivateKeyFromJs(args, &key_offset, true); + if (!private_key_data) { + return Nothing(); + } + params->key = std::move(private_key_data); + + ArrayBufferOrViewContents ciphertext(args[key_offset]); + if (!ciphertext.CheckSizeInt32()) { + THROW_ERR_OUT_OF_RANGE(env, "ciphertext is too big"); + return Nothing(); + } + + params->ciphertext = + mode == kCryptoJobAsync ? ciphertext.ToCopy() : ciphertext.ToByteSource(); + + return v8::JustVoid(); +} + +bool KEMDecapsulateTraits::DeriveBits(Environment* env, + const KEMConfiguration& params, + ByteSource* out, + CryptoJobMode mode) { + Mutex::ScopedLock lock(params.key.mutex()); + const auto& private_key = params.key.GetAsymmetricKey(); + + return DoKEMDecapsulate(env, private_key, params.ciphertext, out, mode); +} + +MaybeLocal KEMDecapsulateTraits::EncodeOutput( + Environment* env, const KEMConfiguration& params, ByteSource* out) { + return out->ToBuffer(env); +} + +void InitializeKEM(Environment* env, Local target) { + KEMEncapsulateJob::Initialize(env, target); + KEMDecapsulateJob::Initialize(env, target); + + constexpr int kKEMEncapsulate = static_cast(KEMMode::Encapsulate); + constexpr int kKEMDecapsulate = static_cast(KEMMode::Decapsulate); + + NODE_DEFINE_CONSTANT(target, kKEMEncapsulate); + NODE_DEFINE_CONSTANT(target, kKEMDecapsulate); +} + +void RegisterKEMExternalReferences(ExternalReferenceRegistry* registry) { + KEMEncapsulateJob::RegisterExternalReferences(registry); + KEMDecapsulateJob::RegisterExternalReferences(registry); +} + +namespace KEM { +void Initialize(Environment* env, Local target) { + InitializeKEM(env, target); +} + +void RegisterExternalReferences(ExternalReferenceRegistry* registry) { + RegisterKEMExternalReferences(registry); +} +} // namespace KEM + +} // namespace crypto +} // namespace node + +#endif diff --git a/src/crypto/crypto_kem.h b/src/crypto/crypto_kem.h new file mode 100644 index 00000000000000..02cef53c27c8c8 --- /dev/null +++ b/src/crypto/crypto_kem.h @@ -0,0 +1,113 @@ +#ifndef SRC_CRYPTO_CRYPTO_KEM_H_ +#define SRC_CRYPTO_CRYPTO_KEM_H_ + +#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS + +#include "base_object.h" +#include "crypto/crypto_keys.h" +#include "crypto/crypto_util.h" +#include "env.h" +#include "memory_tracker.h" +#include "node_external_reference.h" + +#if OPENSSL_VERSION_MAJOR >= 3 + +namespace node { +namespace crypto { + +enum class KEMMode { Encapsulate, Decapsulate }; + +struct KEMConfiguration final : public MemoryRetainer { + CryptoJobMode job_mode; + KEMMode mode; + KeyObjectData key; + ByteSource ciphertext; + + KEMConfiguration() = default; + explicit KEMConfiguration(KEMConfiguration&& other) noexcept; + KEMConfiguration& operator=(KEMConfiguration&& other) noexcept; + + void MemoryInfo(MemoryTracker* tracker) const override; + SET_MEMORY_INFO_NAME(KEMConfiguration) + SET_SELF_SIZE(KEMConfiguration) +}; + +struct KEMEncapsulateTraits final { + using AdditionalParameters = KEMConfiguration; + static constexpr const char* JobName = "KEMEncapsulateJob"; + + static constexpr AsyncWrap::ProviderType Provider = + AsyncWrap::PROVIDER_DERIVEBITSREQUEST; + + static v8::Maybe AdditionalConfig( + CryptoJobMode mode, + const v8::FunctionCallbackInfo& args, + unsigned int offset, + KEMConfiguration* params); + + static bool DeriveBits(Environment* env, + const KEMConfiguration& params, + ByteSource* out, + CryptoJobMode mode); + + static v8::MaybeLocal EncodeOutput(Environment* env, + const KEMConfiguration& params, + ByteSource* out); +}; + +struct KEMDecapsulateTraits final { + using AdditionalParameters = KEMConfiguration; + static constexpr const char* JobName = "KEMDecapsulateJob"; + + static constexpr AsyncWrap::ProviderType Provider = + AsyncWrap::PROVIDER_DERIVEBITSREQUEST; + + static v8::Maybe AdditionalConfig( + CryptoJobMode mode, + const v8::FunctionCallbackInfo& args, + unsigned int offset, + KEMConfiguration* params); + + static bool DeriveBits(Environment* env, + const KEMConfiguration& params, + ByteSource* out, + CryptoJobMode mode); + + static v8::MaybeLocal EncodeOutput(Environment* env, + const KEMConfiguration& params, + ByteSource* out); +}; + +using KEMEncapsulateJob = DeriveBitsJob; +using KEMDecapsulateJob = DeriveBitsJob; + +void InitializeKEM(Environment* env, v8::Local target); +void RegisterKEMExternalReferences(ExternalReferenceRegistry* registry); + +namespace KEM { +void Initialize(Environment* env, v8::Local target); +void RegisterExternalReferences(ExternalReferenceRegistry* registry); +} // namespace KEM + +} // namespace crypto +} // namespace node + +#else + +// Provide stub implementations when OpenSSL < 3.0 +namespace node { +namespace crypto { +namespace KEM { +inline void Initialize(Environment* env, v8::Local target) { + // No-op when OpenSSL < 3.0 +} +inline void RegisterExternalReferences(ExternalReferenceRegistry* registry) { + // No-op when OpenSSL < 3.0 +} +} // namespace KEM +} // namespace crypto +} // namespace node + +#endif +#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS +#endif // SRC_CRYPTO_CRYPTO_KEM_H_ diff --git a/src/crypto/crypto_keys.cc b/src/crypto/crypto_keys.cc index 167018d7d208c6..e23b1058bf465a 100644 --- a/src/crypto/crypto_keys.cc +++ b/src/crypto/crypto_keys.cc @@ -177,7 +177,7 @@ bool ExportJWKAsymmetricKey(Environment* env, // Fall through case EVP_PKEY_X448: return ExportJWKEdKey(env, key, target); -#if OPENSSL_VERSION_MAJOR >= 3 && OPENSSL_VERSION_MINOR >= 5 +#if OPENSSL_WITH_PQC case EVP_PKEY_ML_DSA_44: // Fall through case EVP_PKEY_ML_DSA_65: @@ -280,13 +280,19 @@ int GetNidFromName(const char* name) { nid = EVP_PKEY_X25519; } else if (strcmp(name, "X448") == 0) { nid = EVP_PKEY_X448; -#if OPENSSL_VERSION_MAJOR >= 3 && OPENSSL_VERSION_MINOR >= 5 +#if OPENSSL_WITH_PQC } else if (strcmp(name, "ML-DSA-44") == 0) { nid = EVP_PKEY_ML_DSA_44; } else if (strcmp(name, "ML-DSA-65") == 0) { nid = EVP_PKEY_ML_DSA_65; } else if (strcmp(name, "ML-DSA-87") == 0) { nid = EVP_PKEY_ML_DSA_87; + } else if (strcmp(name, "ML-KEM-512") == 0) { + nid = EVP_PKEY_ML_KEM_512; + } else if (strcmp(name, "ML-KEM-768") == 0) { + nid = EVP_PKEY_ML_KEM_768; + } else if (strcmp(name, "ML-KEM-1024") == 0) { + nid = EVP_PKEY_ML_KEM_1024; #endif } else { nid = NID_undef; @@ -620,8 +626,10 @@ Local KeyObjectHandle::Initialize(Environment* env) { SetProtoMethod(isolate, templ, "exportJwk", ExportJWK); SetProtoMethod(isolate, templ, "initECRaw", InitECRaw); SetProtoMethod(isolate, templ, "initEDRaw", InitEDRaw); -#if OPENSSL_VERSION_MAJOR >= 3 && OPENSSL_VERSION_MINOR >= 5 - SetProtoMethod(isolate, templ, "initMlDsaRaw", InitMlDsaRaw); +#if OPENSSL_WITH_PQC + SetProtoMethod(isolate, templ, "initPqcRaw", InitPqcRaw); + SetProtoMethodNoSideEffect(isolate, templ, "rawPublicKey", RawPublicKey); + SetProtoMethodNoSideEffect(isolate, templ, "rawSeed", RawSeed); #endif SetProtoMethod(isolate, templ, "initJwk", InitJWK); SetProtoMethod(isolate, templ, "keyDetail", GetKeyDetail); @@ -643,8 +651,10 @@ void KeyObjectHandle::RegisterExternalReferences( registry->Register(ExportJWK); registry->Register(InitECRaw); registry->Register(InitEDRaw); -#if OPENSSL_VERSION_MAJOR >= 3 && OPENSSL_VERSION_MINOR >= 5 - registry->Register(InitMlDsaRaw); +#if OPENSSL_WITH_PQC + registry->Register(InitPqcRaw); + registry->Register(RawPublicKey); + registry->Register(RawSeed); #endif registry->Register(InitJWK); registry->Register(GetKeyDetail); @@ -838,8 +848,8 @@ void KeyObjectHandle::InitEDRaw(const FunctionCallbackInfo& args) { args.GetReturnValue().Set(true); } -#if OPENSSL_VERSION_MAJOR >= 3 && OPENSSL_VERSION_MINOR >= 5 -void KeyObjectHandle::InitMlDsaRaw(const FunctionCallbackInfo& args) { +#if OPENSSL_WITH_PQC +void KeyObjectHandle::InitPqcRaw(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); KeyObjectHandle* key; ASSIGN_OR_RETURN_UNWRAP(&key, args.This()); @@ -862,7 +872,10 @@ void KeyObjectHandle::InitMlDsaRaw(const FunctionCallbackInfo& args) { switch (id) { case EVP_PKEY_ML_DSA_44: case EVP_PKEY_ML_DSA_65: - case EVP_PKEY_ML_DSA_87: { + case EVP_PKEY_ML_DSA_87: + case EVP_PKEY_ML_KEM_512: + case EVP_PKEY_ML_KEM_768: + case EVP_PKEY_ML_KEM_1024: { auto pkey = fn(id, ncrypto::Buffer{ .data = key_data.data(), @@ -971,13 +984,19 @@ Local KeyObjectHandle::GetAsymmetricKeyType() const { return env()->crypto_x25519_string(); case EVP_PKEY_X448: return env()->crypto_x448_string(); -#if OPENSSL_VERSION_MAJOR >= 3 && OPENSSL_VERSION_MINOR >= 5 +#if OPENSSL_WITH_PQC case EVP_PKEY_ML_DSA_44: return env()->crypto_ml_dsa_44_string(); case EVP_PKEY_ML_DSA_65: return env()->crypto_ml_dsa_65_string(); case EVP_PKEY_ML_DSA_87: return env()->crypto_ml_dsa_87_string(); + case EVP_PKEY_ML_KEM_512: + return env()->crypto_ml_kem_512_string(); + case EVP_PKEY_ML_KEM_768: + return env()->crypto_ml_kem_768_string(); + case EVP_PKEY_ML_KEM_1024: + return env()->crypto_ml_kem_1024_string(); #endif default: return Undefined(env()->isolate()); @@ -1077,6 +1096,50 @@ MaybeLocal KeyObjectHandle::ExportPrivateKey( return WritePrivateKey(env(), data_.GetAsymmetricKey(), config); } +#if OPENSSL_WITH_PQC +void KeyObjectHandle::RawPublicKey( + const v8::FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + KeyObjectHandle* key; + ASSIGN_OR_RETURN_UNWRAP(&key, args.This()); + + const KeyObjectData& data = key->Data(); + CHECK_NE(data.GetKeyType(), kKeyTypeSecret); + + Mutex::ScopedLock lock(data.mutex()); + auto raw_data = data.GetAsymmetricKey().rawPublicKey(); + if (!raw_data) { + return THROW_ERR_CRYPTO_OPERATION_FAILED(env, + "Failed to get raw public key"); + } + + args.GetReturnValue().Set( + Buffer::Copy( + env, reinterpret_cast(raw_data.get()), raw_data.size()) + .FromMaybe(Local())); +} + +void KeyObjectHandle::RawSeed(const v8::FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + KeyObjectHandle* key; + ASSIGN_OR_RETURN_UNWRAP(&key, args.This()); + + const KeyObjectData& data = key->Data(); + CHECK_EQ(data.GetKeyType(), kKeyTypePrivate); + + Mutex::ScopedLock lock(data.mutex()); + auto raw_data = data.GetAsymmetricKey().rawSeed(); + if (!raw_data) { + return THROW_ERR_CRYPTO_OPERATION_FAILED(env, "Failed to get raw seed"); + } + + args.GetReturnValue().Set( + Buffer::Copy( + env, reinterpret_cast(raw_data.get()), raw_data.size()) + .FromMaybe(Local())); +} +#endif + void KeyObjectHandle::ExportJWK( const v8::FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); @@ -1254,10 +1317,13 @@ void Initialize(Environment* env, Local target) { NODE_DEFINE_CONSTANT(target, kWebCryptoKeyFormatJWK); NODE_DEFINE_CONSTANT(target, EVP_PKEY_ED25519); NODE_DEFINE_CONSTANT(target, EVP_PKEY_ED448); -#if OPENSSL_VERSION_MAJOR >= 3 && OPENSSL_VERSION_MINOR >= 5 +#if OPENSSL_WITH_PQC NODE_DEFINE_CONSTANT(target, EVP_PKEY_ML_DSA_44); NODE_DEFINE_CONSTANT(target, EVP_PKEY_ML_DSA_65); NODE_DEFINE_CONSTANT(target, EVP_PKEY_ML_DSA_87); + NODE_DEFINE_CONSTANT(target, EVP_PKEY_ML_KEM_512); + NODE_DEFINE_CONSTANT(target, EVP_PKEY_ML_KEM_768); + NODE_DEFINE_CONSTANT(target, EVP_PKEY_ML_KEM_1024); #endif NODE_DEFINE_CONSTANT(target, EVP_PKEY_X25519); NODE_DEFINE_CONSTANT(target, EVP_PKEY_X448); diff --git a/src/crypto/crypto_keys.h b/src/crypto/crypto_keys.h index 2a2a38ebe0e3cb..90c252eba28bea 100644 --- a/src/crypto/crypto_keys.h +++ b/src/crypto/crypto_keys.h @@ -127,7 +127,7 @@ class KeyObjectData final : public MemoryRetainer { KeyObjectData(KeyType type, std::shared_ptr mutex, std::shared_ptr data) - : key_type_(type), mutex_(mutex), data_(data) {} + : key_type_(type), mutex_(std::move(mutex)), data_(std::move(data)) {} }; class KeyObjectHandle : public BaseObject { @@ -152,9 +152,6 @@ class KeyObjectHandle : public BaseObject { static void Init(const v8::FunctionCallbackInfo& args); static void InitECRaw(const v8::FunctionCallbackInfo& args); static void InitEDRaw(const v8::FunctionCallbackInfo& args); -#if OPENSSL_VERSION_MAJOR >= 3 && OPENSSL_VERSION_MINOR >= 5 - static void InitMlDsaRaw(const v8::FunctionCallbackInfo& args); -#endif static void InitJWK(const v8::FunctionCallbackInfo& args); static void GetKeyDetail(const v8::FunctionCallbackInfo& args); static void Equals(const v8::FunctionCallbackInfo& args); @@ -173,6 +170,12 @@ class KeyObjectHandle : public BaseObject { static void Export(const v8::FunctionCallbackInfo& args); +#if OPENSSL_WITH_PQC + static void InitPqcRaw(const v8::FunctionCallbackInfo& args); + static void RawPublicKey(const v8::FunctionCallbackInfo& args); + static void RawSeed(const v8::FunctionCallbackInfo& args); +#endif + v8::MaybeLocal ExportSecretKey() const; v8::MaybeLocal ExportPublicKey( const ncrypto::EVPKeyPointer::PublicKeyEncodingConfig& config) const; diff --git a/src/crypto/crypto_ml_dsa.cc b/src/crypto/crypto_ml_dsa.cc index 119597c634cc11..65f7053cc1fa1d 100644 --- a/src/crypto/crypto_ml_dsa.cc +++ b/src/crypto/crypto_ml_dsa.cc @@ -14,7 +14,7 @@ using v8::Value; namespace crypto { -#if OPENSSL_VERSION_MAJOR >= 3 && OPENSSL_VERSION_MINOR >= 5 +#if OPENSSL_WITH_PQC constexpr const char* GetMlDsaAlgorithmName(int id) { switch (id) { case EVP_PKEY_ML_DSA_44: diff --git a/src/crypto/crypto_ml_dsa.h b/src/crypto/crypto_ml_dsa.h index 6ecdedaee12629..e4739fcdd7fda7 100644 --- a/src/crypto/crypto_ml_dsa.h +++ b/src/crypto/crypto_ml_dsa.h @@ -9,7 +9,7 @@ namespace node { namespace crypto { -#if OPENSSL_VERSION_MAJOR >= 3 && OPENSSL_VERSION_MINOR >= 5 +#if OPENSSL_WITH_PQC bool ExportJwkMlDsaKey(Environment* env, const KeyObjectData& key, v8::Local target); diff --git a/src/crypto/crypto_util.h b/src/crypto/crypto_util.h index d2620b40c8bc4b..7a89764581ddd2 100644 --- a/src/crypto/crypto_util.h +++ b/src/crypto/crypto_util.h @@ -45,6 +45,7 @@ void InitCryptoOnce(); void InitCrypto(v8::Local target); extern void UseExtraCaCerts(std::string_view file); +extern int LoadSystemCACertificatesOffThread(); void CleanupCachedRootCertificates(); int PasswordCallback(char* buf, int size, int rwflag, void* u); diff --git a/src/env.cc b/src/env.cc index b7944b10b57178..58043140882a58 100644 --- a/src/env.cc +++ b/src/env.cc @@ -894,18 +894,12 @@ Environment::Environment(IsolateData* isolate_data, if (*TRACE_EVENT_API_GET_CATEGORY_GROUP_ENABLED( TRACING_CATEGORY_NODE1(environment)) != 0) { - auto traced_value = tracing::TracedValue::Create(); - traced_value->BeginArray("args"); - for (const std::string& arg : args) traced_value->AppendString(arg); - traced_value->EndArray(); - traced_value->BeginArray("exec_args"); - for (const std::string& arg : exec_args) traced_value->AppendString(arg); - traced_value->EndArray(); + tracing::EnvironmentArgs traced_value(args, exec_args); TRACE_EVENT_NESTABLE_ASYNC_BEGIN1(TRACING_CATEGORY_NODE1(environment), "Environment", this, "args", - std::move(traced_value)); + tracing::CastTracedValue(traced_value)); } if (options_->permission) { diff --git a/src/env_properties.h b/src/env_properties.h index edc013aec2d07f..3687027c8ae939 100644 --- a/src/env_properties.h +++ b/src/env_properties.h @@ -118,6 +118,9 @@ V(crypto_ml_dsa_44_string, "ml-dsa-44") \ V(crypto_ml_dsa_65_string, "ml-dsa-65") \ V(crypto_ml_dsa_87_string, "ml-dsa-87") \ + V(crypto_ml_kem_512_string, "ml-kem-512") \ + V(crypto_ml_kem_768_string, "ml-kem-768") \ + V(crypto_ml_kem_1024_string, "ml-kem-1024") \ V(crypto_x25519_string, "x25519") \ V(crypto_x448_string, "x448") \ V(crypto_rsa_string, "rsa") \ diff --git a/src/inspector/network_agent.cc b/src/inspector/network_agent.cc index 3b5d9615021101..d136b72e598d07 100644 --- a/src/inspector/network_agent.cc +++ b/src/inspector/network_agent.cc @@ -208,6 +208,35 @@ std::unique_ptr createResponseFromObject( .build(); } +std::unique_ptr createWebSocketResponse( + v8::Local context, Local response) { + HandleScope handle_scope(context->GetIsolate()); + int status; + if (!ObjectGetInt(context, response, "status").To(&status)) { + return {}; + } + protocol::String statusText; + if (!ObjectGetProtocolString(context, response, "statusText") + .To(&statusText)) { + return {}; + } + Local headers_obj; + if (!ObjectGetObject(context, response, "headers").ToLocal(&headers_obj)) { + return {}; + } + std::unique_ptr headers = + createHeadersFromObject(context, headers_obj); + if (!headers) { + return {}; + } + + return protocol::Network::WebSocketResponse::create() + .setStatus(status) + .setStatusText(statusText) + .setHeaders(std::move(headers)) + .build(); +} + NetworkAgent::NetworkAgent( NetworkInspector* inspector, v8_inspector::V8Inspector* v8_inspector, @@ -223,6 +252,64 @@ NetworkAgent::NetworkAgent( event_notifier_map_["loadingFinished"] = &NetworkAgent::loadingFinished; event_notifier_map_["dataSent"] = &NetworkAgent::dataSent; event_notifier_map_["dataReceived"] = &NetworkAgent::dataReceived; + event_notifier_map_["webSocketCreated"] = &NetworkAgent::webSocketCreated; + event_notifier_map_["webSocketClosed"] = &NetworkAgent::webSocketClosed; + event_notifier_map_["webSocketHandshakeResponseReceived"] = + &NetworkAgent::webSocketHandshakeResponseReceived; +} + +void NetworkAgent::webSocketCreated(v8::Local context, + v8::Local params) { + protocol::String request_id; + if (!ObjectGetProtocolString(context, params, "requestId").To(&request_id)) { + return; + } + protocol::String url; + if (!ObjectGetProtocolString(context, params, "url").To(&url)) { + return; + } + std::unique_ptr initiator = + protocol::Network::Initiator::create() + .setType(protocol::Network::Initiator::TypeEnum::Script) + .setStack( + v8_inspector_->captureStackTrace(true)->buildInspectorObject(0)) + .build(); + frontend_->webSocketCreated(request_id, url, std::move(initiator)); +} + +void NetworkAgent::webSocketClosed(v8::Local context, + v8::Local params) { + protocol::String request_id; + if (!ObjectGetProtocolString(context, params, "requestId").To(&request_id)) { + return; + } + double timestamp; + if (!ObjectGetDouble(context, params, "timestamp").To(×tamp)) { + return; + } + frontend_->webSocketClosed(request_id, timestamp); +} + +void NetworkAgent::webSocketHandshakeResponseReceived( + v8::Local context, v8::Local params) { + protocol::String request_id; + if (!ObjectGetProtocolString(context, params, "requestId").To(&request_id)) { + return; + } + double timestamp; + if (!ObjectGetDouble(context, params, "timestamp").To(×tamp)) { + return; + } + Local response_obj; + if (!ObjectGetObject(context, params, "response").ToLocal(&response_obj)) { + return; + } + auto response = createWebSocketResponse(context, response_obj); + if (!response) { + return; + } + frontend_->webSocketHandshakeResponseReceived( + request_id, timestamp, std::move(response)); } void NetworkAgent::emitNotification(v8::Local context, diff --git a/src/inspector/network_agent.h b/src/inspector/network_agent.h index f814c7f5cf6662..487c7d02680d3d 100644 --- a/src/inspector/network_agent.h +++ b/src/inspector/network_agent.h @@ -93,6 +93,13 @@ class NetworkAgent : public protocol::Network::Backend { void dataReceived(v8::Local context, v8::Local params); + void webSocketCreated(v8::Local context, + v8::Local params); + void webSocketClosed(v8::Local context, + v8::Local params); + void webSocketHandshakeResponseReceived(v8::Local context, + v8::Local params); + private: NetworkInspector* inspector_; v8_inspector::V8Inspector* v8_inspector_; diff --git a/src/inspector/node_protocol.pdl b/src/inspector/node_protocol.pdl index 46631bc20ad081..a79dd3595ce493 100644 --- a/src/inspector/node_protocol.pdl +++ b/src/inspector/node_protocol.pdl @@ -185,6 +185,16 @@ experimental domain Network boolean success optional IO.StreamHandle stream + # WebSocket response data. + type WebSocketResponse extends object + properties + # HTTP response status code. + integer status + # HTTP response status text. + string statusText + # HTTP response headers. + Headers headers + # Disables network tracking, prevents network events from being sent to the client. command disable @@ -285,6 +295,31 @@ experimental domain Network integer encodedDataLength # Data that was received. experimental optional binary data + # Fired upon WebSocket creation. + event webSocketCreated + parameters + # Request identifier. + RequestId requestId + # WebSocket request URL. + string url + # Request initiator. + Initiator initiator + # Fired when WebSocket is closed. + event webSocketClosed + parameters + # Request identifier. + RequestId requestId + # Timestamp. + MonotonicTime timestamp + # Fired when WebSocket handshake response becomes available. + event webSocketHandshakeResponseReceived + parameters + # Request identifier. + RequestId requestId + # Timestamp. + MonotonicTime timestamp + # WebSocket response data. + WebSocketResponse response # Support for inspecting node process state. experimental domain NodeRuntime diff --git a/src/inspector/worker_inspector.cc b/src/inspector/worker_inspector.cc index 58c7d2602b327e..4b6ac1021be837 100644 --- a/src/inspector/worker_inspector.cc +++ b/src/inspector/worker_inspector.cc @@ -57,10 +57,10 @@ class WorkerFinishedRequest : public Request { ParentInspectorHandle::ParentInspectorHandle( uint64_t id, - const std::string& url, + std::string_view url, std::shared_ptr parent_thread, bool wait_for_connect, - const std::string& name, + std::string_view name, std::shared_ptr network_resource_manager) : id_(id), url_(url), @@ -104,8 +104,8 @@ void WorkerManager::WorkerStarted(uint64_t session_id, std::unique_ptr WorkerManager::NewParentHandle( uint64_t thread_id, - const std::string& url, - const std::string& name, + std::string_view url, + std::string_view name, std::shared_ptr network_resource_manager) { bool wait = !delegates_waiting_on_start_.empty(); return std::make_unique( diff --git a/src/inspector/worker_inspector.h b/src/inspector/worker_inspector.h index 28a249aea4d91c..468997230aa4c9 100644 --- a/src/inspector/worker_inspector.h +++ b/src/inspector/worker_inspector.h @@ -57,14 +57,14 @@ class ParentInspectorHandle { public: ParentInspectorHandle( uint64_t id, - const std::string& url, + std::string_view url, std::shared_ptr parent_thread, bool wait_for_connect, - const std::string& name, + std::string_view name, std::shared_ptr network_resource_manager); ~ParentInspectorHandle(); std::unique_ptr NewParentInspectorHandle( - uint64_t thread_id, const std::string& url, const std::string& name) { + uint64_t thread_id, std::string_view url, std::string_view name) { return std::make_unique( thread_id, url, parent_thread_, wait_, name, network_resource_manager_); } @@ -97,8 +97,8 @@ class WorkerManager : public std::enable_shared_from_this { std::unique_ptr NewParentHandle( uint64_t thread_id, - const std::string& url, - const std::string& name, + std::string_view url, + std::string_view name, std::shared_ptr network_resource_manager); void WorkerStarted(uint64_t session_id, const WorkerInfo& info, bool waiting); void WorkerFinished(uint64_t session_id); diff --git a/src/inspector_agent.cc b/src/inspector_agent.cc index 87addfa8fbbaf3..f62eb36b57542a 100644 --- a/src/inspector_agent.cc +++ b/src/inspector_agent.cc @@ -1155,7 +1155,7 @@ void Agent::SetParentHandle( } std::unique_ptr Agent::GetParentHandle( - uint64_t thread_id, const std::string& url, const std::string& name) { + uint64_t thread_id, std::string_view url, std::string_view name) { THROW_IF_INSUFFICIENT_PERMISSIONS(parent_env_, permission::PermissionScope::kInspector, "GetParentHandle", diff --git a/src/inspector_agent.h b/src/inspector_agent.h index e43dced8f410f3..5ace72a64012a0 100644 --- a/src/inspector_agent.h +++ b/src/inspector_agent.h @@ -94,8 +94,9 @@ class Agent { void DisableAsyncHook(); void SetParentHandle(std::unique_ptr parent_handle); - std::unique_ptr GetParentHandle( - uint64_t thread_id, const std::string& url, const std::string& name); + std::unique_ptr GetParentHandle(uint64_t thread_id, + std::string_view url, + std::string_view name); // Called to create inspector sessions that can be used from the same thread. // The inspector responds by using the delegate to send messages back. diff --git a/src/large_pages/node_text_start.S b/src/large_pages/node_text_start.S index d27dd39cc236f0..2a643457b57254 100644 --- a/src/large_pages/node_text_start.S +++ b/src/large_pages/node_text_start.S @@ -1,6 +1,27 @@ #if defined(__ELF__) .section .note.GNU-stack,"",%progbits #endif +// Add .note.gnu.property note for x86_64 to enable Intel CET +// Based on: https://sourceware.org/annobin/annobin.html/Test-cf-protection.html +// Refs: https://github.com/nodejs/node/issues/59084 +#if defined(__x86_64__) || defined(_M_X64) +.section .note.gnu.property,"a" +.align 8 +.long 1f - 0f +.long 4f - 1f +.long 5 +0: +.string "GNU" +1: +.align 8 +.long 0xc0000002 +.long 3f - 2f +2: +.long 0x3 +3: +.align 8 +4: +#endif .text .align 0x2000 .global __node_text_start diff --git a/src/module_wrap.cc b/src/module_wrap.cc index 28843f6206e62a..72d910fa4a8144 100644 --- a/src/module_wrap.cc +++ b/src/module_wrap.cc @@ -604,7 +604,6 @@ void ModuleWrap::GetModuleRequests(const FunctionCallbackInfo& args) { // moduleWrap.link(moduleWraps) void ModuleWrap::Link(const FunctionCallbackInfo& args) { - Realm* realm = Realm::GetCurrent(args); Isolate* isolate = args.GetIsolate(); ModuleWrap* dependent; @@ -612,15 +611,6 @@ void ModuleWrap::Link(const FunctionCallbackInfo& args) { CHECK_EQ(args.Length(), 1); - Local linked_requests = - args.This()->GetInternalField(kLinkedRequestsSlot); - if (linked_requests->IsValue() && - !linked_requests.As()->IsUndefined()) { - // If the module is already linked, we should not link it again. - THROW_ERR_VM_MODULE_LINK_FAILURE(realm->env(), "module is already linked"); - return; - } - Local requests = dependent->module_.Get(isolate)->GetModuleRequests(); Local modules = args[0].As(); diff --git a/src/node.cc b/src/node.cc index 95585e7b16a830..79c9dff69cf7f5 100644 --- a/src/node.cc +++ b/src/node.cc @@ -940,7 +940,17 @@ static ExitCode InitializeNodeWithArgsInternal( } #if !defined(NODE_WITHOUT_NODE_OPTIONS) - if (!(flags & ProcessInitializationFlags::kDisableNodeOptionsEnv)) { + bool should_parse_node_options = + !(flags & ProcessInitializationFlags::kDisableNodeOptionsEnv); +#ifndef DISABLE_SINGLE_EXECUTABLE_APPLICATION + if (sea::IsSingleExecutable()) { + sea::SeaResource sea_resource = sea::FindSingleExecutableResource(); + if (sea_resource.exec_argv_extension != sea::SeaExecArgvExtension::kEnv) { + should_parse_node_options = false; + } + } +#endif + if (should_parse_node_options) { // NODE_OPTIONS environment variable is preferred over the file one. if (credentials::SafeGetenv("NODE_OPTIONS", &node_options) || !node_options.empty()) { @@ -1208,6 +1218,20 @@ InitializeOncePerProcessInternal(const std::vector& args, return result; } + if (per_process::cli_options->use_system_ca) { + // Load the system CA certificates eagerly off the main thread to avoid + // blocking the main thread when the first TLS connection is made. We + // don't need to wait for the thread to finish with code here, as + // GetSystemStoreCACertificates() has a function-local static and any + // actual user of it will wait for that to complete initialization. + int r = crypto::LoadSystemCACertificatesOffThread(); + if (r != 0) { + FPrintF( + stderr, + "Warning: Failed to load system CA certificates off thread: %s\n", + uv_strerror(r)); + } + } // Ensure CSPRNG is properly seeded. CHECK(ncrypto::CSPRNG(nullptr, 0)); diff --git a/src/node.h b/src/node.h index 0eb21dee1a9391..c5ade4bd30456c 100644 --- a/src/node.h +++ b/src/node.h @@ -758,6 +758,12 @@ NODE_EXTERN std::unique_ptr GetInspectorParentHandle( const char* child_url, const char* name); +NODE_EXTERN std::unique_ptr GetInspectorParentHandle( + Environment* parent_env, + ThreadId child_thread_id, + std::string_view child_url, + std::string_view name); + struct StartExecutionCallbackInfo { v8::Local process_object; v8::Local native_require; diff --git a/src/node_constants.cc b/src/node_constants.cc index aebda8fb44b919..fd28e0904d05e2 100644 --- a/src/node_constants.cc +++ b/src/node_constants.cc @@ -1105,10 +1105,6 @@ NODE_DEFINE_CONSTANT(target, UV_FS_O_FILEMAP); NODE_DEFINE_CONSTANT(target, O_DIRECTORY); #endif -#ifdef O_EXCL - NODE_DEFINE_CONSTANT(target, O_EXCL); -#endif - #ifdef O_NOATIME NODE_DEFINE_CONSTANT(target, O_NOATIME); #endif diff --git a/src/node_contextify.cc b/src/node_contextify.cc index 4e10afeb28598e..ef4bc0085404b7 100644 --- a/src/node_contextify.cc +++ b/src/node_contextify.cc @@ -1646,6 +1646,12 @@ static const auto throws_only_in_cjs_error_messages = "await is only valid in async functions and " "the top level bodies of modules"}; +static const auto maybe_top_level_await_errors = + std::array{ + "missing ) after argument list", // example: `func(await 1);` + "SyntaxError: Unexpected" // example: `if(await 1)` + }; + // If cached_data is provided, it would be used for the compilation and // the on-disk compilation cache from NODE_COMPILE_CACHE (if configured) // would be ignored. @@ -1876,6 +1882,16 @@ bool ShouldRetryAsESM(Realm* realm, break; } } + + for (const auto& error_message : maybe_top_level_await_errors) { + if (message_view.find(error_message) != std::string_view::npos) { + // If the error message is related to top-level await, we can try to + // compile it as ESM. + maybe_valid_in_esm = true; + break; + } + } + if (!maybe_valid_in_esm) { return false; } diff --git a/src/node_crypto.cc b/src/node_crypto.cc index a94ef62d2c6d78..9bdb7201475f35 100644 --- a/src/node_crypto.cc +++ b/src/node_crypto.cc @@ -38,6 +38,7 @@ namespace crypto { #define CRYPTO_NAMESPACE_LIST_BASE(V) \ V(AES) \ + V(ChaCha20Poly1305) \ V(CipherBase) \ V(DiffieHellman) \ V(DSAAlg) \ @@ -59,6 +60,19 @@ namespace crypto { V(Verify) \ V(X509Certificate) +#if !defined(OPENSSL_NO_ARGON2) && OPENSSL_VERSION_NUMBER >= 0x30200000L +#define ARGON2_NAMESPACE_LIST(V) V(Argon2) +#else +#define ARGON2_NAMESPACE_LIST(V) +#endif // !OPENSSL_NO_ARGON2 && OpenSSL >= 3.2 + +// KEM functionality requires OpenSSL 3.0.0 or later +#if OPENSSL_VERSION_MAJOR >= 3 +#define KEM_NAMESPACE_LIST(V) V(KEM) +#else +#define KEM_NAMESPACE_LIST(V) +#endif + #ifdef OPENSSL_NO_SCRYPT #define SCRYPT_NAMESPACE_LIST(V) #else @@ -67,6 +81,8 @@ namespace crypto { #define CRYPTO_NAMESPACE_LIST(V) \ CRYPTO_NAMESPACE_LIST_BASE(V) \ + ARGON2_NAMESPACE_LIST(V) \ + KEM_NAMESPACE_LIST(V) \ SCRYPT_NAMESPACE_LIST(V) void Initialize(Local target, diff --git a/src/node_crypto.h b/src/node_crypto.h index d34ff52daff8bc..21380d304fdfe0 100644 --- a/src/node_crypto.h +++ b/src/node_crypto.h @@ -29,7 +29,9 @@ // remains for convenience for any code that still imports it. New // code should include the relevant src/crypto headers directly. #include "crypto/crypto_aes.h" +#include "crypto/crypto_argon2.h" #include "crypto/crypto_bio.h" +#include "crypto/crypto_chacha20_poly1305.h" #include "crypto/crypto_cipher.h" #include "crypto/crypto_context.h" #include "crypto/crypto_dh.h" @@ -38,6 +40,9 @@ #include "crypto/crypto_hash.h" #include "crypto/crypto_hkdf.h" #include "crypto/crypto_hmac.h" +#if OPENSSL_VERSION_MAJOR >= 3 +#include "crypto/crypto_kem.h" +#endif #include "crypto/crypto_keygen.h" #include "crypto/crypto_keys.h" #include "crypto/crypto_ml_dsa.h" diff --git a/src/node_errors.h b/src/node_errors.h index 48a9b59c418d12..09fe51d6f79825 100644 --- a/src/node_errors.h +++ b/src/node_errors.h @@ -50,6 +50,7 @@ void OOMErrorHandler(const char* location, const v8::OOMDetails& details); V(ERR_CONSTRUCT_CALL_INVALID, TypeError) \ V(ERR_CRYPTO_CUSTOM_ENGINE_NOT_SUPPORTED, Error) \ V(ERR_CRYPTO_INITIALIZATION_FAILED, Error) \ + V(ERR_CRYPTO_INVALID_ARGON2_PARAMS, TypeError) \ V(ERR_CRYPTO_INVALID_AUTH_TAG, TypeError) \ V(ERR_CRYPTO_INVALID_COUNTER, TypeError) \ V(ERR_CRYPTO_INVALID_CURVE, TypeError) \ @@ -184,6 +185,7 @@ ERRORS_WITH_CODE(V) V(ERR_CONSTRUCT_CALL_INVALID, "Constructor cannot be called") \ V(ERR_CONSTRUCT_CALL_REQUIRED, "Cannot call constructor without `new`") \ V(ERR_CRYPTO_INITIALIZATION_FAILED, "Initialization failed") \ + V(ERR_CRYPTO_INVALID_ARGON2_PARAMS, "Invalid Argon2 params") \ V(ERR_CRYPTO_INVALID_AUTH_TAG, "Invalid authentication tag") \ V(ERR_CRYPTO_INVALID_COUNTER, "Invalid counter") \ V(ERR_CRYPTO_INVALID_CURVE, "Invalid EC curve name") \ diff --git a/src/node_file.cc b/src/node_file.cc index e469498353c82c..0816d088df050c 100644 --- a/src/node_file.cc +++ b/src/node_file.cc @@ -1565,12 +1565,12 @@ static void Unlink(const FunctionCallbackInfo& args) { if (argc > 1) { // unlink(path, req) FSReqBase* req_wrap_async = GetReqWrap(args, 1); + CHECK_NOT_NULL(req_wrap_async); ASYNC_THROW_IF_INSUFFICIENT_PERMISSIONS( env, req_wrap_async, permission::PermissionScope::kFileSystemWrite, path.ToStringView()); - CHECK_NOT_NULL(req_wrap_async); FS_ASYNC_TRACE_BEGIN1( UV_FS_UNLINK, req_wrap_async, "path", TRACE_STR_COPY(*path)) AsyncCall(env, req_wrap_async, args, "unlink", UTF8, AfterNoArgs, diff --git a/src/node_metadata.cc b/src/node_metadata.cc index 87aa96cfdc5858..6544999b84dc0f 100644 --- a/src/node_metadata.cc +++ b/src/node_metadata.cc @@ -161,6 +161,28 @@ Metadata::Versions::Versions() { nbytes = NBYTES_VERSION; } +std::array, + NODE_VERSIONS_KEY_COUNT> +Metadata::Versions::pairs() const { + std::array, + NODE_VERSIONS_KEY_COUNT> + versions_array; + auto slot = versions_array.begin(); + +#define V(key) \ + do { \ + *slot++ = std::pair( \ + #key, per_process::metadata.versions.key); \ + } while (0); + NODE_VERSIONS_KEYS(V) +#undef V + + std::ranges::sort(versions_array, + [](auto& a, auto& b) { return a.first < b.first; }); + + return versions_array; +} + Metadata::Release::Release() : name(NODE_RELEASE) { #if NODE_VERSION_IS_LTS lts = NODE_VERSION_LTS_CODENAME; diff --git a/src/node_metadata.h b/src/node_metadata.h index 7b2072ad39c3f1..d9c533f100d25a 100644 --- a/src/node_metadata.h +++ b/src/node_metadata.h @@ -3,7 +3,9 @@ #if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS +#include #include +#include #include "node_version.h" #if HAVE_OPENSSL @@ -98,6 +100,10 @@ namespace node { NODE_VERSIONS_KEY_QUIC(V) \ NODE_VERSIONS_KEY_SQLITE(V) +#define V(key) +1 +constexpr int NODE_VERSIONS_KEY_COUNT = NODE_VERSIONS_KEYS(V); +#undef V + class Metadata { public: Metadata(); @@ -118,6 +124,10 @@ class Metadata { #define V(key) std::string key; NODE_VERSIONS_KEYS(V) #undef V + + std::array, + NODE_VERSIONS_KEY_COUNT> + pairs() const; }; struct Release { diff --git a/src/node_modules.cc b/src/node_modules.cc index b0dea6f12b0c53..9de75dda832d70 100644 --- a/src/node_modules.cc +++ b/src/node_modules.cc @@ -107,23 +107,11 @@ const BindingData::PackageConfig* BindingData::GetPackageJSON( if (ReadFileSync(&package_config.raw_json, path.data()) < 0) { return nullptr; } - // In some systems, std::string is annotated to generate an - // AddressSanitizer: container-overflow error when reading beyond the end of - // the string even when we are still within the capacity of the string. - // https://github.com/google/sanitizers/wiki/AddressSanitizerContainerOverflow - // https://github.com/nodejs/node/issues/55584 - // The next lines are a workaround to avoid this false positive. - size_t json_length = package_config.raw_json.size(); - package_config.raw_json.append(simdjson::SIMDJSON_PADDING, ' '); - simdjson::padded_string_view json_view(package_config.raw_json.data(), - json_length, - package_config.raw_json.size()); - // End of workaround - simdjson::ondemand::document document; simdjson::ondemand::object main_object; simdjson::error_code error = - binding_data->json_parser.iterate(json_view).get(document); + binding_data->json_parser.iterate(simdjson::pad(package_config.raw_json)) + .get(document); const auto throw_invalid_package_config = [error_context, path, realm]() { if (error_context == nullptr) { diff --git a/src/node_options.cc b/src/node_options.cc index 3848c5e0d4c1f5..159954cb6946b9 100644 --- a/src/node_options.cc +++ b/src/node_options.cc @@ -138,6 +138,11 @@ void PerIsolateOptions::HandleMaxOldSpaceSizePercentage( ? constrained_memory : total_memory; + if (available_memory == 0) { + errors->push_back("the available memory can not be calculated"); + return; + } + // Convert to MB and calculate the percentage uint64_t memory_mb = available_memory / (1024 * 1024); uint64_t calculated_mb = static_cast(memory_mb * percentage / 100.0); @@ -914,6 +919,11 @@ EnvironmentOptionsParser::EnvironmentOptionsParser() { &EnvironmentOptions::test_global_setup_path, kAllowedInEnvvar, OptionNamespaces::kTestRunnerNamespace); + AddOption("--test-rerun-failures", + "specifies the path to the rerun state file", + &EnvironmentOptions::test_rerun_failures_path, + kAllowedInEnvvar, + OptionNamespaces::kTestRunnerNamespace); AddOption("--test-udp-no-try-send", "", // For testing only. &EnvironmentOptions::test_udp_no_try_send, @@ -1864,6 +1874,117 @@ void GetNamespaceOptionsInputType(const FunctionCallbackInfo& args) { args.GetReturnValue().Set(namespaces_map); } +// Return an array containing all currently active options as flag +// strings from all sources (command line, NODE_OPTIONS, config file) +void GetOptionsAsFlags(const FunctionCallbackInfo& args) { + Isolate* isolate = args.GetIsolate(); + Local context = isolate->GetCurrentContext(); + Environment* env = Environment::GetCurrent(context); + + if (!env->has_run_bootstrapping_code()) { + // No code because this is an assertion. + THROW_ERR_OPTIONS_BEFORE_BOOTSTRAPPING( + isolate, "Should not query options before bootstrapping is done"); + } + env->set_has_serialized_options(true); + + Mutex::ScopedLock lock(per_process::cli_options_mutex); + IterateCLIOptionsScope s(env); + + std::vector flags; + PerProcessOptions* opts = per_process::cli_options.get(); + + for (const auto& item : _ppop_instance.options_) { + const std::string& option_name = item.first; + const auto& option_info = item.second; + auto field = option_info.field; + + // TODO(pmarchini): Skip internal options for the moment as probably not + // required + if (option_name.empty() || option_name.starts_with('[')) { + continue; + } + + // Skip V8 options and NoOp options - only Node.js-specific options + if (option_info.type == kNoOp || option_info.type == kV8Option) { + continue; + } + + switch (option_info.type) { + case kBoolean: { + bool current_value = *_ppop_instance.Lookup(field, opts); + // For boolean options with default_is_true, we want the opposite logic + if (option_info.default_is_true) { + if (!current_value) { + // If default is true and current is false, add --no-* flag + flags.push_back("--no-" + option_name.substr(2)); + } + } else { + if (current_value) { + // If default is false and current is true, add --flag + flags.push_back(option_name); + } + } + break; + } + case kInteger: { + int64_t current_value = *_ppop_instance.Lookup(field, opts); + flags.push_back(option_name + "=" + std::to_string(current_value)); + break; + } + case kUInteger: { + uint64_t current_value = *_ppop_instance.Lookup(field, opts); + flags.push_back(option_name + "=" + std::to_string(current_value)); + break; + } + case kString: { + const std::string& current_value = + *_ppop_instance.Lookup(field, opts); + // Only include if not empty + if (!current_value.empty()) { + flags.push_back(option_name + "=" + current_value); + } + break; + } + case kStringList: { + const std::vector& current_values = + *_ppop_instance.Lookup(field, opts); + // Add each string in the list as a separate flag + for (const std::string& value : current_values) { + flags.push_back(option_name + "=" + value); + } + break; + } + case kHostPort: { + const HostPort& host_port = + *_ppop_instance.Lookup(field, opts); + // Only include if host is not empty or port is not default + if (!host_port.host().empty() || host_port.port() != 0) { + std::string host_port_str = host_port.host(); + if (host_port.port() != 0) { + if (!host_port_str.empty()) { + host_port_str += ":"; + } + host_port_str += std::to_string(host_port.port()); + } + if (!host_port_str.empty()) { + flags.push_back(option_name + "=" + host_port_str); + } + } + break; + } + default: + // Skip unknown types + break; + } + } + + Local result; + CHECK(ToV8Value(context, flags).ToLocal(&result)); + + args.GetReturnValue().Set(result); +} + void Initialize(Local target, Local unused, Local context, @@ -1874,6 +1995,8 @@ void Initialize(Local target, context, target, "getCLIOptionsValues", GetCLIOptionsValues); SetMethodNoSideEffect( context, target, "getCLIOptionsInfo", GetCLIOptionsInfo); + SetMethodNoSideEffect( + context, target, "getOptionsAsFlags", GetOptionsAsFlags); SetMethodNoSideEffect( context, target, "getEmbedderOptions", GetEmbedderOptions); SetMethodNoSideEffect( @@ -1906,6 +2029,7 @@ void Initialize(Local target, void RegisterExternalReferences(ExternalReferenceRegistry* registry) { registry->Register(GetCLIOptionsValues); registry->Register(GetCLIOptionsInfo); + registry->Register(GetOptionsAsFlags); registry->Register(GetEmbedderOptions); registry->Register(GetEnvOptionsInputType); registry->Register(GetNamespaceOptionsInputType); diff --git a/src/node_options.h b/src/node_options.h index 460deced771d0b..3a1503a035e12b 100644 --- a/src/node_options.h +++ b/src/node_options.h @@ -197,6 +197,7 @@ class EnvironmentOptions : public Options { bool test_runner_update_snapshots = false; std::vector test_name_pattern; std::vector test_reporter; + std::string test_rerun_failures_path; std::vector test_reporter_destination; std::string test_global_setup_path; bool test_only = false; @@ -648,6 +649,8 @@ class OptionsParser { friend std::vector MapAvailableNamespaces(); friend void GetEnvOptionsInputType( const v8::FunctionCallbackInfo& args); + friend void GetOptionsAsFlags( + const v8::FunctionCallbackInfo& args); }; using StringVector = std::vector; diff --git a/src/node_process_object.cc b/src/node_process_object.cc index 5138ef7d616787..5f5af63486a556 100644 --- a/src/node_process_object.cc +++ b/src/node_process_object.cc @@ -83,24 +83,7 @@ static void SetVersions(Isolate* isolate, Local versions) { READONLY_STRING_PROPERTY( versions, "node", per_process::metadata.versions.node); -#define V(key) +1 - std::pair - versions_array[NODE_VERSIONS_KEYS(V)]; -#undef V - auto* slot = &versions_array[0]; - -#define V(key) \ - do { \ - *slot++ = std::pair( \ - #key, per_process::metadata.versions.key); \ - } while (0); - NODE_VERSIONS_KEYS(V) -#undef V - - std::ranges::sort(versions_array, - [](auto& a, auto& b) { return a.first < b.first; }); - - for (const auto& version : versions_array) { + for (const auto& version : per_process::metadata.versions.pairs()) { versions ->DefineOwnProperty(context, OneByteString(isolate, version.first), diff --git a/src/node_report.cc b/src/node_report.cc index 2dcdfa8b139cf7..ff69d3b44bd15e 100644 --- a/src/node_report.cc +++ b/src/node_report.cc @@ -233,11 +233,11 @@ static void WriteNodeReport(Isolate* isolate, size_t expected_results = 0; env->ForEachWorker([&](Worker* w) { - expected_results += w->RequestInterrupt([&](Environment* env) { + expected_results += w->RequestInterrupt([&, w = w](Environment* env) { std::ostringstream os; - - GetNodeReport( - env, "Worker thread subreport", trigger, Local(), os); + std::string name = + "Worker thread subreport [" + std::string(w->name()) + "]"; + GetNodeReport(env, name.c_str(), trigger, Local(), os); Mutex::ScopedLock lock(workers_mutex); worker_infos.emplace_back(os.str()); @@ -797,24 +797,7 @@ static void PrintComponentVersions(JSONWriter* writer) { writer->json_objectstart("componentVersions"); -#define V(key) +1 - std::pair - versions_array[NODE_VERSIONS_KEYS(V)]; -#undef V - auto* slot = &versions_array[0]; - -#define V(key) \ - do { \ - *slot++ = std::pair( \ - #key, per_process::metadata.versions.key); \ - } while (0); - NODE_VERSIONS_KEYS(V) -#undef V - - std::ranges::sort(versions_array, - [](auto& a, auto& b) { return a.first < b.first; }); - - for (const auto& version : versions_array) { + for (const auto& version : per_process::metadata.versions.pairs()) { writer->json_keyvalue(version.first, version.second); } diff --git a/src/node_root_certs.h b/src/node_root_certs.h index 69634bca1db986..9bf95d49f41e7f 100644 --- a/src/node_root_certs.h +++ b/src/node_root_certs.h @@ -1,71 +1,5 @@ #if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS -/* GlobalSign Root CA */ -"-----BEGIN CERTIFICATE-----\n" -"MIIDdTCCAl2gAwIBAgILBAAAAAABFUtaw5QwDQYJKoZIhvcNAQEFBQAwVzELMAkGA1UEBhMC\n" -"QkUxGTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYtc2ExEDAOBgNVBAsTB1Jvb3QgQ0ExGzAZBgNV\n" -"BAMTEkdsb2JhbFNpZ24gUm9vdCBDQTAeFw05ODA5MDExMjAwMDBaFw0yODAxMjgxMjAwMDBa\n" -"MFcxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMRAwDgYDVQQLEwdS\n" -"b290IENBMRswGQYDVQQDExJHbG9iYWxTaWduIFJvb3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUA\n" -"A4IBDwAwggEKAoIBAQDaDuaZjc6j40+Kfvvxi4Mla+pIH/EqsLmVEQS98GPR4mdmzxzdzxtI\n" -"K+6NiY6arymAZavpxy0Sy6scTHAHoT0KMM0VjU/43dSMUBUc71DuxC73/OlS8pF94G3VNTCO\n" -"XkNz8kHp1Wrjsok6Vjk4bwY8iGlbKk3Fp1S4bInMm/k8yuX9ifUSPJJ4ltbcdG6TRGHRjcdG\n" -"snUOhugZitVtbNV4FpWi6cgKOOvyJBNPc1STE4U6G7weNLWLBYy5d4ux2x8gkasJU26Qzns3\n" -"dLlwR5EiUWMWea6xrkEmCMgZK9FGqkjWZCrXgzT/LCrBbBlDSgeF59N89iFo7+ryUp9/k5DP\n" -"AgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRg\n" -"e2YaRQ2XyolQL30EzTSo//z9SzANBgkqhkiG9w0BAQUFAAOCAQEA1nPnfE920I2/7LqivjTF\n" -"KDK1fPxsnCwrvQmeU79rXqoRSLblCKOzyj1hTdNGCbM+w6DjY1Ub8rrvrTnhQ7k4o+YviiY7\n" -"76BQVvnGCv04zcQLcFGUl5gE38NflNUVyRRBnMRddWQVDf9VMOyGj/8N7yy5Y0b2qvzfvGn9\n" -"LhJIZJrglfCm7ymPAbEVtQwdpf5pLGkkeB6zpxxxYu7KyJesF12KwvhHhm4qxFYxldBniYUr\n" -"+WymXUadDKqC5JlR3XC321Y9YeRq4VzW9v493kHMB65jUr9TU/Qr6cf9tveCX4XSQRjbgbME\n" -"HMUfpIBvFSDJ3gyICh3WZlXi/EjJKSZp4A==\n" -"-----END CERTIFICATE-----", - -/* Entrust.net Premium 2048 Secure Server CA */ -"-----BEGIN CERTIFICATE-----\n" -"MIIEKjCCAxKgAwIBAgIEOGPe+DANBgkqhkiG9w0BAQUFADCBtDEUMBIGA1UEChMLRW50cnVz\n" -"dC5uZXQxQDA+BgNVBAsUN3d3dy5lbnRydXN0Lm5ldC9DUFNfMjA0OCBpbmNvcnAuIGJ5IHJl\n" -"Zi4gKGxpbWl0cyBsaWFiLikxJTAjBgNVBAsTHChjKSAxOTk5IEVudHJ1c3QubmV0IExpbWl0\n" -"ZWQxMzAxBgNVBAMTKkVudHJ1c3QubmV0IENlcnRpZmljYXRpb24gQXV0aG9yaXR5ICgyMDQ4\n" -"KTAeFw05OTEyMjQxNzUwNTFaFw0yOTA3MjQxNDE1MTJaMIG0MRQwEgYDVQQKEwtFbnRydXN0\n" -"Lm5ldDFAMD4GA1UECxQ3d3d3LmVudHJ1c3QubmV0L0NQU18yMDQ4IGluY29ycC4gYnkgcmVm\n" -"LiAobGltaXRzIGxpYWIuKTElMCMGA1UECxMcKGMpIDE5OTkgRW50cnVzdC5uZXQgTGltaXRl\n" -"ZDEzMDEGA1UEAxMqRW50cnVzdC5uZXQgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgKDIwNDgp\n" -"MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArU1LqRKGsuqjIAcVFmQqK0vRvwtK\n" -"TY7tgHalZ7d4QMBzQshowNtTK91euHaYNZOLGp18EzoOH1u3Hs/lJBQesYGpjX24zGtLA/EC\n" -"DNyrpUAkAH90lKGdCCmziAv1h3edVc3kw37XamSrhRSGlVuXMlBvPci6Zgzj/L24ScF2iUkZ\n" -"/cCovYmjZy/Gn7xxGWC4LeksyZB2ZnuU4q941mVTXTzWnLLPKQP5L6RQstRIzgUyVYr9smRM\n" -"DuSYB3Xbf9+5CFVghTAp+XtIpGmG4zU/HoZdenoVve8AjhUiVBcAkCaTvA5JaJG/+EfTnZVC\n" -"wQ5N328mz8MYIWJmQ3DW1cAH4QIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/\n" -"BAUwAwEB/zAdBgNVHQ4EFgQUVeSB0RGAvtiJuQijMfmhJAkWuXAwDQYJKoZIhvcNAQEFBQAD\n" -"ggEBADubj1abMOdTmXx6eadNl9cZlZD7Bh/KM3xGY4+WZiT6QBshJ8rmcnPyT/4xmf3IDExo\n" -"U8aAghOY+rat2l098c5u9hURlIIM7j+VrxGrD9cv3h8Dj1csHsm7mhpElesYT6YfzX1XEC+b\n" -"BAlahLVu2B064dae0Wx5XnkcFMXj0EyTO2U87d89vqbllRrDtRnDvV5bu/8j72gZyxKTJ1wD\n" -"LW8w0B62GqzeWvfRqqgnpv55gcR5mTNXuhKwqeBCbJPKVt7+bYQLCIt+jerXmCHG8+c8eS9e\n" -"nNFMFY3h7CI3zJpDC5fcgJCNs2ebb0gIFVbPv/ErfF6adulZkMV8gzURZVE=\n" -"-----END CERTIFICATE-----", - -/* Baltimore CyberTrust Root */ -"-----BEGIN CERTIFICATE-----\n" -"MIIDdzCCAl+gAwIBAgIEAgAAuTANBgkqhkiG9w0BAQUFADBaMQswCQYDVQQGEwJJRTESMBAG\n" -"A1UEChMJQmFsdGltb3JlMRMwEQYDVQQLEwpDeWJlclRydXN0MSIwIAYDVQQDExlCYWx0aW1v\n" -"cmUgQ3liZXJUcnVzdCBSb290MB4XDTAwMDUxMjE4NDYwMFoXDTI1MDUxMjIzNTkwMFowWjEL\n" -"MAkGA1UEBhMCSUUxEjAQBgNVBAoTCUJhbHRpbW9yZTETMBEGA1UECxMKQ3liZXJUcnVzdDEi\n" -"MCAGA1UEAxMZQmFsdGltb3JlIEN5YmVyVHJ1c3QgUm9vdDCCASIwDQYJKoZIhvcNAQEBBQAD\n" -"ggEPADCCAQoCggEBAKMEuyKrmD1X6CZymrV51Cni4eiVgLGw41uOKymaZN+hXe2wCQVt2ygu\n" -"zmKiYv60iNoS6zjrIZ3AQSsBUnuId9Mcj8e6uYi1agnnc+gRQKfRzMpijS3ljwumUNKoUMMo\n" -"6vWrJYeKmpYcqWe4PwzV9/lSEy/CG9VwcPCPwBLKBsua4dnKM3p31vjsufFoREJIE9LAwqSu\n" -"XmD+tqYF/LTdB1kC1FkYmGP1pWPgkAx9XbIGevOF6uvUA65ehD5f/xXtabz5OTZydc93Uk3z\n" -"yZAsuT3lySNTPx8kmCFcB5kpvcY67Oduhjprl3RjM71oGDHweI12v/yejl0qhqdNkNwnGjkC\n" -"AwEAAaNFMEMwHQYDVR0OBBYEFOWdWTCCR1jMrPoIVDaGezq1BE3wMBIGA1UdEwEB/wQIMAYB\n" -"Af8CAQMwDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3DQEBBQUAA4IBAQCFDF2O5G9RaEIFoN27\n" -"TyclhAO992T9Ldcw46QQF+vaKSm2eT929hkTI7gQCvlYpNRhcL0EYWoSihfVCr3FvDB81ukM\n" -"JY2GQE/szKN+OMY3EU/t3WgxjkzSswF07r51XgdIGn9w/xZchMB5hbgF/X++ZRGjD8ACtPhS\n" -"NzkE1akxehi/oCr0Epn3o0WC4zxe9Z2etciefC7IpJ5OCBRLbf1wbWsaY71k5h+3zvDyny67\n" -"G7fyUIhzksLi4xaNmjICq44Y3ekQEe5+NauQrz4wlHrQMz2nZQ/1/I6eYs9HRCwBXbsdtTLS\n" -"R9I4LtD+gdwyah617jzV/OeBHRnDJELqYzmp\n" -"-----END CERTIFICATE-----", - /* Entrust Root Certification Authority */ "-----BEGIN CERTIFICATE-----\n" "MIIEkTCCA3mgAwIBAgIERWtQVDANBgkqhkiG9w0BAQUFADCBsDELMAkGA1UEBhMCVVMxFjAU\n" @@ -92,30 +26,6 @@ "j2A781q0tHuu2guQOHXvgR1m0vdXcDazv/wor3ElhVsT/h5/WrQ8\n" "-----END CERTIFICATE-----", -/* Comodo AAA Services root */ -"-----BEGIN CERTIFICATE-----\n" -"MIIEMjCCAxqgAwIBAgIBATANBgkqhkiG9w0BAQUFADB7MQswCQYDVQQGEwJHQjEbMBkGA1UE\n" -"CAwSR3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHDAdTYWxmb3JkMRowGAYDVQQKDBFDb21v\n" -"ZG8gQ0EgTGltaXRlZDEhMB8GA1UEAwwYQUFBIENlcnRpZmljYXRlIFNlcnZpY2VzMB4XDTA0\n" -"MDEwMTAwMDAwMFoXDTI4MTIzMTIzNTk1OVowezELMAkGA1UEBhMCR0IxGzAZBgNVBAgMEkdy\n" -"ZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBwwHU2FsZm9yZDEaMBgGA1UECgwRQ29tb2RvIENB\n" -"IExpbWl0ZWQxITAfBgNVBAMMGEFBQSBDZXJ0aWZpY2F0ZSBTZXJ2aWNlczCCASIwDQYJKoZI\n" -"hvcNAQEBBQADggEPADCCAQoCggEBAL5AnfRu4ep2hxxNRUSOvkbIgwadwSr+GB+O5AL686td\n" -"UIoWMQuaBtDFcCLNSS1UY8y2bmhGC1Pqy0wkwLxyTurxFa70VJoSCsN6sjNg4tqJVfMiWPPe\n" -"3M/vg4aijJRPn2jymJBGhCfHdr/jzDUsi14HZGWCwEiwqJH5YZ92IFCokcdmtet4YgNW8Ioa\n" -"E+oxox6gmf049vYnMlhvB/VruPsUK6+3qszWY19zjNoFmag4qMsXeDZRrOme9Hg6jc8P2ULi\n" -"mAyrL58OAd7vn5lJ8S3frHRNG5i1R8XlKdH5kBjHYpy+g8cmez6KJcfA3Z3mNWgQIJ2P2N7S\n" -"w4ScDV7oL8kCAwEAAaOBwDCBvTAdBgNVHQ4EFgQUoBEKIz6W8Qfs4q8p74Klf9AwpLQwDgYD\n" -"VR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wewYDVR0fBHQwcjA4oDagNIYyaHR0cDov\n" -"L2NybC5jb21vZG9jYS5jb20vQUFBQ2VydGlmaWNhdGVTZXJ2aWNlcy5jcmwwNqA0oDKGMGh0\n" -"dHA6Ly9jcmwuY29tb2RvLm5ldC9BQUFDZXJ0aWZpY2F0ZVNlcnZpY2VzLmNybDANBgkqhkiG\n" -"9w0BAQUFAAOCAQEACFb8AvCb6P+k+tZ7xkSAzk/ExfYAWMymtrwUSWgEdujm7l3sAg9g1o1Q\n" -"GE8mTgHj5rCl7r+8dFRBv/38ErjHT1r0iWAFf2C3BUrz9vHCv8S5dIa2LX1rzNLzRt0vxuBq\n" -"w8M0Ayx9lt1awg6nCpnBBYurDC/zXDrPbDdVCYfeU0BsWO/8tqtlbgT2G9w84FoVxp7Z8VlI\n" -"MCFlA2zs6SFz7JsDoeA3raAVGI/6ugLOpyypEBMs1OUIJqsil2D4kF501KKaU73yqWjgom7C\n" -"12yxow+ev+to51byrvLjKzg6CYG1a4XXvi3tPxq3smPi9WIsgtRqAEFQ8TmDn5XpNpaYbg==\n" -"-----END CERTIFICATE-----", - /* QuoVadis Root CA 2 */ "-----BEGIN CERTIFICATE-----\n" "MIIFtzCCA5+gAwIBAgICBQkwDQYJKoZIhvcNAQEFBQAwRTELMAkGA1UEBhMCQk0xGTAXBgNV\n" @@ -184,78 +94,6 @@ "zTSMmfXK4SVhM7JZG+Ju1zdXtg2pEto=\n" "-----END CERTIFICATE-----", -/* XRamp Global CA Root */ -"-----BEGIN CERTIFICATE-----\n" -"MIIEMDCCAxigAwIBAgIQUJRs7Bjq1ZxN1ZfvdY+grTANBgkqhkiG9w0BAQUFADCBgjELMAkG\n" -"A1UEBhMCVVMxHjAcBgNVBAsTFXd3dy54cmFtcHNlY3VyaXR5LmNvbTEkMCIGA1UEChMbWFJh\n" -"bXAgU2VjdXJpdHkgU2VydmljZXMgSW5jMS0wKwYDVQQDEyRYUmFtcCBHbG9iYWwgQ2VydGlm\n" -"aWNhdGlvbiBBdXRob3JpdHkwHhcNMDQxMTAxMTcxNDA0WhcNMzUwMTAxMDUzNzE5WjCBgjEL\n" -"MAkGA1UEBhMCVVMxHjAcBgNVBAsTFXd3dy54cmFtcHNlY3VyaXR5LmNvbTEkMCIGA1UEChMb\n" -"WFJhbXAgU2VjdXJpdHkgU2VydmljZXMgSW5jMS0wKwYDVQQDEyRYUmFtcCBHbG9iYWwgQ2Vy\n" -"dGlmaWNhdGlvbiBBdXRob3JpdHkwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCY\n" -"JB69FbS638eMpSe2OAtp87ZOqCwuIR1cRN8hXX4jdP5efrRKt6atH67gBhbim1vZZ3RrXYCP\n" -"KZ2GG9mcDZhtdhAoWORlsH9KmHmf4MMxfoArtYzAQDsRhtDLooY2YKTVMIJt2W7QDxIEM5df\n" -"T2Fa8OT5kavnHTu86M/0ay00fOJIYRyO82FEzG+gSqmUsE3a56k0enI4qEHMPJQRfevIpoy3\n" -"hsvKMzvZPTeL+3o+hiznc9cKV6xkmxnr9A8ECIqsAxcZZPRaJSKNNCyy9mgdEm3Tih4U2sSP\n" -"puIjhdV6Db1q4Ons7Be7QhtnqiXtRYMh/MHJfNViPvryxS3T/dRlAgMBAAGjgZ8wgZwwEwYJ\n" -"KwYBBAGCNxQCBAYeBABDAEEwCwYDVR0PBAQDAgGGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0O\n" -"BBYEFMZPoj0GY4QJnM5i5ASsjVy16bYbMDYGA1UdHwQvMC0wK6ApoCeGJWh0dHA6Ly9jcmwu\n" -"eHJhbXBzZWN1cml0eS5jb20vWEdDQS5jcmwwEAYJKwYBBAGCNxUBBAMCAQEwDQYJKoZIhvcN\n" -"AQEFBQADggEBAJEVOQMBG2f7Shz5CmBbodpNl2L5JFMn14JkTpAuw0kbK5rc/Kh4ZzXxHfAR\n" -"vbdI4xD2Dd8/0sm2qlWkSLoC295ZLhVbO50WfUfXN+pfTXYSNrsf16GBBEYgoyxtqZ4Bfj8p\n" -"zgCT3/3JknOJiWSe5yvkHJEs0rnOfc5vMZnT5r7SHpDwCRR5XCOrTdLaIR9NmXmd4c8nnxCb\n" -"HIgNsIpkQTG4DmyQJKSbXHGPurt+HBvbaoAPIbzp26a3QPSyi6mx5O+aGtA9aZnuqCij4Tyz\n" -"8LIRnM98QObd50N9otg6tamN8jSZxNQQ4Qb9CYQQO+7ETPTsJ3xCwnR8gooJybQDJbw=\n" -"-----END CERTIFICATE-----", - -/* Go Daddy Class 2 CA */ -"-----BEGIN CERTIFICATE-----\n" -"MIIEADCCAuigAwIBAgIBADANBgkqhkiG9w0BAQUFADBjMQswCQYDVQQGEwJVUzEhMB8GA1UE\n" -"ChMYVGhlIEdvIERhZGR5IEdyb3VwLCBJbmMuMTEwLwYDVQQLEyhHbyBEYWRkeSBDbGFzcyAy\n" -"IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTA0MDYyOTE3MDYyMFoXDTM0MDYyOTE3MDYy\n" -"MFowYzELMAkGA1UEBhMCVVMxITAfBgNVBAoTGFRoZSBHbyBEYWRkeSBHcm91cCwgSW5jLjEx\n" -"MC8GA1UECxMoR28gRGFkZHkgQ2xhc3MgMiBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTCCASAw\n" -"DQYJKoZIhvcNAQEBBQADggENADCCAQgCggEBAN6d1+pXGEmhW+vXX0iG6r7d/+TvZxz0ZWiz\n" -"V3GgXne77ZtJ6XCAPVYYYwhv2vLM0D9/AlQiVBDYsoHUwHU9S3/Hd8M+eKsaA7Ugay9qK7HF\n" -"iH7Eux6wwdhFJ2+qN1j3hybX2C32qRe3H3I2TqYXP2WYktsqbl2i/ojgC95/5Y0V4evLOtXi\n" -"EqITLdiOr18SPaAIBQi2XKVlOARFmR6jYGB0xUGlcmIbYsUfb18aQr4CUWWoriMYavx4A6lN\n" -"f4DD+qta/KFApMoZFv6yyO9ecw3ud72a9nmYvLEHZ6IVDd2gWMZEewo+YihfukEHU1jPEX44\n" -"dMX4/7VpkI+EdOqXG68CAQOjgcAwgb0wHQYDVR0OBBYEFNLEsNKR1EwRcbNhyz2h/t2oatTj\n" -"MIGNBgNVHSMEgYUwgYKAFNLEsNKR1EwRcbNhyz2h/t2oatTjoWekZTBjMQswCQYDVQQGEwJV\n" -"UzEhMB8GA1UEChMYVGhlIEdvIERhZGR5IEdyb3VwLCBJbmMuMTEwLwYDVQQLEyhHbyBEYWRk\n" -"eSBDbGFzcyAyIENlcnRpZmljYXRpb24gQXV0aG9yaXR5ggEAMAwGA1UdEwQFMAMBAf8wDQYJ\n" -"KoZIhvcNAQEFBQADggEBADJL87LKPpH8EsahB4yOd6AzBhRckB4Y9wimPQoZ+YeAEW5p5JYX\n" -"MP80kWNyOO7MHAGjHZQopDH2esRU1/blMVgDoszOYtuURXO1v0XJJLXVggKtI3lpjbi2Tc7P\n" -"TMozI+gciKqdi0FuFskg5YmezTvacPd+mSYgFFQlq25zheabIZ0KbIIOqPjCDPoQHmyW74cN\n" -"xA9hi63ugyuV+I6ShHI56yDqg+2DzZduCLzrTia2cyvk0/ZM/iZx4mERdEr/VxqHD3VILs9R\n" -"aRegAhJhldXRQLIQTO7ErBBDpqWeCtWVYpoNz4iCxTIM5CufReYNnyicsbkqWletNw+vHX/b\n" -"vZ8=\n" -"-----END CERTIFICATE-----", - -/* Starfield Class 2 CA */ -"-----BEGIN CERTIFICATE-----\n" -"MIIEDzCCAvegAwIBAgIBADANBgkqhkiG9w0BAQUFADBoMQswCQYDVQQGEwJVUzElMCMGA1UE\n" -"ChMcU3RhcmZpZWxkIFRlY2hub2xvZ2llcywgSW5jLjEyMDAGA1UECxMpU3RhcmZpZWxkIENs\n" -"YXNzIDIgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDQwNjI5MTczOTE2WhcNMzQwNjI5\n" -"MTczOTE2WjBoMQswCQYDVQQGEwJVUzElMCMGA1UEChMcU3RhcmZpZWxkIFRlY2hub2xvZ2ll\n" -"cywgSW5jLjEyMDAGA1UECxMpU3RhcmZpZWxkIENsYXNzIDIgQ2VydGlmaWNhdGlvbiBBdXRo\n" -"b3JpdHkwggEgMA0GCSqGSIb3DQEBAQUAA4IBDQAwggEIAoIBAQC3Msj+6XGmBIWtDBFk385N\n" -"78gDGIc/oav7PKaf8MOh2tTYbitTkPskpD6E8J7oX+zlJ0T1KKY/e97gKvDIr1MvnsoFAZMe\n" -"j2YcOadN+lq2cwQlZut3f+dZxkqZJRRU6ybH838Z1TBwj6+wRir/resp7defqgSHo9T5iaU0\n" -"X9tDkYI22WY8sbi5gv2cOj4QyDvvBmVmepsZGD3/cVE8MC5fvj13c7JdBmzDI1aaK4Umkhyn\n" -"ArPkPw2vCHmCuDY96pzTNbO8acr1zJ3o/WSNF4Azbl5KXZnJHoe0nRrA1W4TNSNe35tfPe/W\n" -"93bC6j67eA0cQmdrBNj41tpvi/JEoAGrAgEDo4HFMIHCMB0GA1UdDgQWBBS/X7fRzt0fhvRb\n" -"Vazc1xDCDqmI5zCBkgYDVR0jBIGKMIGHgBS/X7fRzt0fhvRbVazc1xDCDqmI56FspGowaDEL\n" -"MAkGA1UEBhMCVVMxJTAjBgNVBAoTHFN0YXJmaWVsZCBUZWNobm9sb2dpZXMsIEluYy4xMjAw\n" -"BgNVBAsTKVN0YXJmaWVsZCBDbGFzcyAyIENlcnRpZmljYXRpb24gQXV0aG9yaXR5ggEAMAwG\n" -"A1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAAWdP4id0ckaVaGsafPzWdqbAYcaT1ep\n" -"oXkJKtv3L7IezMdeatiDh6GX70k1PncGQVhiv45YuApnP+yz3SFmH8lU+nLMPUxA2IGvd56D\n" -"eruix/U0F47ZEUD0/CwqTRV/p2JdLiXTAAsgGh1o+Re49L2L7ShZ3U0WixeDyLJlxy16paq8\n" -"U4Zt3VekyvggQQto8PT7dL5WXXp59fkdheMtlb71cZBDzI0fmgAKhynpVSJYACPq4xJDKVtH\n" -"CN2MQWplBqjlIapBtJUhlbl90TSrE9atvNziPTnNvT51cKEYWQPJIrSPnNVeKtelttQKbfi3\n" -"QBFGmh95DmK/D5fs4C8fF5Q=\n" -"-----END CERTIFICATE-----", - /* DigiCert Assured ID Root CA */ "-----BEGIN CERTIFICATE-----\n" "MIIDtzCCAp+gAwIBAgIQDOfg5RfYRv6P5WD8G/AwOTANBgkqhkiG9w0BAQUFADBlMQswCQYD\n" @@ -3598,6 +3436,52 @@ "GJCy89UDyibK79XH4I9TjvAA46jtn/mtd+ArY0+ew+43u3gJhJ65bvspmZDogNOfJA==\n" "-----END CERTIFICATE-----", +/* TrustAsia TLS ECC Root CA */ +"-----BEGIN CERTIFICATE-----\n" +"MIICMTCCAbegAwIBAgIUNnThTXxlE8msg1UloD5Sfi9QaMcwCgYIKoZIzj0EAwMwWDELMAkG\n" +"A1UEBhMCQ04xJTAjBgNVBAoTHFRydXN0QXNpYSBUZWNobm9sb2dpZXMsIEluYy4xIjAgBgNV\n" +"BAMTGVRydXN0QXNpYSBUTFMgRUNDIFJvb3QgQ0EwHhcNMjQwNTE1MDU0MTU2WhcNNDQwNTE1\n" +"MDU0MTU1WjBYMQswCQYDVQQGEwJDTjElMCMGA1UEChMcVHJ1c3RBc2lhIFRlY2hub2xvZ2ll\n" +"cywgSW5jLjEiMCAGA1UEAxMZVHJ1c3RBc2lhIFRMUyBFQ0MgUm9vdCBDQTB2MBAGByqGSM49\n" +"AgEGBSuBBAAiA2IABLh/pVs/AT598IhtrimY4ZtcU5nb9wj/1WrgjstEpvDBjL1P1M7UiFPo\n" +"XlfXTr4sP/MSpwDpguMqWzJ8S5sUKZ74LYO1644xST0mYekdcouJtgq7nDM1D9rs3qlKH8kz\n" +"saNCMEAwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQULIVTu7FDzTLqnqOH/qKYqKaT6RAw\n" +"DgYDVR0PAQH/BAQDAgEGMAoGCCqGSM49BAMDA2gAMGUCMFRH18MtYYZI9HlaVQ01L18N9mds\n" +"d0AaRuf4aFtOJx24mH1/k78ITcTaRTChD15KeAIxAKORh/IRM4PDwYqROkwrULG9IpRdNYlz\n" +"g8WbGf60oenUoWa2AaU2+dhoYSi3dOGiMQ==\n" +"-----END CERTIFICATE-----", + +/* TrustAsia TLS RSA Root CA */ +"-----BEGIN CERTIFICATE-----\n" +"MIIFgDCCA2igAwIBAgIUHBjYz+VTPyI1RlNUJDxsR9FcSpwwDQYJKoZIhvcNAQEMBQAwWDEL\n" +"MAkGA1UEBhMCQ04xJTAjBgNVBAoTHFRydXN0QXNpYSBUZWNobm9sb2dpZXMsIEluYy4xIjAg\n" +"BgNVBAMTGVRydXN0QXNpYSBUTFMgUlNBIFJvb3QgQ0EwHhcNMjQwNTE1MDU0MTU3WhcNNDQw\n" +"NTE1MDU0MTU2WjBYMQswCQYDVQQGEwJDTjElMCMGA1UEChMcVHJ1c3RBc2lhIFRlY2hub2xv\n" +"Z2llcywgSW5jLjEiMCAGA1UEAxMZVHJ1c3RBc2lhIFRMUyBSU0EgUm9vdCBDQTCCAiIwDQYJ\n" +"KoZIhvcNAQEBBQADggIPADCCAgoCggIBAMMWuBtqpERz5dZO9LnPWwvB0ZqB9WOwj0PBuwha\n" +"GnrhB3YmH49pVr7+NmDQDIPNlOrnxS1cLwUWAp4KqC/lYCZUlviYQB2srp10Zy9U+5RjmOMm\n" +"SoPGlbYJQ1DNDX3eRA5gEk9bNb2/mThtfWza4mhzH/kxpRkQcwUqwzIZheo0qt1CHjCNP561\n" +"HmHVb70AcnKtEj+qpklz8oYVlQwQX1Fkzv93uMltrOXVmPGZLmzjyUT5tUMnCE32ft5Eebuy\n" +"jBza00tsLtbDeLdM1aTk2tyKjg7/D8OmYCYozza/+lcK7Fs/6TAWe8TbxNRkoDD75f0dcZLd\n" +"KY9BWN4ArTr9PXwaqLEX8E40eFgl1oUh63kd0Nyrz2I8sMeXi9bQn9P+PN7F4/w6g3CEIR0J\n" +"wqH8uyghZVNgepBtljhb//HXeltt08lwSUq6HTrQUNoyIBnkiz/r1RYmNzz7dZ6wB3C4FGB3\n" +"3PYPXFIKvF1tjVEK2sUYyJtt3LCDs3+jTnhMmCWr8n4uIF6CFabW2I+s5c0yhsj55NqJ4js+\n" +"k8UTav/H9xj8Z7XvGCxUq0DTbE3txci3OE9kxJRMT6DNrqXGJyV1J23G2pyOsAWZ1SgRxSHU\n" +"uPzHlqtKZFlhaxP8S8ySpg+kUb8OWJDZgoM5pl+z+m6Ss80zDoWo8SnTq1mt1tve1CuBAgMB\n" +"AAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFLgHkXlcBvRG/XtZylomkadFK/hT\n" +"MA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQwFAAOCAgEAIZtqBSBdGBanEqT3Rz/Nyjuu\n" +"jsCCztxIJXgXbODgcMTWltnZ9r96nBO7U5WS/8+S4PPFJzVXqDuiGev4iqME3mmL5Dw8veWv\n" +"0BIb5Ylrc5tvJQJLkIKvQMKtuppgJFqBTQUYo+IzeXoLH5Pt7DlK9RME7I10nYEKqG/odv6L\n" +"TytpEoYKNDbdgptvT+Bz3Ul/KD7JO6NXBNiT2Twp2xIQaOHEibgGIOcberyxk2GaGUARtWqF\n" +"VwHxtlotJnMnlvm5P1vQiJ3koP26TpUJg3933FEFlJ0gcXax7PqJtZwuhfG5WyRasQmr2soa\n" +"B82G39tp27RIGAAtvKLEiUUjpQ7hRGU+isFqMB3iYPg6qocJQrmBktwliJiJ8Xw18WLK7nn4\n" +"GS/+X/jbh87qqA8MpugLoDzga5SYnH+tBuYc6kIQX+ImFTw3OffXvO645e8D7r0i+yiGNFjE\n" +"Wn9hongPXvPKnbwbPKfILfanIhHKA9jnZwqKDss1jjQ52MjqjZ9k4DewbNfFj8GQYSbbJIwe\n" +"SsCI3zWQzj8C9GRh3sfIB5XeMhg6j6JCQCTl1jNdfK7vsU1P1FeQNWrcrgSXSYk0ly4wBOeY\n" +"99sLAZDBHwo/+ML+TvrbmnNzFrwFuHnYWa8G5z9nODmxfKuU4CkUpijy323imttUQ/hHWKNd\n" +"dBWcwauwxzQ=\n" +"-----END CERTIFICATE-----", + /* D-TRUST EV Root CA 2 2023 */ "-----BEGIN CERTIFICATE-----\n" "MIIFqTCCA5GgAwIBAgIQaSYJfoBLTKCnjHhiU19abzANBgkqhkiG9w0BAQ0FADBIMQswCQYD\n" @@ -3628,4 +3512,35 @@ "S5N5YHVpD/Aa1VP6IQzCP+k/HxiMkl14p3ZnGbuy6n/pcAlWVqOwDAstNl7F6cTVg8uGF5cs\n" "bBNvh1qvSaYd2804BC5f4ko1Di1L+KIkBI3Y4WNeApI02phhXBxvWHZks/wCuPWdCg==\n" "-----END CERTIFICATE-----", + +/* SwissSign RSA TLS Root CA 2022 - 1 */ +"-----BEGIN CERTIFICATE-----\n" +"MIIFkzCCA3ugAwIBAgIUQ/oMX04bgBhE79G0TzUfRPSA7cswDQYJKoZIhvcNAQELBQAwUTEL\n" +"MAkGA1UEBhMCQ0gxFTATBgNVBAoTDFN3aXNzU2lnbiBBRzErMCkGA1UEAxMiU3dpc3NTaWdu\n" +"IFJTQSBUTFMgUm9vdCBDQSAyMDIyIC0gMTAeFw0yMjA2MDgxMTA4MjJaFw00NzA2MDgxMTA4\n" +"MjJaMFExCzAJBgNVBAYTAkNIMRUwEwYDVQQKEwxTd2lzc1NpZ24gQUcxKzApBgNVBAMTIlN3\n" +"aXNzU2lnbiBSU0EgVExTIFJvb3QgQ0EgMjAyMiAtIDEwggIiMA0GCSqGSIb3DQEBAQUAA4IC\n" +"DwAwggIKAoICAQDLKmjiC8NXvDVjvHClO/OMPE5Xlm7DTjak9gLKHqquuN6orx122ro10JFw\n" +"B9+zBvKK8i5VUXu7LCTLf5ImgKO0lPaCoaTo+nUdWfMHamFk4saMla+ju45vVs9xzF6BYQ1t\n" +"8qsCLqSX5XH8irCRIFucdFJtrhUnWXjyCcplDn/L9Ovn3KlMd/YrFgSVrpxxpT8q2kFC5zyE\n" +"EPThPYxr4iuRR1VPuFa+Rd4iUU1OKNlfGUEGjw5NBuBwQCMBauTLE5tzrE0USJIt/m2n+Idr\n" +"eXXhvhCxqohAWVTXz8TQm0SzOGlkjIHRI36qOTw7D59Ke4LKa2/KIj4x0LDQKhySio/YGZxH\n" +"5D4MucLNvkEM+KRHBdvBFzA4OmnczcNpI/2aDwLOEGrOyvi5KaM2iYauC8BPY7kGWUleDsFp\n" +"swrzd34unYyzJ5jSmY0lpx+Gs6ZUcDj8fV3oT4MM0ZPlEuRU2j7yrTrePjxF8CgPBrnh25d7\n" +"mUWe3f6VWQQvdT/TromZhqwUtKiE+shdOxtYk8EXlFXIC+OCeYSf8wCENO7cMdWP8vpPlkwG\n" +"qnj73mSiI80fPsWMvDdUDrtaclXvyFu1cvh43zcgTFeRc5JzrBh3Q4IgaezprClG5QtO+Ddz\n" +"iZaKHG29777YtvTKwP1H8K4LWCDFyB02rpeNUIMmJCn3nTsPBQIDAQABo2MwYTAPBgNVHRMB\n" +"Af8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAfBgNVHSMEGDAWgBRvjmKLk0Ow4UD2p8P98Q+4\n" +"DxU4pTAdBgNVHQ4EFgQUb45ii5NDsOFA9qfD/fEPuA8VOKUwDQYJKoZIhvcNAQELBQADggIB\n" +"AKwsKUF9+lz1GpUYvyypiqkkVHX1uECry6gkUSsYP2OprphWKwVDIqO310aewCoSPY6WlkDf\n" +"DDOLazeROpW7OSltwAJsipQLBwJNGD77+3v1dj2b9l4wBlgzHqp41eZUBDqyggmNzhYzWUUo\n" +"8aWjlw5DI/0LIICQ/+Mmz7hkkeUFjxOgdg3XNwwQiJb0Pr6VvfHDffCjw3lHC1ySFWPtUnWK\n" +"50Zpy1FVCypM9fJkT6lc/2cyjlUtMoIcgC9qkfjLvH4YoiaoLqNTKIftV+Vlek4ASltOU8li\n" +"Nr3CjlvrzG4ngRhZi0Rjn9UMZfQpZX+RLOV/fuiJz48gy20HQhFRJjKKLjpHE7iNvUcNCfAW\n" +"pO2Whi4Z2L6MOuhFLhG6rlrnub+xzI/goP+4s9GFe3lmozm1O2bYQL7Pt2eLSMkZJVX8vY3P\n" +"XtpOpvJpzv1/THfQwUY1mFwjmwJFQ5Ra3bxHrSL+ul4vkSkphnsh3m5kt8sNjzdbowhq6/Td\n" +"Ao9QAwKxuDdollDruF/UKIqlIgyKhPBZLtU30WHlQnNYKoH3dtvi4k0NX/a3vgW0rk4N3hY9\n" +"A4GzJl5LuEsAz/+MF7psYC0nhzck5npgL7XTgwSqT0N1osGDsieYK7EOgLrAhV5Cud+xYJHT\n" +"6xh+cHiudoO+cVrQkOPKwRYlZ0rwtnu64ZzZ\n" +"-----END CERTIFICATE-----", #endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS diff --git a/src/node_sea.cc b/src/node_sea.cc index 461862d67907c2..49071304262f10 100644 --- a/src/node_sea.cc +++ b/src/node_sea.cc @@ -7,6 +7,7 @@ #include "node_errors.h" #include "node_external_reference.h" #include "node_internals.h" +#include "node_options.h" #include "node_snapshot_builder.h" #include "node_union_bytes.h" #include "node_v8_platform-inl.h" @@ -86,6 +87,11 @@ size_t SeaSerializer::Write(const SeaResource& sea) { uint32_t flags = static_cast(sea.flags); Debug("Write SEA flags %x\n", flags); written_total += WriteArithmetic(flags); + + Debug("Write SEA resource exec argv extension %u\n", + static_cast(sea.exec_argv_extension)); + written_total += + WriteArithmetic(static_cast(sea.exec_argv_extension)); DCHECK_EQ(written_total, SeaResource::kHeaderSize); Debug("Write SEA code path %p, size=%zu\n", @@ -123,6 +129,18 @@ size_t SeaSerializer::Write(const SeaResource& sea) { written_total += WriteStringView(content, StringLogMode::kAddressOnly); } } + + if (static_cast(sea.flags & SeaFlags::kIncludeExecArgv)) { + Debug("Write SEA resource exec argv size %zu\n", sea.exec_argv.size()); + written_total += WriteArithmetic(sea.exec_argv.size()); + for (const auto& arg : sea.exec_argv) { + Debug("Write SEA resource exec arg %s at %p, size=%zu\n", + arg.data(), + arg.data(), + arg.size()); + written_total += WriteStringView(arg, StringLogMode::kAddressAndContent); + } + } return written_total; } @@ -146,6 +164,11 @@ SeaResource SeaDeserializer::Read() { CHECK_EQ(magic, kMagic); SeaFlags flags(static_cast(ReadArithmetic())); Debug("Read SEA flags %x\n", static_cast(flags)); + + uint8_t extension_value = ReadArithmetic(); + SeaExecArgvExtension exec_argv_extension = + static_cast(extension_value); + Debug("Read SEA resource exec argv extension %u\n", extension_value); CHECK_EQ(read_total, SeaResource::kHeaderSize); std::string_view code_path = @@ -185,7 +208,28 @@ SeaResource SeaDeserializer::Read() { assets.emplace(key, content); } } - return {flags, code_path, code, code_cache, assets}; + + std::vector exec_argv; + if (static_cast(flags & SeaFlags::kIncludeExecArgv)) { + size_t exec_argv_size = ReadArithmetic(); + Debug("Read SEA resource exec args size %zu\n", exec_argv_size); + exec_argv.reserve(exec_argv_size); + for (size_t i = 0; i < exec_argv_size; ++i) { + std::string_view arg = ReadStringView(StringLogMode::kAddressAndContent); + Debug("Read SEA resource exec arg %s at %p, size=%zu\n", + arg.data(), + arg.data(), + arg.size()); + exec_argv.emplace_back(arg); + } + } + return {flags, + exec_argv_extension, + code_path, + code, + code_cache, + assets, + exec_argv}; } std::string_view FindSingleExecutableBlob() { @@ -269,8 +313,56 @@ std::tuple FixupArgsForSEA(int argc, char** argv) { // entry point file path. if (IsSingleExecutable()) { static std::vector new_argv; - new_argv.reserve(argc + 2); + static std::vector exec_argv_storage; + static std::vector cli_extension_args; + + SeaResource sea_resource = FindSingleExecutableResource(); + + new_argv.clear(); + exec_argv_storage.clear(); + cli_extension_args.clear(); + + // Handle CLI extension mode for --node-options + if (sea_resource.exec_argv_extension == SeaExecArgvExtension::kCli) { + // Extract --node-options and filter argv + for (int i = 1; i < argc; ++i) { + if (strncmp(argv[i], "--node-options=", 15) == 0) { + std::string node_options = argv[i] + 15; + std::vector errors; + cli_extension_args = ParseNodeOptionsEnvVar(node_options, &errors); + // Remove this argument by shifting the rest + for (int j = i; j < argc - 1; ++j) { + argv[j] = argv[j + 1]; + } + argc--; + i--; // Adjust index since we removed an element + } + } + } + + // Reserve space for argv[0], exec argv, cli extension args, original argv, + // and nullptr + new_argv.reserve(argc + sea_resource.exec_argv.size() + + cli_extension_args.size() + 2); new_argv.emplace_back(argv[0]); + + // Insert exec argv from SEA config + if (!sea_resource.exec_argv.empty()) { + exec_argv_storage.reserve(sea_resource.exec_argv.size() + + cli_extension_args.size()); + for (const auto& arg : sea_resource.exec_argv) { + exec_argv_storage.emplace_back(arg); + new_argv.emplace_back(exec_argv_storage.back().data()); + } + } + + // Insert CLI extension args + for (const auto& arg : cli_extension_args) { + exec_argv_storage.emplace_back(arg); + new_argv.emplace_back(exec_argv_storage.back().data()); + } + + // Add actual run time arguments new_argv.insert(new_argv.end(), argv, argv + argc); new_argv.emplace_back(nullptr); argc = new_argv.size() - 1; @@ -286,7 +378,9 @@ struct SeaConfig { std::string main_path; std::string output_path; SeaFlags flags = SeaFlags::kDefault; + SeaExecArgvExtension exec_argv_extension = SeaExecArgvExtension::kEnv; std::unordered_map assets; + std::vector exec_argv; }; std::optional ParseSingleExecutableConfig( @@ -405,6 +499,50 @@ std::optional ParseSingleExecutableConfig( if (!result.assets.empty()) { result.flags |= SeaFlags::kIncludeAssets; } + } else if (key == "execArgv") { + simdjson::ondemand::array exec_argv_array; + if (field.value().get_array().get(exec_argv_array)) { + FPrintF(stderr, + "\"execArgv\" field of %s is not an array of strings\n", + config_path); + return std::nullopt; + } + std::vector exec_argv; + for (auto argv : exec_argv_array) { + std::string_view argv_str; + if (argv.get_string().get(argv_str)) { + FPrintF(stderr, + "\"execArgv\" field of %s is not an array of strings\n", + config_path); + return std::nullopt; + } + exec_argv.emplace_back(argv_str); + } + if (!exec_argv.empty()) { + result.flags |= SeaFlags::kIncludeExecArgv; + result.exec_argv = std::move(exec_argv); + } + } else if (key == "execArgvExtension") { + std::string_view extension_str; + if (field.value().get_string().get(extension_str)) { + FPrintF(stderr, + "\"execArgvExtension\" field of %s is not a string\n", + config_path); + return std::nullopt; + } + if (extension_str == "none") { + result.exec_argv_extension = SeaExecArgvExtension::kNone; + } else if (extension_str == "env") { + result.exec_argv_extension = SeaExecArgvExtension::kEnv; + } else if (extension_str == "cli") { + result.exec_argv_extension = SeaExecArgvExtension::kCli; + } else { + FPrintF(stderr, + "\"execArgvExtension\" field of %s must be one of " + "\"none\", \"env\", or \"cli\"\n", + config_path); + return std::nullopt; + } } } @@ -598,14 +736,20 @@ ExitCode GenerateSingleExecutableBlob( for (auto const& [key, content] : assets) { assets_view.emplace(key, content); } + std::vector exec_argv_view; + for (const auto& arg : config.exec_argv) { + exec_argv_view.emplace_back(arg); + } SeaResource sea{ config.flags, + config.exec_argv_extension, config.main_path, builds_snapshot_from_main ? std::string_view{snapshot_blob.data(), snapshot_blob.size()} : std::string_view{main_script.data(), main_script.size()}, optional_sv_code_cache, - assets_view}; + assets_view, + exec_argv_view}; SeaSerializer serializer; serializer.Write(sea); diff --git a/src/node_sea.h b/src/node_sea.h index f3b3c34d26a969..686e283fd6441b 100644 --- a/src/node_sea.h +++ b/src/node_sea.h @@ -28,19 +28,29 @@ enum class SeaFlags : uint32_t { kUseSnapshot = 1 << 1, kUseCodeCache = 1 << 2, kIncludeAssets = 1 << 3, + kIncludeExecArgv = 1 << 4, +}; + +enum class SeaExecArgvExtension : uint8_t { + kNone = 0, + kEnv = 1, + kCli = 2, }; struct SeaResource { SeaFlags flags = SeaFlags::kDefault; + SeaExecArgvExtension exec_argv_extension = SeaExecArgvExtension::kEnv; std::string_view code_path; std::string_view main_code_or_snapshot; std::optional code_cache; std::unordered_map assets; + std::vector exec_argv; bool use_snapshot() const; bool use_code_cache() const; - static constexpr size_t kHeaderSize = sizeof(kMagic) + sizeof(SeaFlags); + static constexpr size_t kHeaderSize = + sizeof(kMagic) + sizeof(SeaFlags) + sizeof(SeaExecArgvExtension); }; bool IsSingleExecutable(); diff --git a/src/node_snapshotable.cc b/src/node_snapshotable.cc index 9bdef1032e4dc9..c2e24b4645e790 100644 --- a/src/node_snapshotable.cc +++ b/src/node_snapshotable.cc @@ -11,7 +11,6 @@ #include "embedded_data.h" #include "encoding_binding.h" #include "env-inl.h" -#include "json_parser.h" #include "node_blob.h" #include "node_builtins.h" #include "node_contextify.h" @@ -27,6 +26,7 @@ #include "node_url.h" #include "node_v8.h" #include "node_v8_platform-inl.h" +#include "simdjson.h" #include "timers.h" #if HAVE_INSPECTOR @@ -909,32 +909,61 @@ std::optional ReadSnapshotConfig(const char* config_path) { return std::nullopt; } - JSONParser parser; - if (!parser.Parse(config_content)) { - FPrintF(stderr, "Cannot parse JSON from %s\n", config_path); - return std::nullopt; - } - SnapshotConfig result; - result.builder_script_path = parser.GetTopLevelStringField("builder"); - if (!result.builder_script_path.has_value()) { + + simdjson::ondemand::parser parser; + simdjson::ondemand::document document; + simdjson::ondemand::object main_object; + simdjson::error_code error = + parser.iterate(simdjson::pad(config_content)).get(document); + + if (!error) { + error = document.get_object().get(main_object); + } + if (error) { FPrintF(stderr, - "\"builder\" field of %s is not a non-empty string\n", - config_path); + "Cannot parse JSON from %s: %s\n", + config_path, + simdjson::error_message(error)); return std::nullopt; } - std::optional WithoutCodeCache = - parser.GetTopLevelBoolField("withoutCodeCache"); - if (!WithoutCodeCache.has_value()) { + for (auto field : main_object) { + std::string_view key; + if (field.unescaped_key().get(key)) { + FPrintF(stderr, "Cannot read key from %s\n", config_path); + return std::nullopt; + } + if (key == "builder") { + std::string builder_path; + if (field.value().get_string().get(builder_path) || + builder_path.empty()) { + FPrintF(stderr, + "\"builder\" field of %s is not a non-empty string\n", + config_path); + return std::nullopt; + } + result.builder_script_path = builder_path; + } else if (key == "withoutCodeCache") { + bool without_code_cache_value = false; + if (field.value().get_bool().get(without_code_cache_value)) { + FPrintF(stderr, + "\"withoutCodeCache\" field of %s is not a boolean\n", + config_path); + return std::nullopt; + } + if (without_code_cache_value) { + result.flags |= SnapshotFlags::kWithoutCodeCache; + } + } + } + + if (!result.builder_script_path.has_value()) { FPrintF(stderr, - "\"withoutCodeCache\" field of %s is not a boolean\n", + "\"builder\" field of %s is not a non-empty string\n", config_path); return std::nullopt; } - if (WithoutCodeCache.value()) { - result.flags |= SnapshotFlags::kWithoutCodeCache; - } return result; } diff --git a/src/node_sqlite.cc b/src/node_sqlite.cc index 7bfd49cf70c605..c635b1a206e66d 100644 --- a/src/node_sqlite.cc +++ b/src/node_sqlite.cc @@ -507,8 +507,7 @@ class BackupJob : public ThreadPoolWork { Local argv[] = {progress_info}; TryCatch try_catch(env()->isolate()); - fn->Call(env()->context(), Null(env()->isolate()), 1, argv) - .FromMaybe(Local()); + USE(fn->Call(env()->context(), Null(env()->isolate()), 1, argv)); if (try_catch.HasCaught()) { Finalize(); resolver->Reject(env()->context(), try_catch.Exception()).ToChecked(); @@ -1934,7 +1933,9 @@ bool StatementSync::BindParams(const FunctionCallbackInfo& args) { } for (int i = anon_start; i < args.Length(); ++i) { - while (sqlite3_bind_parameter_name(statement_, anon_idx) != nullptr) { + while (1) { + const char* param = sqlite3_bind_parameter_name(statement_, anon_idx); + if (param == nullptr || param[0] == '?') break; anon_idx++; } @@ -2740,6 +2741,13 @@ static void Initialize(Local target, db_tmpl, FIXED_ONE_BYTE_STRING(isolate, "isTransaction"), DatabaseSync::IsTransactionGetter); + Local sqlite_type_key = FIXED_ONE_BYTE_STRING(isolate, "sqlite-type"); + Local sqlite_type_symbol = + v8::Symbol::For(isolate, sqlite_type_key); + Local database_sync_string = + FIXED_ONE_BYTE_STRING(isolate, "node:sqlite"); + db_tmpl->InstanceTemplate()->Set(sqlite_type_symbol, database_sync_string); + SetConstructorFunction(context, target, "DatabaseSync", db_tmpl); SetConstructorFunction(context, target, diff --git a/src/node_v8_platform-inl.h b/src/node_v8_platform-inl.h index fc0922035aac07..57a725a988f445 100644 --- a/src/node_v8_platform-inl.h +++ b/src/node_v8_platform-inl.h @@ -38,31 +38,11 @@ class NodeTraceStateObserver TRACE_EVENT_METADATA1( "__metadata", "thread_name", "name", "JavaScriptMainThread"); - auto trace_process = tracing::TracedValue::Create(); - trace_process->BeginDictionary("versions"); - -#define V(key) \ - trace_process->SetString(#key, per_process::metadata.versions.key.c_str()); - - NODE_VERSIONS_KEYS(V) -#undef V - - trace_process->EndDictionary(); - - trace_process->SetString("arch", per_process::metadata.arch.c_str()); - trace_process->SetString("platform", - per_process::metadata.platform.c_str()); - - trace_process->BeginDictionary("release"); - trace_process->SetString("name", - per_process::metadata.release.name.c_str()); -#if NODE_VERSION_IS_LTS - trace_process->SetString("lts", per_process::metadata.release.lts.c_str()); -#endif - trace_process->EndDictionary(); - TRACE_EVENT_METADATA1( - "__metadata", "node", "process", std::move(trace_process)); - + tracing::ProcessMeta trace_process; + TRACE_EVENT_METADATA1("__metadata", + "node", + "process", + tracing::CastTracedValue(trace_process)); // This only runs the first time tracing is enabled controller_->RemoveTraceStateObserver(this); } diff --git a/src/node_version.h b/src/node_version.h index 0d6e6b917c04df..3039f907c4046e 100644 --- a/src/node_version.h +++ b/src/node_version.h @@ -23,13 +23,13 @@ #define SRC_NODE_VERSION_H_ #define NODE_MAJOR_VERSION 24 -#define NODE_MINOR_VERSION 6 -#define NODE_PATCH_VERSION 1 +#define NODE_MINOR_VERSION 7 +#define NODE_PATCH_VERSION 0 #define NODE_VERSION_IS_LTS 0 #define NODE_VERSION_LTS_CODENAME "" -#define NODE_VERSION_IS_RELEASE 0 +#define NODE_VERSION_IS_RELEASE 1 #ifndef NODE_STRINGIFY #define NODE_STRINGIFY(n) NODE_STRINGIFY_HELPER(n) diff --git a/src/node_worker.cc b/src/node_worker.cc index 76a17305f088d6..e00a5d60d1295c 100644 --- a/src/node_worker.cc +++ b/src/node_worker.cc @@ -104,7 +104,7 @@ Worker::Worker(Environment* env, if (env->permission()->is_granted( env, node::permission::PermissionScope::kInspector)) { inspector_parent_handle_ = - GetInspectorParentHandle(env, thread_id_, url.c_str(), name.c_str()); + GetInspectorParentHandle(env, thread_id_, url, name); } argv_ = std::vector{env->argv()[0]}; diff --git a/src/node_worker.h b/src/node_worker.h index 9e80a764a8bd71..54e76e5f98b26b 100644 --- a/src/node_worker.h +++ b/src/node_worker.h @@ -62,6 +62,7 @@ class Worker : public AsyncWrap { bool is_stopped() const; const SnapshotData* snapshot_data() const { return snapshot_data_; } bool is_internal() const { return is_internal_; } + std::string_view name() const { return name_; } static void New(const v8::FunctionCallbackInfo& args); static void CloneParentEnvVars( diff --git a/src/tracing/traced_value.cc b/src/tracing/traced_value.cc index 662b0357a19305..46c15ae8f1669c 100644 --- a/src/tracing/traced_value.cc +++ b/src/tracing/traced_value.cc @@ -9,10 +9,10 @@ #include #endif -#include -#include #include -#include + +#include "node_metadata.h" +#include "util.h" #if defined(_STLP_VENDOR_CSTD) // STLPort doesn't import fpclassify into the std namespace. @@ -218,5 +218,44 @@ void TracedValue::AppendAsTraceFormat(std::string* out) const { *out += root_is_array_ ? ']' : '}'; } +std::unique_ptr EnvironmentArgs::Cast() const { + auto traced_value = tracing::TracedValue::Create(); + traced_value->BeginArray("args"); + for (const std::string& arg : args_) traced_value->AppendString(arg); + traced_value->EndArray(); + traced_value->BeginArray("exec_args"); + for (const std::string& arg : exec_args_) traced_value->AppendString(arg); + traced_value->EndArray(); + return traced_value; +} + +std::unique_ptr AsyncWrapArgs::Cast() const { + auto data = tracing::TracedValue::Create(); + data->SetInteger("executionAsyncId", execution_async_id_); + data->SetInteger("triggerAsyncId", trigger_async_id_); + return data; +} + +std::unique_ptr ProcessMeta::Cast() const { + auto trace_process = tracing::TracedValue::Create(); + trace_process->BeginDictionary("versions"); + for (const auto& version : per_process::metadata.versions.pairs()) { + trace_process->SetString(version.first.data(), version.second.data()); + } + trace_process->EndDictionary(); + + trace_process->SetString("arch", per_process::metadata.arch.c_str()); + trace_process->SetString("platform", per_process::metadata.platform.c_str()); + + trace_process->BeginDictionary("release"); + trace_process->SetString("name", per_process::metadata.release.name.c_str()); +#if NODE_VERSION_IS_LTS + trace_process->SetString("lts", per_process::metadata.release.lts.c_str()); +#endif + trace_process->EndDictionary(); + + return trace_process; +} + } // namespace tracing } // namespace node diff --git a/src/tracing/traced_value.h b/src/tracing/traced_value.h index 93c5be799b63a4..0bc9df81d87562 100644 --- a/src/tracing/traced_value.h +++ b/src/tracing/traced_value.h @@ -5,17 +5,76 @@ #ifndef SRC_TRACING_TRACED_VALUE_H_ #define SRC_TRACING_TRACED_VALUE_H_ -#include "node.h" -#include "util.h" -#include "v8.h" +#include "v8-platform.h" -#include -#include +#include +#include #include namespace node { namespace tracing { +template +std::unique_ptr CastTracedValue(const T& value) { + return value.Cast(); +} + +class EnvironmentArgs { + public: + EnvironmentArgs(std::span args, + std::span exec_args) + : args_(args), exec_args_(exec_args) {} + + std::unique_ptr Cast() const; + + private: + std::span args_; + std::span exec_args_; +}; + +class AsyncWrapArgs { + public: + AsyncWrapArgs(int64_t execution_async_id, int64_t trigger_async_id) + : execution_async_id_(execution_async_id), + trigger_async_id_(trigger_async_id) {} + + std::unique_ptr Cast() const; + + private: + int64_t execution_async_id_; + int64_t trigger_async_id_; +}; + +class ProcessMeta { + public: + std::unique_ptr Cast() const; +}; + +// Do not use this class directly. Define a custom structured class to provide +// a conversion method so that the class can be used with both V8 legacy +// trace API and perfetto API. +// +// These classes provide a JSON-inspired way to write structed data into traces. +// +// To define how a custom class should be written into the trace, users should +// define one of the two following functions: +// - Foo::Cast(TracedValue) const +// (preferred for code which depends on perfetto directly) +// +// After defining a conversion method, the object can be used as a +// TRACE_EVENT argument: +// +// Foo foo; +// TRACE_EVENT("cat", "Event", "arg", CastTracedValue(foo)); +// +// class Foo { +// std::unique_ptr Cast() const { +// auto traced_value = tracing::TracedValue::Create(); +// dict->SetInteger("key", 42); +// dict->SetString("foo", "bar"); +// return traced_value; +// } +// } class TracedValue : public v8::ConvertableToTraceFormat { public: ~TracedValue() override = default; diff --git a/src/undici_version.h b/src/undici_version.h index 9df0737f163f4a..37a129b9bbab23 100644 --- a/src/undici_version.h +++ b/src/undici_version.h @@ -2,5 +2,5 @@ // Refer to tools/dep_updaters/update-undici.sh #ifndef SRC_UNDICI_VERSION_H_ #define SRC_UNDICI_VERSION_H_ -#define UNDICI_VERSION "7.13.0" +#define UNDICI_VERSION "7.14.0" #endif // SRC_UNDICI_VERSION_H_ diff --git a/src/util.h b/src/util.h index 74edcdbc2d45d1..a5313506d80d7c 100644 --- a/src/util.h +++ b/src/util.h @@ -346,17 +346,19 @@ inline v8::Local OneByteString(v8::Isolate* isolate, std::string_view str); // Used to be a macro, hence the uppercase name. -template -inline v8::Local FIXED_ONE_BYTE_STRING( - v8::Isolate* isolate, - const char(&data)[N]) { +template + requires(N > 0) +inline v8::Local FIXED_ONE_BYTE_STRING(v8::Isolate* isolate, + const char (&data)[N]) { + CHECK_EQ(data[N - 1], '\0'); return OneByteString(isolate, data, N - 1); } template + requires(N > 0) inline v8::Local FIXED_ONE_BYTE_STRING( - v8::Isolate* isolate, - const std::array& arr) { + v8::Isolate* isolate, const std::array& arr) { + CHECK_EQ(arr[N - 1], '\0'); return OneByteString(isolate, arr.data(), N - 1); } diff --git a/test/cctest/test_traced_value.cc b/test/cctest/test_traced_value.cc index fa938c881ef06c..f877e9d0b00e87 100644 --- a/test/cctest/test_traced_value.cc +++ b/test/cctest/test_traced_value.cc @@ -94,3 +94,29 @@ TEST(TracedValue, EscapingArray) { EXPECT_EQ(check, string); } + +TEST(TracedValue, EnvironmentArgs) { + std::vector args{"a", "bb", "ccc"}; + std::vector exec_args{"--inspect", "--a-long-arg"}; + node::tracing::EnvironmentArgs env_args(args, exec_args); + + std::string string; + env_args.Cast()->AppendAsTraceFormat(&string); + + static const char* check = "{\"args\":[\"a\",\"bb\",\"ccc\"]," + "\"exec_args\":[\"--inspect\",\"--a-long-arg\"]}"; + + EXPECT_EQ(check, string); +} + +TEST(TracedValue, AsyncWrapArgs) { + node::tracing::AsyncWrapArgs aw_args(1, 1); + + std::string string; + aw_args.Cast()->AppendAsTraceFormat(&string); + + static const char* check = "{\"executionAsyncId\":1," + "\"triggerAsyncId\":1}"; + + EXPECT_EQ(check, string); +} diff --git a/test/client-proxy/test-http-proxy-request-connection-refused.mjs b/test/client-proxy/test-http-proxy-request-connection-refused.mjs index 234587dd1dfe0d..d07cf8ce530203 100644 --- a/test/client-proxy/test-http-proxy-request-connection-refused.mjs +++ b/test/client-proxy/test-http-proxy-request-connection-refused.mjs @@ -2,11 +2,10 @@ // handle it correctly. import * as common from '../common/index.mjs'; -import assert from 'node:assert'; import http from 'node:http'; +import assert from 'node:assert'; import { once } from 'events'; import { runProxiedRequest } from '../common/proxy-server.js'; -import dgram from 'node:dgram'; const server = http.createServer(common.mustNotCall()); server.on('error', common.mustNotCall((err) => { console.error('Server error', err); })); @@ -16,24 +15,34 @@ await once(server, 'listening'); const serverHost = `localhost:${server.address().port}`; const requestUrl = `http://${serverHost}/test`; -// Make it fail on connection refused by connecting to a UDP port with TCP. -const udp = dgram.createSocket('udp4'); -udp.bind(0, '127.0.0.1'); -await once(udp, 'listening'); - -const port = udp.address().port; - -const { code, signal, stderr, stdout } = await runProxiedRequest({ - NODE_USE_ENV_PROXY: 1, - REQUEST_URL: requestUrl, - HTTP_PROXY: `http://localhost:${port}`, -}); - -// The proxy client should get a connection refused error. -assert.match(stderr, /Error.*connect ECONNREFUSED/); -assert.strictEqual(stdout.trim(), ''); -assert.strictEqual(code, 0); -assert.strictEqual(signal, null); +let maxRetries = 10; +let foundRefused = false; +while (maxRetries-- > 0) { + // Make it fail on connection refused by connecting to a port of a closed server. + // If it succeeds, get a different port and retry. + const proxy = http.createServer((req, res) => { + res.destroy(); + }); + proxy.listen(0); + await once(proxy, 'listening'); + const port = proxy.address().port; + proxy.close(); + await once(proxy, 'close'); + + console.log(`Trying proxy at port ${port}`); + const { stderr } = await runProxiedRequest({ + NODE_USE_ENV_PROXY: 1, + REQUEST_URL: requestUrl, + HTTP_PROXY: `http://localhost:${port}`, + REQUEST_TIMEOUT: 5000, + }); + + foundRefused = /Error.*connect ECONNREFUSED/.test(stderr); + if (foundRefused) { + // The proxy client should get a connection refused error. + break; + } +} server.close(); -udp.close(); +assert(foundRefused, 'Expected ECONNREFUSED error from proxy request'); diff --git a/test/client-proxy/test-https-proxy-request-connection-refused.mjs b/test/client-proxy/test-https-proxy-request-connection-refused.mjs index de13342c67f2e8..f2a9875a4ef44a 100644 --- a/test/client-proxy/test-https-proxy-request-connection-refused.mjs +++ b/test/client-proxy/test-https-proxy-request-connection-refused.mjs @@ -5,7 +5,7 @@ import fixtures from '../common/fixtures.js'; import assert from 'node:assert'; import { once } from 'events'; import { runProxiedRequest } from '../common/proxy-server.js'; -import dgram from 'node:dgram'; +import http from 'node:http'; if (!common.hasCrypto) common.skip('missing crypto'); @@ -25,24 +25,35 @@ await once(server, 'listening'); const serverHost = `localhost:${server.address().port}`; const requestUrl = `https://${serverHost}/test`; -// Make it fail on connection refused by connecting to a UDP port with TCP. -const udp = dgram.createSocket('udp4'); -udp.bind(0, '127.0.0.1'); -await once(udp, 'listening'); -const port = udp.address().port; - -const { code, signal, stderr, stdout } = await runProxiedRequest({ - NODE_USE_ENV_PROXY: 1, - REQUEST_URL: requestUrl, - HTTPS_PROXY: `http://localhost:${port}`, - NODE_EXTRA_CA_CERTS: fixtures.path('keys', 'fake-startcom-root-cert.pem'), -}); - -// The proxy client should get a connection refused error. -assert.match(stderr, /Error.*connect ECONNREFUSED/); -assert.strictEqual(stdout.trim(), ''); -assert.strictEqual(code, 0); -assert.strictEqual(signal, null); +let maxRetries = 10; +let foundRefused = false; +while (maxRetries-- > 0) { + // Make it fail on connection refused by connecting to a port of a closed server. + // If it succeeds, get a different port and retry. + const proxy = http.createServer((req, res) => { + res.destroy(); + }); + proxy.listen(0); + await once(proxy, 'listening'); + const port = proxy.address().port; + proxy.close(); + await once(proxy, 'close'); + + console.log(`Trying proxy at port ${port}`); + const { stderr } = await runProxiedRequest({ + NODE_USE_ENV_PROXY: 1, + REQUEST_URL: requestUrl, + HTTPS_PROXY: `http://localhost:${port}`, + NODE_EXTRA_CA_CERTS: fixtures.path('keys', 'fake-startcom-root-cert.pem'), + REQUEST_TIMEOUT: 5000, + }); + + foundRefused = /Error.*connect ECONNREFUSED/.test(stderr); + if (foundRefused) { + // The proxy client should get a connection refused error. + break; + } +} server.close(); -udp.close(); +assert(foundRefused, 'Expected ECONNREFUSED error from proxy request'); diff --git a/test/common/assertSnapshot.js b/test/common/assertSnapshot.js index 7a40c94389eda9..7ce70b8c31d3aa 100644 --- a/test/common/assertSnapshot.js +++ b/test/common/assertSnapshot.js @@ -84,7 +84,7 @@ async function spawnAndAssert(filename, transform = (x) => x, { tty = false, ... test({ skip: 'Skipping pseudo-tty tests, as pseudo terminals are not available on Windows.' }); return; } - let flags = common.parseTestFlags(filename); + let { flags } = common.parseTestMetadata(filename); if (options.flags) { flags = [...options.flags, ...flags]; } diff --git a/test/common/index.js b/test/common/index.js index c837adf811c352..0f06c480ff1556 100755 --- a/test/common/index.js +++ b/test/common/index.js @@ -58,8 +58,16 @@ const hasSQLite = Boolean(process.versions.sqlite); const hasQuic = hasCrypto && !!process.config.variables.node_quic; -function parseTestFlags(filename = process.argv[1]) { - // The copyright notice is relatively big and the flags could come afterwards. +/** + * Parse test metadata from the specified file. + * @param {string} filename - The name of the file to parse. + * @returns {{ + * flags: string[], + * envs: Record + * }} An object containing the parsed flags and environment variables. + */ +function parseTestMetadata(filename = process.argv[1]) { + // The copyright notice is relatively big and the metadata could come afterwards. const bytesToRead = 1500; const buffer = Buffer.allocUnsafe(bytesToRead); const fd = fs.openSync(filename, 'r'); @@ -68,19 +76,33 @@ function parseTestFlags(filename = process.argv[1]) { const source = buffer.toString('utf8', 0, bytesRead); const flagStart = source.search(/\/\/ Flags:\s+--/) + 10; - - if (flagStart === 9) { - return []; + let flags = []; + if (flagStart !== 9) { + let flagEnd = source.indexOf('\n', flagStart); + if (source[flagEnd - 1] === '\r') { + flagEnd--; + } + flags = source + .substring(flagStart, flagEnd) + .split(/\s+/) + .filter(Boolean); } - let flagEnd = source.indexOf('\n', flagStart); - // Normalize different EOL. - if (source[flagEnd - 1] === '\r') { - flagEnd--; + + const envStart = source.search(/\/\/ Env:\s+/) + 8; + let envs = {}; + if (envStart !== 7) { + let envEnd = source.indexOf('\n', envStart); + if (source[envEnd - 1] === '\r') { + envEnd--; + } + const envArray = source + .substring(envStart, envEnd) + .split(/\s+/) + .filter(Boolean); + envs = Object.fromEntries(envArray.map((env) => env.split('='))); } - return source - .substring(flagStart, flagEnd) - .split(/\s+/) - .filter(Boolean); + + return { flags, envs }; } // Check for flags. Skip this for workers (both, the `cluster` module and @@ -93,26 +115,39 @@ if (process.argv.length === 2 && hasCrypto && require('cluster').isPrimary && fs.existsSync(process.argv[1])) { - const flags = parseTestFlags(); - for (const flag of flags) { - if (!process.execArgv.includes(flag) && - // If the binary is build without `intl` the inspect option is - // invalid. The test itself should handle this case. - (process.features.inspector || !flag.startsWith('--inspect'))) { - console.log( - 'NOTE: The test started as a child_process using these flags:', - inspect(flags), - 'Use NODE_SKIP_FLAG_CHECK to run the test with the original flags.', - ); - const { spawnSync } = require('child_process'); - const args = [...flags, ...process.execArgv, ...process.argv.slice(1)]; - const options = { encoding: 'utf8', stdio: 'inherit' }; - const result = spawnSync(process.execPath, args, options); - if (result.signal) { - process.kill(0, result.signal); - } else { - process.exit(result.status); - } + const { flags, envs } = parseTestMetadata(); + + const flagsTriggerSpawn = flags.some((flag) => ( + !process.execArgv.includes(flag) && + // If the binary is build without `intl` the inspect option is + // invalid. The test itself should handle this case. + (process.features.inspector || !flag.startsWith('--inspect')) + )); + const envsTriggerSpawn = Object.keys(envs).some((key) => process.env[key] !== envs[key]); + + if (flagsTriggerSpawn || envsTriggerSpawn) { + console.log( + 'NOTE: The test started as a child_process using these flags:', + inspect(flags), + 'And these environment variables:', + inspect(envs), + 'Use NODE_SKIP_FLAG_CHECK to run the test with the original flags.', + ); + const { spawnSync } = require('child_process'); + const args = [...flags, ...process.execArgv, ...process.argv.slice(1)]; + const options = { + encoding: 'utf8', + stdio: 'inherit', + env: { + ...process.env, + ...envs, + }, + }; + const result = spawnSync(process.execPath, args, options); + if (result.signal) { + process.kill(0, result.signal); + } else { + process.exit(result.status); } } } @@ -912,7 +947,7 @@ const common = { mustSucceed, nodeProcessAborted, PIPE, - parseTestFlags, + parseTestMetadata, platformTimeout, printSkipMessage, pwdCommand, diff --git a/test/common/index.mjs b/test/common/index.mjs index 51f4bb222b6b81..2c752db65e7ac4 100644 --- a/test/common/index.mjs +++ b/test/common/index.mjs @@ -36,7 +36,7 @@ const { mustNotMutateObjectDeep, mustSucceed, nodeProcessAborted, - parseTestFlags, + parseTestMetadata, PIPE, platformTimeout, printSkipMessage, @@ -86,7 +86,7 @@ export { mustNotMutateObjectDeep, mustSucceed, nodeProcessAborted, - parseTestFlags, + parseTestMetadata, PIPE, platformTimeout, printSkipMessage, diff --git a/test/common/websocket-server.js b/test/common/websocket-server.js new file mode 100644 index 00000000000000..a3b12ab8d30d49 --- /dev/null +++ b/test/common/websocket-server.js @@ -0,0 +1,105 @@ +'use strict'; +const common = require('./index'); +if (!common.hasCrypto) + common.skip('missing crypto'); +const http = require('http'); +const crypto = require('crypto'); + +class WebSocketServer { + constructor({ + port = 0, + }) { + this.port = port; + this.server = http.createServer(); + this.clients = new Set(); + + this.server.on('upgrade', this.handleUpgrade.bind(this)); + } + + start() { + return new Promise((resolve) => { + this.server.listen(this.port, () => { + this.port = this.server.address().port; + resolve(); + }); + }).catch((err) => { + console.error('Failed to start WebSocket server:', err); + }); + } + + handleUpgrade(req, socket, head) { + const key = req.headers['sec-websocket-key']; + const acceptKey = this.generateAcceptValue(key); + const responseHeaders = [ + 'HTTP/1.1 101 Switching Protocols', + 'Upgrade: websocket', + 'Connection: Upgrade', + `Sec-WebSocket-Accept: ${acceptKey}`, + ]; + + socket.write(responseHeaders.join('\r\n') + '\r\n\r\n'); + this.clients.add(socket); + + socket.on('data', (buffer) => { + const opcode = buffer[0] & 0x0f; + + if (opcode === 0x8) { + socket.end(); + this.clients.delete(socket); + return; + } + + socket.write(this.encodeMessage('Hello from server!')); + }); + + socket.on('close', () => { + this.clients.delete(socket); + }); + + socket.on('error', (err) => { + console.error('Socket error:', err); + this.clients.delete(socket); + }); + } + + generateAcceptValue(secWebSocketKey) { + return crypto + .createHash('sha1') + .update(secWebSocketKey + '258EAFA5-E914-47DA-95CA-C5AB0DC85B11', 'binary') + .digest('base64'); + } + + decodeMessage(buffer) { + const secondByte = buffer[1]; + const length = secondByte & 127; + const maskStart = 2; + const dataStart = maskStart + 4; + const masks = buffer.slice(maskStart, dataStart); + const data = buffer.slice(dataStart, dataStart + length); + const result = Buffer.alloc(length); + + for (let i = 0; i < length; i++) { + result[i] = data[i] ^ masks[i % 4]; + } + + return result.toString(); + } + + encodeMessage(message) { + const msgBuffer = Buffer.from(message); + const length = msgBuffer.length; + const frame = [0x81]; + + if (length < 126) { + frame.push(length); + } else if (length < 65536) { + frame.push(126, (length >> 8) & 0xff, length & 0xff); + } else { + throw new Error('Message too long'); + } + + return Buffer.concat([Buffer.from(frame), msgBuffer]); + } +} + +module.exports = WebSocketServer; diff --git a/test/es-module/test-esm-error-cache.js b/test/es-module/test-esm-error-cache.js index d780f1a22164d7..e39ddb895acc1e 100644 --- a/test/es-module/test-esm-error-cache.js +++ b/test/es-module/test-esm-error-cache.js @@ -19,7 +19,9 @@ let error; await assert.rejects( () => import(file), (e) => { - assert.strictEqual(error, e); + // The module may be compiled again and a new SyntaxError would be thrown but + // with the same content. + assert.deepStrictEqual(error, e); return true; } ); diff --git a/test/es-module/test-esm-import-attributes-errors.js b/test/es-module/test-esm-import-attributes-errors.js index 63513a51e5abb5..9789484be986b3 100644 --- a/test/es-module/test-esm-import-attributes-errors.js +++ b/test/es-module/test-esm-import-attributes-errors.js @@ -28,7 +28,7 @@ async function test() { await rejects( import(jsModuleDataUrl, { with: { type: 'json', other: 'unsupported' } }), - { code: 'ERR_IMPORT_ATTRIBUTE_TYPE_INCOMPATIBLE' } + { code: 'ERR_IMPORT_ATTRIBUTE_UNSUPPORTED' } ); await rejects( @@ -48,7 +48,7 @@ async function test() { await rejects( import(jsonModuleDataUrl, { with: { foo: 'bar' } }), - { code: 'ERR_IMPORT_ATTRIBUTE_MISSING' } + { code: 'ERR_IMPORT_ATTRIBUTE_UNSUPPORTED' } ); await rejects( diff --git a/test/es-module/test-esm-import-attributes-errors.mjs b/test/es-module/test-esm-import-attributes-errors.mjs index d7ffc6f92c99ea..8095662b104135 100644 --- a/test/es-module/test-esm-import-attributes-errors.mjs +++ b/test/es-module/test-esm-import-attributes-errors.mjs @@ -23,7 +23,7 @@ await rejects( await rejects( import(jsModuleDataUrl, { with: { type: 'json', other: 'unsupported' } }), - { code: 'ERR_IMPORT_ATTRIBUTE_TYPE_INCOMPATIBLE' } + { code: 'ERR_IMPORT_ATTRIBUTE_UNSUPPORTED' } ); await rejects( @@ -43,7 +43,7 @@ await rejects( await rejects( import(jsonModuleDataUrl, { with: { foo: 'bar' } }), - { code: 'ERR_IMPORT_ATTRIBUTE_MISSING' } + { code: 'ERR_IMPORT_ATTRIBUTE_UNSUPPORTED' } ); await rejects( diff --git a/test/es-module/test-esm-tla-syntax-errors-not-recognized-as-tla-error.mjs b/test/es-module/test-esm-tla-syntax-errors-not-recognized-as-tla-error.mjs new file mode 100644 index 00000000000000..f6d1ca962c94a3 --- /dev/null +++ b/test/es-module/test-esm-tla-syntax-errors-not-recognized-as-tla-error.mjs @@ -0,0 +1,78 @@ +import { spawnPromisified } from '../common/index.mjs'; +import { describe, it } from 'node:test'; +import { strictEqual, match } from 'node:assert'; + +describe('unusual top-level await syntax errors', () => { + const expressions = [ + // string + { expression: '""' }, + // number + { expression: '0' }, + // boolean + { expression: 'true' }, + // null + { expression: 'null' }, + // undefined + { expression: 'undefined' }, + // object + { expression: '{}' }, + // array + { expression: '[]' }, + // new + { expression: 'new Date()' }, + // identifier + { initialize: 'const a = 2;', expression: 'a' }, + ]; + it('should not crash the process', async () => { + for (const { expression, initialize } of expressions) { + const wrapperExpressions = [ + `function callAwait() {}; callAwait(await ${expression});`, + `if (await ${expression}) {}`, + `{ key: await ${expression} }`, + `[await ${expression}]`, + `(await ${expression})`, + ]; + for (const wrapperExpression of wrapperExpressions) { + const { code, signal, stdout, stderr } = await spawnPromisified(process.execPath, [ + '--eval', + ` + ${initialize || ''} + ${wrapperExpression} + `, + ]); + + strictEqual(stderr, ''); + strictEqual(stdout, ''); + strictEqual(code, 0); + strictEqual(signal, null); + } + } + }); + + it('should throw the error for unrelated syntax errors', async () => { + const expression = 'foo bar'; + const wrapperExpressions = [ + [`function callSyntaxError() {}; callSyntaxError(${expression});`, /missing \) after argument list/], + [`if (${expression}) {}`, /Unexpected identifier/], + [`{ key: ${expression} }`, /Unexpected identifier/], + [`[${expression}]`, /Unexpected identifier/], + [`(${expression})`, /Unexpected identifier/], + [`const ${expression} = 1;`, /Missing initializer in const declaration/], + [`console.log('PI: ' Math.PI);`, /missing \) after argument list/], + [`callAwait(await "" "");`, /missing \) after argument list/], + ]; + + for (const [wrapperExpression, error] of wrapperExpressions) { + const { code, signal, stdout, stderr } = await spawnPromisified(process.execPath, [ + '--eval', + ` + ${wrapperExpression} + `, + ]); + match(stderr, error); + strictEqual(stdout, ''); + strictEqual(code, 1); + strictEqual(signal, null); + } + }); +}); diff --git a/test/fixtures/crypto/aes_ocb.js b/test/fixtures/crypto/aes_ocb.js new file mode 100644 index 00000000000000..add970d478e2da --- /dev/null +++ b/test/fixtures/crypto/aes_ocb.js @@ -0,0 +1,124 @@ +'use strict'; + +module.exports = function() { + const kPlaintext = + Buffer.from('546869732073706563696669636174696f6e206465736372696265' + + '732061204a6176615363726970742041504920666f722070657266' + + '6f726d696e672062617369632063727970746f6772617068696320' + + '6f7065726174696f6e7320696e20776562206170706c6963617469' + + '6f6e732c20737563682061732068617368696e672c207369676e61' + + '747572652067656e65726174696f6e20616e642076657269666963' + + '6174696f6e2c20616e6420656e6372797074696f6e20616e642064' + + '656372797074696f6e2e204164646974696f6e616c6c792c206974' + + '2064657363726962657320616e2041504920666f72206170706c69' + + '636174696f6e7320746f2067656e657261746520616e642f6f7220' + + '6d616e61676520746865206b6579696e67206d6174657269616c20' + + '6e656365737361727920746f20706572666f726d20746865736520' + + '6f7065726174696f6e732e205573657320666f7220746869732041' + + '50492072616e67652066726f6d2075736572206f72207365727669' + + '63652061757468656e7469636174696f6e2c20646f63756d656e74' + + '206f7220636f6465207369676e696e672c20616e64207468652063' + + '6f6e666964656e7469616c69747920616e6420696e746567726974' + + '79206f6620636f6d6d756e69636174696f6e732e', 'hex'); + + const kKeyBytes = { + '128': Buffer.from('dec0d4fcbf3c4741c892dabd1cd4c04e', 'hex'), + '256': Buffer.from('67693823fb1d58073f91ece9cc3af910e5532616a4d27b1' + + '3eb7b74d8000bbf30', 'hex') + } + + const iv = Buffer.from('3a92732aa6ea39bf3986e0c73fa920', 'hex'); + + const additionalData = Buffer.from( + '5468657265206172652037206675727468657220656469746f72696' + + '16c206e6f74657320696e2074686520646f63756d656e742e', 'hex'); + + const vectorData = { + '128': { + '64': { + ciphertext: Buffer.from('4680d176c2fa66ef4376bc013ca5435ebd27b260c1236ae0148eb84eb24869ec1f1ebba2ba5356a2ee36944e717f668ab180c94817058216930d0192f403652bd2b0f3adac6466a74a69a8676d8460e2d81811de0cf8c0ec0c1aea48d470d0b6818fffb30dcdba67ffcf4bcf62e241e853c04370014cbea9cd68de4b90f8e52b5d40e972df70104fb70a78ddff9e7eb6e0c528c52aca9738030a6ad253d042697de254a059d06606ce718e8c95afd35767d05640b11367c5de4be405dd0c0bbbff54c8adfdae259b6588a44af382b3c5a2dec4c91bc8c3c156ae4859bd95e1a12f13fd292e0e80de25267941b8c5974e53dcff3741211d9c9e312919283f625201b201bb208f341b792d50c26b3c5769107e28c694ee55396a92b8ef18f6aa5849e44f63da4ab7d6d27d0b7c0869be21c650049dba5c3691de3fdc0dc9cd9676857d35d924372487e87c5ce4d656f69ee0cd62edbd949db134f9850eb6f017d5ba1933e8a39e56822fbe6a35eb9590e28bd1bbd46217c2db14264518caad1929885c143d28f4274fb4a655de0e24b2f37f1351c4820cb5c4fe49e9433f28bc1a0ac63a52200ac0876471c4db9a7ef1852b679f8a1d9bd54e739ce642bdfca700ed162516a33798733b52b726376e10f840714109150c7afeb71c652970ea86', 'hex'), + tagWithAD: Buffer.from('cd1d3fa016ffebf8', 'hex'), + tagWithoutAD: Buffer.from('85917be096f12614', 'hex') + }, + '96': { + ciphertext: Buffer.from('165e7cb1789fc9cb1e9b81e48d2c30d22a019bce5ff79d45ea7adbcf585b7bfc015a5959c9c1478714f4621ee0675f785a1689f1a9254b76580d368cc4a02b18b3f2a8abb5173e2b9f27042af4c0daeae44d88679e7bb79cab48a8f100804c0dad11547c68f2ac0e9a74f1abdeaa7c95e12b97361c4217905f25a03d9a5f8982af979b7756768cd9b044cc928d25ccd56e4fc494e4f62d96aabf3a4bd4889478990e58dcc180c4a81aceaf93afbbcf866a47030d579a981e42d78fae1907df32fd6c8cb37e1bd12e9ac7e81636e411e1717dac7836cf35b2683dd055fd0032a37d048835ef977b381d282ebb4c743eb09126d37764bd177af48d40f0c50534484dfd23ca9d046be673f493a83f705bd3a7d6579814690ee936095f1d80175271f33832ce9d93fff24d4c4ac3fbfa5e12b57109a56fdd5fa302391fe561095dafa4e41ce8e6dc5aac6091aefd7ca3b694ff6301ffdbe02c0c2ce438101ee92a08f85f3b153aa3116a80bc7778040ed9ee8b408909fc6d86004f23798ae85d9b1957435c9f74becdc53b38a7f0b9ac3d515e17d0ca9f5874096db5fb234d0d45e8149f6d15e2d7d3138622fa6fa7eded639fb6929fcaacf03060ec9db0106e58a3fa45d9ab2f6a2b56eee39cfc8cb305901c8f612e24da4cd3b07d4cf966cdf1', 'hex'), + tagWithAD: Buffer.from('80b55f5111770c4fd51ae2a1', 'hex'), + tagWithoutAD: Buffer.from('c8391b119179c1a3c534f03b', 'hex') + }, + '128': { + ciphertext: Buffer.from('0e33334c6fd3cf8c371d06875f342d239832a94c43b2f721d8bd70b6d62e7ac34bfd2041b214cd77624e0330e0892abfd696577144205882a24a1f4f0234602503222558c8c0e7dd033c3888d7f747107d3b11ad3f4d2c6088a80413d12a83587503a7393022ec541b284b358fa1fe3236ae706cd49fb8e2d4216318e8659275d80616940d2f3762e672a19ece3f2ee918c4e99b173c544dfb3300a867564790a436967563fa2bd3240dbb4d370d9153110411d772ff7542651db8c38672cc0f0ceae4f24065dfc996dd8b8d915f1bce206878ac54fad4df8a8157af6f1a8dc0344f526cd6cc398e1f049af3af9334204c5025a653292c0db11985ab83acf1bce2879754c0684a40d7e64e1f062a5d586a7c8702f326119dec9b1d0d316f8ba93f63d07546ee796db70fa66738499126c3a4bdada811dfc698b96569fbfcb935059c9b80349ce2b5caf6def0f2f6ba0d8ebf3395bb1766cddbc93a946d9706342bc378cda55eaee8edb411314c73bb2c480dea05e2eab5f83d089624bc9884dd14ba714d62e15767f730782e37c519b608d8d4ccee98e6d4ba28171417753f72a3b476403ccd5f0bbc4d7021170c751f8d844ee58ce6d2558270333d26e14e184d07e46a22d9270258517c7d6fa55875642d07a74ae0056c41e2931ef08d3f', 'hex'), + tagWithAD: Buffer.from('cdee14358fba08d170fe5906ab34a56f', 'hex'), + tagWithoutAD: Buffer.from('856250750fb4c53d60d04b9cb18534ba', 'hex') + } + }, + '256': { + '64': { + ciphertext: Buffer.from('188ee89ebee501f6a1dd111aa09b00eb67e1b2c6e1f205737d7f47e2cca0b80c9408daccfe820ebeab75f290589dc2175039d60c891002dea5214237393a5672bf91403d69fe41122b666fdd4b796ca18eb84a219895a58ca91689758dfc578079f89f27016a3b3080d0d8177a5ed8fc4b6e0af40604eaf4d91125aeac6656277a429c120bd9a2fa73086eb93302e3cc4d31dd2433d2f07cfbf604e60e380b7f94fb9de182f9752664c57dcb5c9797a952cc8a27b88a582342747c84bdcaae7ddfce460ab681856432429c6cc6e3658929f5669d5088a123a9158b680a8601960b068ea5a7b8f9bac98c3b7399c8fb8067ecbf6313606afdc3d1528d048b6803e12bdb44b119a2107463d01db4bc2791df8f3d0761ce5b401f8e0383f279fe7b1af335b10b48bef05d0e91bdab9631fa79a67a03a4790b4b1d325be028f6beda26d68958811670c86050d05b745f399ac77ff97be3b91cff64fd15e5047b1698c2dffe6d3d7c6cb0c8b5956cee43e8ecf7bc22199bfd7d61178843f9554bc0db539e7a59001ba3c963f299a1d838b8629bddc7c5646145a86ce52077af7785d213c787640b2010424b73b7b786ac7ca946fff501fecd6793ac00e9fb10ce7fb3a6d7b277b7096c83c94a421747f63c43723dd098b144549edbaeda4536ded1', 'hex'), + tagWithAD: Buffer.from('dd2a37097d430860', 'hex'), + tagWithoutAD: Buffer.from('4aefbda08fd14436', 'hex') + }, + '96': { + ciphertext: Buffer.from('18da5c8e77a1fa3cbe6c510c594b905f027ba3b56e446f23bbc17d12f265f260799ec5531792b8ec5cfec2b660bef94760ff5e7a714947cb342a7e9a1e0f3085e7c1608ea9dd1c71507cf09a7683e855b664615702cb556553319b5bd0fcb600d5c3a3bb8c36d8014f30b85a2d26ac36e83cfe8cebb6f7118c3e5875597fa39efd269f5ea237edd60036a906ea592fbe2868455503e9b1928396a894fefe2321138e8f5df4fcc932f4f05f3c15cd16cd5da9d54399dd0a90448f12c54a288b1724b60dfb7a434e72e357ddb631c1038efe9331a5037eb98e61f2a69df51d17de14799df035b5e468782d95d58d0cfd68d69c1b1529b4ad191bbf6f4c10f8a85e2c4ac7c525b3ef258de9a2f9b6193d74ca6e0180333167de426039cf8490d15e7f8ea86bfd143f98f2bb8e5c32a17e19aa0370cc7cbad8cfaeb69be29b6a6a7c27354a9a0dae13258578c681d182855c917c300e96912d24a80db1824e719fd5bddafd839f67fe18f632a892e69e46e0bef56aa94ff26ab7943a4b7de052f5d24302feb3add1c481b019d0d97bd442bd7c0021721d13b9e471686409c7c69551b3977ed3505eff8213e5564d1afe6042dc1ea572aca30cd1b7540e954b2e6c0498ebd2526fb0fb5ffcf48c6ef23b385e99649fd49290e0ce4de6d0a57498b7', 'hex'), + tagWithAD: Buffer.from('06a22a5a7776797981c5ee6d', 'hex'), + tagWithoutAD: Buffer.from('9167a0f385e4352f4c97aa94', 'hex') + }, + '128': { + ciphertext: Buffer.from('4604fafe03ab3897dd54e41c5884cac385d575768a2eb1c4c21c8470636a62565309605c8a556c4f3837be13df6f02de9b5a1ebbedba07e34a1fe0cd037eefc5c324ddfd95a9d16eaebecafd5b93d3c8d9a2deadb079497437d120c1fb0ae4b4d60b9fe220486c54f49c23c00fc3ffb1d57022761315c51f609fb28d70e4de2479325851af9c71671b6507c81fdecc5e5b8335b9d78320b0a4dfdff2f4a0dc86401128fdbe5601491acc6b0876d72fa842e95b75626dde15e602593b82874ed9233ddc64b06c41ea25dcccd678eb720d10d1c85f17635aceef2f102706f6de89b6d0fe6dcd686677d0a682fa3bf781a1fdb13c506be5b1c46ead578c54161129dbe0763d897fde4bfaf87ba61c5cf6884bd1e75678c086aeb2fcf057faf14ec38492ecba850595fa5b84d66c07576486da9cff68dbd961872985b1094d23f9dc31dda35cbe68ee570323843374cb89e07d2f11adf3476e6bdc2b2525cffdffcb7ee58190b19d8601b73d175bd8ebda079c97ed36e77c09a7c1c5e48f57c1881b91e2b17b6a737c79a1528c90ffbc1504914677593b9f6eca64fad08cdd4d318cd7f163cdf325667e949d829bebcccd932481ef132b49ac156eef34947924c165ce64b9e1e1461bd8d3d1e4e928411b448faa5d7db7f8bddc4fdce1ea035d60', 'hex'), + tagWithAD: Buffer.from('a36db50a9235d475609287268818792c', 'hex'), + tagWithoutAD: Buffer.from('34a83fa360a79823adc0c3dfc8e64d20', 'hex') + } + } + }; + + const kKeyLengths = [128, 256]; + const kTagLengths = [64, 96, 128]; + + const passing = []; + kKeyLengths.forEach((keyLength) => { + kTagLengths.forEach((tagLength) => { + const byteCount = tagLength / 8; + const data = vectorData[keyLength][tagLength]; + + // With additional data + const result = new Uint8Array(data.ciphertext.byteLength + byteCount); + result.set(data.ciphertext, 0); + result.set(data.tagWithAD.slice(0, byteCount), data.ciphertext.byteLength); + passing.push({ + keyBuffer: kKeyBytes[keyLength], + algorithm: { name: 'AES-OCB', iv, additionalData, tagLength }, + plaintext: kPlaintext, + result + }); + + // Without additional data + const noadresult = new Uint8Array(data.ciphertext.byteLength + byteCount); + noadresult.set(data.ciphertext, 0); + noadresult.set(data.tagWithoutAD.slice(0, byteCount), data.ciphertext.byteLength); + passing.push({ + keyBuffer: kKeyBytes[keyLength], + algorithm: { name: 'AES-OCB', iv, tagLength }, + plaintext: kPlaintext, + result: noadresult + }); + }); + }); + + const failing = []; + kKeyLengths.forEach((keyLength) => { + [24, 48, 72, 95, 129].forEach((badTagLength) => { + failing.push({ + keyBuffer: kKeyBytes[keyLength], + algorithm: { + name: 'AES-OCB', + iv, + additionalData, + tagLength: badTagLength + }, + plaintext: kPlaintext, + result: vectorData[keyLength]['128'].ciphertext, + }); + }); + }); + + return { passing, failing, decryptionFailing: [] }; +}; diff --git a/test/fixtures/crypto/chacha20_poly1305.js b/test/fixtures/crypto/chacha20_poly1305.js new file mode 100644 index 00000000000000..a43edbf6f96d99 --- /dev/null +++ b/test/fixtures/crypto/chacha20_poly1305.js @@ -0,0 +1,102 @@ +'use strict'; + +module.exports = function() { + const kPlaintext = + Buffer.from('546869732073706563696669636174696f6e206465736372696265' + + '732061204a6176615363726970742041504920666f722070657266' + + '6f726d696e672062617369632063727970746f6772617068696320' + + '6f7065726174696f6e7320696e20776562206170706c6963617469' + + '6f6e732c20737563682061732068617368696e672c207369676e61' + + '747572652067656e65726174696f6e20616e642076657269666963' + + '6174696f6e2c20616e6420656e6372797074696f6e20616e642064' + + '656372797074696f6e2e204164646974696f6e616c6c792c206974' + + '2064657363726962657320616e2041504920666f72206170706c69' + + '636174696f6e7320746f2067656e657261746520616e642f6f7220' + + '6d616e61676520746865206b6579696e67206d6174657269616c20' + + '6e656365737361727920746f20706572666f726d20746865736520' + + '6f7065726174696f6e732e205573657320666f7220746869732041' + + '50492072616e67652066726f6d2075736572206f72207365727669' + + '63652061757468656e7469636174696f6e2c20646f63756d656e74' + + '206f7220636f6465207369676e696e672c20616e64207468652063' + + '6f6e666964656e7469616c69747920616e6420696e746567726974' + + '79206f6620636f6d6d756e69636174696f6e732e', 'hex'); + + const kKeyBytes = Buffer.from('67693823fb1d58073f91ece9cc3af910e5532616a4d27b1' + + '3eb7b74d8000bbf30', 'hex') + + const iv = Buffer.from('3a92732aa6ea39bf3986e0c73', 'hex'); + + const additionalData = Buffer.from( + '5468657265206172652037206675727468657220656469746f72696' + + '16c206e6f74657320696e2074686520646f63756d656e742e', 'hex'); + + const tag = Buffer.from('87d611a2b8012f4eb792ccdee7998d22', 'hex') + + const tag_with_empty_ad = Buffer.from('2ba3e8380c1f49f10665fd15a4ac599e', 'hex') + + const kCiphertext = Buffer.from( + '01e15951ce23d7672df9d13f19c54ff5b3fe17114eb637ec25c1a8ac2' + + '24eebe154b3a1206187e18abd31d022b1a66551fbbf0ae2d9fa4e9ab4' + + 'a680b185528000a7654731f05f405ce164cfc904d1759afa758ac459f' + + 'e26420fccac9692af9259243f53e7e0f42d56c6a4b4827056ca76bc9d' + + 'e92577a9f405810fd1e4cb7289f7d528772bde654fef456f031b87802' + + '6616df7349bef4fdff4a52953afadddbd61a3bd1a43815daf1b1ab962' + + '8aaeaee52866466dcb45650b489b2226a01da24d85c20af24e2beb790' + + '233081c5651258cf77e5c47e87ac070aeaa470d13b28b4df82729c350' + + '3cd80a65ac50a8d7a10dabe29ac696410b70209064c3b698343f97f5a' + + '38d63265504ee0922cf5a7c03fe0f3ac1fce28f8eed0153d2f6c500ef' + + '68c71e56e3f1abbfc194be4dd75b73983c3e7c0c68555b71eb4695110' + + 'bb8cd8f495ce7c1e4512c72fca23a095897a9a0dfd584abc3e949cf3e' + + '0fa1d855284d74a915b6e7455e0307985a356c01878700b21c6e0afac' + + 'ee72021a81c3164193e0126d5b841018da2c7c9aa0afb8cd746b378e3' + + '04590eb8b0428b4409b7bcb0cb4a5e9072bb693f011edbe9ab6c5e0c5' + + 'ca51f344fb29034cdbe78b3b66d23467a75e5d28f7e7c92e4e7246ba0' + + 'db7aa408efa3b33e57a4d67fda86d346fc690f07981631', 'hex'); + + const kTagLengths = [128]; + + const passing = []; + kTagLengths.forEach((tagLength) => { + const byteCount = tagLength / 8; + const result = + new Uint8Array(kCiphertext.byteLength + byteCount); + result.set(kCiphertext, 0); + result.set(tag.slice(0, byteCount), + kCiphertext.byteLength); + passing.push({ + keyBuffer: kKeyBytes, + algorithm: { name: 'ChaCha20-Poly1305', iv, additionalData, tagLength }, + plaintext: kPlaintext, + result + }); + + const noadresult = + new Uint8Array(kCiphertext.byteLength + byteCount); + noadresult.set(kCiphertext, 0); + noadresult.set(tag_with_empty_ad.slice(0, byteCount), + kCiphertext.byteLength); + passing.push({ + keyBuffer: kKeyBytes, + algorithm: { name: 'ChaCha20-Poly1305', iv, tagLength }, + plaintext: kPlaintext, + result: noadresult + }); + }); + + const failing = []; + [24, 48, 72, 95, 129].forEach((badTagLength) => { + failing.push({ + keyBuffer: kKeyBytes, + algorithm: { + name: 'ChaCha20-Poly1305', + iv, + additionalData, + tagLength: badTagLength + }, + plaintext: kPlaintext, + result: kCiphertext + }); + }); + + return { passing, failing, decryptionFailing: [] }; +}; diff --git a/test/fixtures/crypto/ecdsa.js b/test/fixtures/crypto/ecdsa.js index 4b3539edb1fc89..b8827b24d41965 100644 --- a/test/fixtures/crypto/ecdsa.js +++ b/test/fixtures/crypto/ecdsa.js @@ -2,6 +2,12 @@ module.exports = function() { const pkcs8 = { + 'P-256': Buffer.from( + '308187020100301306072a8648ce3d020106082a8648ce3d030107046d306b020101' + + '04205119f596e17f3c55a170b674c96e6bea7269dc9240047c76c841ff49106c4989' + + 'a14403420004b1d1e7da708dfadf90bc013a4009184bdb3f9065078f5598f6ad2638' + + '65a387e249ebf1a514ad8c943635a66d8acd64ebf2c876e55448813f10026a5e1f0a' + + '9817', 'hex'), 'P-384': Buffer.from( '3081b6020100301006072a8648ce3d020106052b8104002204819e30819b02010104' + '3002a9a0d899efa87e7564110907e9d82c21bd6265a37abd9a6fdb0f80ec844dd3a1' + @@ -21,6 +27,10 @@ module.exports = function() { } const spki = { + 'P-256': Buffer.from( + '3059301306072a8648ce3d020106082a8648ce3d03010703420004b1d1e7da708dfa' + + 'df90bc013a4009184bdb3f9065078f5598f6ad263865a387e249ebf1a514ad8c9436' + + '35a66d8acd64ebf2c876e55448813f10026a5e1f0a9817', 'hex'), 'P-384': Buffer.from( '3076301006072a8648ce3d020106052b81040022036200041d319d692dca5f5754ba' + '7b32c11642c6d8d2b4fb8249c3f214d71e90b5252966d97f7beb1faab1e4f3e26055' + @@ -45,6 +55,36 @@ module.exports = function() { // For verification tests. const signatures = { + 'P-256': { + 'SHA-1': Buffer.from( + 'a6f9548fa945bca4ce6e41d4099623b409e21070c03179161867d3ca411ee4f39e' + + '51ba999723e609d0abb2cc48450e886544ca400bae09841651211b43907672', + 'hex'), + 'SHA-256': Buffer.from( + '15f4adec59122298cd1642ee9104748c705dc6a3f70ed8222a52ee0420a35ce4c8' + + '293db29689acf24f6009b98df0cb8ec1aab17f8ad448a8c0e86843dfa824a3', + 'hex'), + 'SHA-384': Buffer.from( + 'b860be71578d07ad137c2a75ac29528114b23f58021b2c2875ac1374ed3143a928' + + '4efd9402b950cfc738fc9df4e33d917594f2078b96a02d5fbf17efe94e72a6', + 'hex'), + 'SHA-512': Buffer.from( + 'b6a0a14d7e4bc6dd2eda82c9234f174b670b60c8f7d101f68fdf5889e02373b025' + + 'dcbc4c82f2929b8e06c68535da98e38fe399c53a814b097935581ef21535eb', + 'hex'), + 'SHA3-256': Buffer.from( + 'f6a48eb5557f484ed0c3e4b5c78a3cf497cbd346db06a4165d429248aa2cc51a69' + + '747d09f57af145469a8b607a9b8b9709629d74e8f5ca337c6ddc581b6f6103', + 'hex'), + 'SHA3-384': Buffer.from( + '777785978eb59da32888554dc7fd62d1ba1a3033cddaa8c36b8f3dcea8f85e1c8e' + + '6db26f509747bd144dfa9436784bf4abbcaa6abcf1ecc09cea3b921d46738c', + 'hex'), + 'SHA3-512': Buffer.from( + '0f01c2083b5dd7fccb2784563f88cd9a815d570a1690695e426643ab725780760d' + + 'e972e26e18d67f5557be89f17b4cd0065ce2937de299bdb2e972ebf7635084', + 'hex') + }, 'P-384': { 'SHA-1': Buffer.from( '65fe070ec3eac35250d00b9ee6db4d2dadd5f3bbb9c495c8671d2a0d2b99149fb2' + @@ -61,7 +101,19 @@ module.exports = function() { 'SHA-512': Buffer.from( '72fbdb369fd34c1c54264d07f4facd69b02e4206f8a8bb259b882a305c56fde2d3' + '5107e493c53cd6b4af0b31306f4d03fd43cfc762a1030e17a3d775453a1212b142' + - '9f7b3d93066a5f42a10b138cd177dc09616e827d598822d78d4627b754e6', 'hex') + '9f7b3d93066a5f42a10b138cd177dc09616e827d598822d78d4627b754e6', 'hex'), + 'SHA3-256': Buffer.from( + '0b07c078be30fa5925a307d6fc559c5f398e63fb5d007d6b24a834847f2d3d18d5' + + 'b5e840711c52a7bc6626c3ced93301e873c013a706f6b297c12cc6d47a71e0529e' + + '719f43957de9995621d3cb0217469adaa6fd3135470771d0aa9d05d7a9c6', 'hex'), + 'SHA3-384': Buffer.from( + '2f36e8b04af46f68ef900c2720e3518b06f5440865d44072bbad5d62288c575042' + + 'b183a372acd70328c738668dcecb9866801462d62df3c35450fdc6c95433103fcd' + + 'c77999b640e3f92bd4e9be6e27ab129d1bc4f0b2a4c829388666920892d3', 'hex'), + 'SHA3-512': Buffer.from( + '32a951e886c33ac57a008efe9643bc92aa3ece9521d115e0c7240caecf124d1f7c' + + 'dcba7fabb9ad5202e04f7aa591ab01ed3f060f04f493e4f24430fe8159200612f0' + + '2849108b8be6edc8494c328097ad9265928efe5cb9d91be2f013ee17ee4e', 'hex') }, 'P-521': { 'SHA-1': Buffer.from( @@ -87,12 +139,30 @@ module.exports = function() { '5ae23cfcca0aad78f6b6dee6b4718b95d0d1a715aa3378470e50b516c18e0f3305' + '01f0071e6a32867fa70f695cd39c4e87e142b9e4134d38740bd6fee354a575167e' + '13524e94832637910fe11e53a85fb21b91adb81bb1779c4e2b8bc87c717dc35084', + 'hex'), + 'SHA3-256': Buffer.from( + '00463679f47a4c705e03447360dcf34d1743e0d4b2591cc66832a6bc80d92e538c' + + '169a1fd330f98e7235ca7fec7e16ac44fb13095b8edf2c76b75c4845177d59e425' + + '0127c4359f6a4c9ccb63e7a9ff8122c0b4a8b7408e28c96817ecc3baf8c559c413' + + 'c3bb580447dec9f52139b2afde369cd51730f050bc94137556ae137f0509464219', + 'hex'), + 'SHA3-384': Buffer.from( + '01969a4db0888bc067a68a31fe5d0fc97e0b701f570565f7b25cb27707c6f020ff' + + '680f8553ec5c2d6885e9e91b39262ed1bde375525eb13fdf12089b7939c7689735' + + '0101c8b8d1129a217e8e956bef78cf7b9a0458523b04ac8e0b84ce73d54326f7a8' + + '704ee42fe183f3ef79d83e676f34dc5476e2342641a5b973d3d94e8503676fbbc5', + 'hex'), + 'SHA3-512': Buffer.from( + '000f362e914ee0136663cf57bf4085c25604af6dc198b4818751e1195ee7e41a16' + + '91be909dcbc2bae00b8917f6bb918eae3740ac1b76e0913137c2da1171d6400b55' + + '01ec6e1dc5987a27fe16fc2ce5c8e954088f898a9bbefb176eaa8bbd9ccc264c4c' + + 'cc38c83ac8b5a168f90228daf8405a2b9bf7829c263a646b4e1098e2ace38deec7', 'hex') } } - const curves = ['P-384', 'P-521']; - const hashes = ['SHA-1', 'SHA-256', 'SHA-384', 'SHA-512']; + const curves = ['P-256', 'P-384', 'P-521']; + const hashes = ['SHA-1', 'SHA-256', 'SHA-384', 'SHA-512', 'SHA3-256', 'SHA3-384', 'SHA3-512']; const vectors = []; curves.forEach((namedCurve) => { diff --git a/test/fixtures/crypto/eddsa.js b/test/fixtures/crypto/eddsa.js index 8b1a5ce1c5a45d..f752d0d4427732 100644 --- a/test/fixtures/crypto/eddsa.js +++ b/test/fixtures/crypto/eddsa.js @@ -1,5 +1,7 @@ 'use strict'; +const common = require('../../common'); + module.exports = function() { const pkcs8 = { 'Ed25519': Buffer.from( @@ -37,7 +39,13 @@ module.exports = function() { '025a2a5a572b9d23b0642f00', 'hex') } - const algorithms = ['Ed25519', 'Ed448']; + const algorithms = ['Ed25519']; + + if (!process.features.openssl_is_boringssl) { + algorithms.push('Ed448') + } else { + common.printSkipMessage(`Skipping unsupported Ed448 test cases`); + } const vectors = algorithms.map((algorithm) => ({ publicKeyBuffer: spki[algorithm], diff --git a/test/fixtures/crypto/hmac.js b/test/fixtures/crypto/hmac.js index c4942976adf49b..6505c6e2ae55b5 100644 --- a/test/fixtures/crypto/hmac.js +++ b/test/fixtures/crypto/hmac.js @@ -19,6 +19,16 @@ module.exports = function () { '6b1da28eab1f582ad9718effe05e23d5fd2c9877a2d9443f90bec093bece2ea7' + 'd2354cd0bdc5e147d2e9009373494488', 'hex'), 'SHA-512': Buffer.from( + '5dcc359443aaf652fa1375d6b3e61fdcf29bb4a28bd5d3dcfa40f82f906bb280' + + '0455db03b5d31fb972a15a6d0103a24e56d156a119c0e5a1e92a44c3c5657cf9', + 'hex'), + 'SHA3-256': Buffer.from( + 'e588ec0811463d767241df1074b47ae4071b51f2ce36537ba69ccdc3fdc2b7a8', + 'hex'), + 'SHA3-384': Buffer.from( + '6b1da28eab1f582ad9718effe05e23d5fd2c9877a2d9443f90bec093bece2ea7' + + 'd2354cd0bdc5e147d2e9009373494488', 'hex'), + 'SHA3-512': Buffer.from( '5dcc359443aaf652fa1375d6b3e61fdcf29bb4a28bd5d3dcfa40f82f906bb280' + '0455db03b5d31fb972a15a6d0103a24e56d156a119c0e5a1e92a44c3c5657cf9', 'hex') @@ -35,6 +45,16 @@ module.exports = function () { 'SHA-512': Buffer.from( '61fb278c3ffb0cce2bf1cf723ddfd8ef1f931c0c618c25907324605939e3f9a2' + 'c6f4af690bda3407dc2f5770f6a0a44b954d64a332e3ee0821abf82b7f3e99c1', + 'hex'), + 'SHA3-256': Buffer.from( + 'c1ac5e11fcd50c48bf567f6e296632f5801c4eb07a8a47579b41dee971a3099b', + 'hex'), + 'SHA3-384': Buffer.from( + 'ac8c97f6dd8d9e16101063077c16b23fe291a5e6d149653e9ac7002365159317' + + 'adcfad511996578b0053a5c14b75f16c', 'hex'), + 'SHA3-512': Buffer.from( + '2162c2a8907e6b2f68599a69e81a464d8f076b5eeb555d98b4d20330034df3c7' + + 'cf35b1fa958a074ca12f0d242df39f0da3d4f1dbfb3629057798fe1f883974ee', 'hex') } diff --git a/test/fixtures/crypto/ml-dsa.js b/test/fixtures/crypto/ml-dsa.js new file mode 100644 index 00000000000000..26327ddf1ad1e8 --- /dev/null +++ b/test/fixtures/crypto/ml-dsa.js @@ -0,0 +1,47 @@ +'use strict'; + +const fixtures = require('../../common/fixtures'); + +function getKeyFileName(type, suffix) { + return `${type.replaceAll('-', '_')}_${suffix}.pem`; +} + +module.exports = function() { + const pkcs8 = { + 'ML-DSA-44': fixtures.readKey(getKeyFileName('ml-dsa-44', 'private_seed_only'), 'ascii'), + 'ML-DSA-65': fixtures.readKey(getKeyFileName('ml-dsa-65', 'private_seed_only'), 'ascii'), + 'ML-DSA-87': fixtures.readKey(getKeyFileName('ml-dsa-87', 'private_seed_only'), 'ascii'), + } + + const spki = { + 'ML-DSA-44': fixtures.readKey(getKeyFileName('ml-dsa-44', 'public'), 'ascii'), + 'ML-DSA-65': fixtures.readKey(getKeyFileName('ml-dsa-65', 'public'), 'ascii'), + 'ML-DSA-87': fixtures.readKey(getKeyFileName('ml-dsa-87', 'public'), 'ascii'), + } + + const data = Buffer.from( + '2b7ed0bc7795694ab4acd35903fe8cd7d80f6a1c8688a6c3414409457514a1457855bb' + + 'b219e30a1beea8fe869082d99fc8282f9050d024e59eaf0730ba9db70a', 'hex'); + + // For verification tests. + const signatures = { + // eslint-disable-next-line @stylistic/js/max-len + 'ML-DSA-44': Buffer.from('f4d00cfbf585bc0178c62de0a37a3e3a2593a8fb4ea7147ae35c3020e0084b0a1ec21f6523695ca7e14a267083b95f56e9423b3487fb09be59c84f1ea80c8ec573e8e8d2eaa9a52dd7739295f6614fc705a2dc9b0122f9d32b731b3743d8bb44f47c5a4488cbaabbd44bc3aad0bbad9fa9ca6d29c4c3cf6a87f1c8654b2fd7c955f2721d0da7e39a01a7fa39b1b1a0c603da985a4b02e6951bb7b5ab0943fdb199defbdea65f64b619ab3334337a67b4547efa7e6e132f18b8abbe66875fc3f9fa95d9d4613f55356dfdb2357c7b7556560695aae35dbfa69d2c259ba7966dad1640fa6fefdfdcdee71aaf2a35784b19f8f6118ecb5a8f174bda4589f13dbfd21b5d90a68bca5c31a270ab3aecf9ac549568396396b40b2fba1e5e1500a9f127eb5d08685be93a4298f03351715a917e1cbedd4db605389b69448c06607d7f38a25746f9d1acae6c7827b620ea6e8526fce50e6ec4cdef1a7a154fb52db77cd3b0c4de8e54f43426fc9342aa4b9077dbc62b30a71af3eab48ccd83dd84122bdae87173bd9520c6caba46d8184340cb34da8a97803a21a670714096efc925f6920df3dd8b29aea13788040328192c44245275c562ffec15cbdca5bfd5a3514080353872b8d88bf4ecc58b6db8d16d00d16e13d536eb91df584c8aa8ac560f752c1d5b0c7247d628f4a569a6a3810562d1364055f3cef73f1b2f51a0f2595f50a3a09840c59a497f3cd837aa29f64f28b635fd0500b4b62766987c99cc5d3c79b01178a7fd2f84d588858905a74da8b725d094a7c255a12be113cd7907d44ebbabdae92ae21d9affb25dbd08524fa5fffa513d772df135bdd8bc99e48af30ea46567f8336cdb546cb2db23817b23c0915b5da6e56a83cf19ab742faf7eefc9f95e22ca95d0587b3dcc580cd2a932b884e0d7b6e72124cdcdd6f49caa63e4f85aac04825ffefcf223f4537ab04cb718dc18ba72993aa51d4d712e9a42fe3e1834e6c70b19762a5895ee4fe1e2329dadd053d296bc18084bac81431280433f8b73d3d7a63f9a9ce7a09b90698e467517382bd0d3e305e212381065bd2e01d6ac4b30a9ddb95f9af7b9724fe3e38d97b6324d90dae256fe389b3b835b63a47d80da3e9389b8970d58ff5b8bdc62c6984c86ae7629b9b6e728e87341515d2420275d00a5a65d9bd512af1b1d47fa84e422d6757a9a70244d0aa64ca818d5259f9efb80e5d251a6e1c4e1768ec9c77c6822246d2cbe18ed72b7877cf95002ebf4aefd93caacc5e130dedd3b35c782fb75a57ed29bf6dce66041e13ebeb54e98ea35eeaa6b32e943f6a7b668d5ecd563fa03be46151d863e9747cb1c355c2e3cb0b7627172e1ecaa50d295ed93ac0529d606b8c4c8285d9dce0c9b061ad93a12dc1832710bf1e4c5799fc0b7383c8055ea14a9a039d3f1ed8334741b7ec8bf2d8f7b1d6ad9db22b44a170aed2d358e5a20fc19e1ada04c390ec7647c49bde13924a9650dcc356d45acdbbcad1b2ec21a5eb835689dc958078e3c41898bad8989c5d816bbf403babe96661215e68e59f0ba747e433a3073afb957ba3ae3a435666dc287c2def5aba859674b181c522a6a54ed613faadcd26729725fffaed54692634b13908dd52db47d441306d8f31aafe6b1146cc736290ab4f8e01d54f10525a1454b5a49d389451d071076d8cac0b8d09ddbe44e2f5648a617cc075da438014eeb1c3bb3de26a2d1a332f82b1cdf11544e9f459334cd61e94d9ba2ba57b2db7e9d22b86950e2a2ef5bc5ecee6efb60d5123a7507b6fb73f2ffeab60c9a70de6098427aabaf8fd29f32335e2d14964b56d5bc86361fe01ab0ea6278579b4e7c3866c9e79ce006997a74d7964be9a632b376df797fb593ded91c802959792b3c38fc92186ae2eeb38dae94181f15c8a4abb5e1740c8eb3460fc88de5ca6a558a81381b29e2fafd2bca4dcabdaa4e45c9cd334614757c6a7be7788c93e27b6fe8a4135db82312274c57e98f06f8114e11ba5a4354cae97d44ae4f5b4682e34f955d5535b859a6cb59028feb0309470177163da3a7c1a76fb37f021f16678cdcb07bd2b49b0c77211560e02bc08446e0af3d1c7f736cf620a0a79d2a849117cf1af585e26b4984c157f5012557f96d41dabf98d1533c31df28bb8a43183e592d890bc111c0a94db3d081c34bd6c003cca0abef79bb8c95c62c6647de01e47962874a68b671042e315e6f192175ace030aa9b76ffa9f4e199c6a4f56453a2b312477eafa4c383ad2429fa453ac15bfde030532fa2e26cb200e3c7b7061f076fe28dda68cc07e7c58ccdc22fd164cf5643238d9a39945da27372f600b80f88d641734a1f8f5e4e47d0cdfc083139198f42908d766f308a3409f56b853dda9c4eeef87f9e772bc8a1a3d5f7c4ebf865a722d5181da636297161f8a7e23d54b8523e2b52c17ddcf8f388032e4b8780a5698dda7fcca8fb6fdeb5e4328d6a886da5015bd58087a0cc43a72f5b88a78a7a12b0daaf470ed049e170b8b03ef8620b654e744859bba5d5543f1e823eacb59511230aff547900091a37227c9021549e4eb954c2b7b587764fc88dcd224067d9639cbc8c3fcbef71651d4273f21f5813626c710c12f16cd0757b3d8b8ed19b474e53072207502b66faaf756fa8846304fbb20ece8ce87cb71dd8344d7b10c7e516fc3eec7854e6048a893bd456421402994612a52fef16a97ab642edd69c30b1cbbe0f8e73b8f12db6574f7c9c7de94cbd698688060de7a63c8247aa3b95bbabbfdbdab7fa637e5404123ebafba52383c459621c154a2a23eec97382eb531b2ca83cdf075ee5bd54ba3bc3e19dc3ccfff08e5e61e8b49b080809a57db3548845ae9f5e15fa10024e2b86b283259aa11f3620d5104561fc32afffe54526c5f26e1c941928fed2fc4ad247ae0f590484265589cf0ead253f0a9fd50ae2d8cf02deab6a0f715148f905274186f0c30743e3542bd6ee93563f596c43baa6e0b99be127d722fe17299af2716365a7e72ebc20eb811291ce489b739ad2d3f79a4e81a2090cf81b7a45ff782ba3655bc717e0f80e4a276c0c3db48036c67d1e95797e2c7c9e183a861e28c9604f3e2187e470cd0b37a7d2e78b3415f6391b7fa04c492edc03c5a728507f234fb6fa885ab1939f64291a2058538bf83cc7cc3ea785774332d92b816c76d8d99b94fb8c237113a24b90768b2314aeba2846a645e623834a3a947856e7c85eddd63a1865700d8ad5e36521fa1f90478601a557ce111bc7eed0b7542ff6e8e887e2253db793abed45581fbe9ff0a3100308102026616a7a858f9e9fa1b0e10e2254607f84b1b3b9c2cce4e7fe08162b31384354627b8296a5b4c6e3e6f1f6f7020a191c213b686f8491929c9faae3fb000000000000000000000000000000000f1d3040', 'hex'), + // eslint-disable-next-line @stylistic/js/max-len + 'ML-DSA-65': Buffer.from('0d7f54a113bc87e6be6abc34282a0abf0dd12e8b3ef64f338d62be9986eff721776a3ba2992b069c8c2d15e0b948f2615a7a71ba42650abcddf147f50e6ff7de01aea560ead3b540ca1754b4749ad345f750d75c379804b7893cdfd059345297c8776fca5378ebbe1ee1fceadae9bcd01baf39c8f01e8791730e7e1e5d0efdfae2acdb34e6be09a258b83eb29da9e48506c5d1029d1b7157b103fa4fd77c8b305a0e4f2dc82b68936254bf3a345143920f67bada15d3038f76c160281085a666aba741ee44b12ed4018e9a41d77c642bfefd658255ef560958575c338635d140bf73c2daf923f26898fdf45fd48ec24c4ecb8adbef15bcec7ef5302fb58ce6a16628bae20a55c497c0897f86f0ec22323b96e1bce5e0cd7c608aed5706a2c73d5d4d05cc637f86320a8eac06b8e26a13874bcaa9131db6c9e237841df21789a47fe48a13895575b77e141e3285197b5a16f19a00bccb1d384ae026a17d64a55152188b88a682562340f667a19dc3bd6b5592d39e8b7b408c1ecbf768628038fa4333a475a72c03f3cd8aef346b1910724a47ed915ff1b2bc71ae17f127b903c679b103026c2aeda967b0d6497a30790e2507cc79bb78e9326f66c8d015f20baab3311a7b436c357356f20cb03209bc82863004597439d0905d73eb6213d14ef4505879e5ac3f4988956d5483bd0665d0d9b3e9353de68e7108dd0a6af6d23422fe193294419c3887571b7470d206f23cd059895ed5a53cdaa6ec94969b43eb9c98ae53b909a4162d6230717b3105dd4d511a40b9817a71e037df02d95c508df1a392aa40d9516811bd520442c00b8377023cce3f1462e50636813ba1e4350942c435743939cb540d4688d0db69885ec2d2e3d37a408030737dc8fbd42cf19df941fa5f8b532cbf839ab13b940b5b37200b900d27681ad97a4f92c256c29d63888302b076665d5b9c2ce219fcb12f80dce2b94e6001dcf3f662e01f905694a78ca5e2afdf8ab7cc1fb72e04521d9998662d423936d5ad359ab44091b53467406b7c306c0a0ad2e480c968f2d9e4f392ecf983fd2a4ad198bd701d09a1e2efc5c313999ceb83695d99a5ef25daf159d26f4c0ffbe3c2ae03cde0962cbf1a4e6a98dfc660922c2a399190abcf933a73f68d491c4ad8806cc450bf23d4e1c1377f6168a3c4df5b6bf8e1cb4be11bda24b6d9c113ed4d40fc2dba9f541d97739b5e40e915be1e10eb01c30ef609b6eaaf5d570f886d031df36fb7614080dfc088e821d1ac4fc50a48c5dd7ca6f08c7ac280bfcca640cf738f08f7da29639341e396a46d7204cc514d9c8dbc213b967657f4a67a29458947687b55ecf26d833239a7c0915f10334befa61b74372a8e1f4adf44906ea6395e15dda5e4b2d2005d574a943abd9d224c7f904298e2b1e249ab33d93089f239de6ce6b0c2e1b856940b7b08ccf1f98a65dfb3fc615457e2ba436bc63cce72510757380a5c07d56b47c93430b6bf9a89e5f6e1721d7d50b7417432471dc2ee0b02430fe71f4122c0e3363db446e4f7ad21cee944f3e6072fc1fa57d175329707f52c51f97eb287d7af70ef91c0632ad5b89929507a2d01609993c2ff8bb991e232b554430cbc0ae4f6b3225013e1fd0f8e0a485f7b55c1f96dc0fb6b395ce104d9c3d780567cc9f7bcda296f665a5eca11a94fd1d0f35d10e0b017880e16f31de961f7b5fabafadd1c99c80cf7535e7df7b807fde38b530ee527234983e25eace30c4be8fe18edc58ae53ad79a145ee0700be63b636a18a4eedfab826383a9e5d9f2b7e490c6bbcc3ec0f37ee44b8e301091c6636cf212e3519b706919af41b872d6b4778cca8ca3373f62dbd8f68b59ca29467c63e10b886a18d2a32864ebca2ebb2f602b736ac8587d544d74d9aacbe6e436646ffccf2ca3da2121af8b3431b523e09a538bdd07ad3cd4c71a76cfcb67273f277e18094ca9730d06af60c354824abc84f623e1727a4f43c7021ca18d463c5aa1c53cd4adb77f13156103ac0907f6487689bf5eb2202a6ace8c9f55b5a285311d2e243090ce07e9cd2bcd253edc4ca57c1c42383184ef08d3f7b4ddc05fee25e8d72a6fa0dbe545dd3240edfc8081fa49542bc23d426ad286010920ad92b0fbae5833523a94ae59b87d90e1ae4cb504f06512b9c0c47d854750b45bb01a0a2788c51d808748f6cfab177fdd943965187fabc8419292f45f57718f709228a9d51dc43e21ed3af2bc5cf8ba75a4d22203a77662a71675b15bf91d3c7074657948b43e87837449bfc5eab1fb1fa9f8f91df5669b4957eceb02d013b0c04b493444ef673a103f630923c9ecc5c2cfb9fe40c1877fb9a67e54f7f0ea0c49d0a624220eea68bbe0e23d20092ae73a8a0f7749cf85499dbf4846308a190bf9ce68fb712a20db6910923eba88f42e93e76230bdbfa9890b6bce07a5ecffc396cbb314d3152bf1677a26a0245fd8373a1288d97e713d2326194e2e26990ef413a1076035e7e04be3d07cbf9403ce9f8b7f10c70badf62efd144324c1d37ead93ea94edff937b0d62820baeca5f56cdf30487097f5058ed3d74639543b1c160d6e876021c96feceff84bb2b1588c72f5f9a6a843b85d9b78da39a085f258296538467ae9568d23d9006e768445f9752ee754dc5ddf9b44998e90dae1a5c720d0abd3e81b750ff8a689547998a45b5a6155b462e8ed38c7b6c44bb81e64630449191a38ddff8eacb3f0cc675bd84aeb6acba135a65d2e7233e6b3d59f57d5a1bdf215a3a847a96306a203ff4a564524e03526fe3c35906fac3316cd19cbf39fa9b3a37bf2624c4f7eab0b98d43b967598349e7b0e69ae7a110aa5614e68afde1ac453bd8b02a39307c6423339d49df4a1d7c3042d0bd34ab9c36894eacaf1872ac70f0691dfec0ace938d17f1f61c7a0b6a12542f0d5e0438d2f0c69dc4f0a2eb5eebc56155954d97855a5e95d133663f29402279d39e41750c0667a721cee486847913d383940419dc9fbc118d1f608abf2f2ca244a7486d1ca2e883958a0d36e290edeea0e5dc8b44f22a6ae7efacec2ee642e8417d5b6338d58ef540b5438a2de2589e3c911d93570d1f7043da7f04f6a97c64af34f25a99c57662ddc27264d5fd880d6538a81661b8e83ce0f00e41a09dedc2dcf0935842b46c7b0560f0fef6c7e45d20de5566cd68c4bea75af56f68ec6f8061b59bad47d346c02d4ba89ed92ebe9276090a70f119faf4038cac4d655682285dd53707d00063f89f97dcda0e370a2b83bb6c8d9a4db2dc85efbf93d76b007de3566ae416a817d1fe9d7383451b8789d4892a4144befbf784a837dda4963cd20cca292273121b370cd1d0dbdc9e42aee99ea7c833779cd1fcd570f2c502c791ce06b082ca2f820eb75fa2b5ca072c915e3bdbc6eaa3cce642e1001fcca516c6635ee21c4a921af590d50d5127da0acb8ea48d11ea777192a540e588855acfe2ab0aa23e7743da7dddf11cb5fd7c3b0536b1dde5156b2968420b58d502d546781c2c85d39c80e9182474bf36970b19a721f01d103b99b7074e2fcd3bafe90609e6c3764e12bbdda9544cf0715aea922b34f33fbcbe0d3a878520f8fcb63a68302c704053ca1f4ec2ba20935561863d4a4d7c442b82f7e8d4c12a987ce5c1aca012fcbce39b96af6c34525eb197c82385771dda017e0d88007e944cce84336c08505f39138caa9f9b2106ad5aab0cb178d3361eeeae7b2124729afd40064c9dc3c211ddb5228791f888dfdebf9c17df3470f6c3ecd400ad150e2da5acebec9e0707916e481733b40eb728bf4530f634e0834e14d6e5024d5e00278f503cd0b308284016c829bab2239429eed5dd8b99b3295e27e4968a59f6e411abce882d1b4282f3cddad447ae8011740985ca9e8de7862f80ce1713a2e7261217864671567728fd0aca1812804a99264fba96e6b6ffb2aefcc1c8f95c990710f1e422175c8d26cb3968522c4216806d83982948d4a9a99af261c85cf713c70b4552079f9ec7e1563f4f3604cf3a8f258a63774a2c98071fde26a4b462ff64d3a829d53274ebc8306fbcdf90f244333a217196a744dc3bb67d998d6af560bd9e328aff3865d02621b4203caf986a0f6216c827eff118b28ed08ab42e70be5568d41a5e9ebfd4ece177670f5373797c0b9f99d07e25cf8a1615fbe0331f952685c6ff68626cd01551778bb6efc6e8912acfe7028254b20f3a8f6fa11d0ca336c73cfa4f754c16cbeebd89d11b91b5b59f422e14b5d419d19669af331427ec4949ebefd83ca4b973c53655a38d189c06764d3e7230fe2327d7d98e4e6f82b15d4ea0a4d3c07214d00b4bd00a6198010f13415d43076e0d6af24e94a242f70ae947bd0550f391516a9223bef09862bf5e13787561539fafe4c07e4db618239e6fd69c0ab130eea98e864f76ef0eb273af8eb7228016b495aa3b717ef057a7e28ebc1fc8e28d8549936f511382bdec65af3c7f1e149df5b51ad0480ce282fc605bccc8b0c4f7688351947b396643cb1f7f61dbcb123f2230175cf5fc6ecaf9ca6a58ec493cd6379095f6afc4e0f4cf0e6dc710d3e2dbbc307e4b80db6855bc26c47334d4e5a7d8691a3afea043bb9e5f818406f0e5da4c0f7fd57c9cad8e277b1b4e7000000000000000000000000000000000000000000000a0f12181d21', 'hex'), + // eslint-disable-next-line @stylistic/js/max-len + 'ML-DSA-87': Buffer.from('737897d83f7fb82c704f4660c16c544212f3d3d280b3390c43d356c281aca7a3c90637a5dea83bf3504e593b178b17ec38a8175507a6c28b80c21cee28146af2161af60fb123420578e0576fbb9d579631efda9a4304c0ed67572d393d813155024ceab089c1cf1d0a5e9716948312f70b4a7deff76853f5cdf29737160901c14ac2f24ea4c7f819d86fa9758b8ca6aa6ffcfcd6d60f90ce31d31cd9d88586b9a38a22402be7c89c9d57c2b194179011c4fd185a5bd737032c1e54da0108f355130ffecae3f39a47148dacafdf25f9590bf6c8fdd9d9b7cdfe30607a235993c59631f29fe01453e2fd61cd4e8e3be21ddffa054c5e91fec88ecd531090ce21d68b7b11888ae9372fe2dabdb00ea311bfa564f86de46c61f284a246e420dd8056f021448a2fa6dfa2391e5beb6ef0a5a28180dcdf20e00dff4f9628a99054ce72061b0359cc67d4329545faea8943d90bb0f85bcdc88ef2acfd72056d2b5fbfed40034f2d344a395434d604a336c1e418e2598d07e8611f9c28c23bd2771c4d4dd5f820f197b50e58bfea502f6a53893d7fe6a7460a62b8ee892d17dedf606cf61fd5f27e002230bd15f0e3d5d951df3c54228c20d58fe5e5d072a1869bdeba1d90cf019c6bc3fb0df24a11894bb3a2d279c67d3a6d4342e0e2913861bd3f684bce8ffccbd6358dc0778c02896b719d675b584621edceabec3a8e679686363825e6bcdf6579ec67ecafdf23d6e23b747e340fc0982422865b8002590f9e35c8332ac47be5c4be6eeac3ee4ba79574f7c0aee0fac0e5cc7a9b7288814c36b10e2dbc900acee6841c386fec11c69580a87244fc9f995f6adec68b308b28eaa17129c1edfcbb8592b4bfdf6604afbf1f206010b47e92d08d830052d109f0265213fffd6ecc47aef5bd6f4d5fc19d2b06924cb96309945168f382977e9f6a2f59237b4a91bdde2d00453df0e7c516b7a53b0b8582aaa7ad8af9f40723d09a247b653ee48999148b5c160a25f5e9cbbdb53642429909a5af11c6f36db3fe8f8916f36f565fdbcd863565a6dd63b974304b6aaeddf91decc981d21cc93c11d8f76dfeca8444b6b882ff59be214fb1ae9fd90108f65d4f533031c35bbc552950b1a96f4be7ea9cd167231d7a7fbbb203d34bd764dab3ed2eeaba3419ae3674f7f01347427b84ddb5b86969e57e6bbf86b61bbda8fe830f357d12d15eea0df7d5fa2d641b03077f058719bf11819f0eb1dd921b783b9c648b5b3cad3d22e9442e514e9eef96d53c1de328dbb79eb801974b88e179d98d28bcc75469a2be46ae9fcafbfdb22d302ad2b535d7c09d06cc5ce1a50a569dbb13debeee5cbdd3341d06d8be28681c7716b466284efb4bb1b316b72f50c85c2a260c68231e2c516265f156e5f5ac735b9a420878db52950cdfcf21dcd9da62e41a4e117386b624f638ed51b4d2e156573b6a327698bd41a3d036414b201b0a31d818fcdce9c1e4d55def388fff5fcf5110b680e5ba3808f244dccf7fb01e8197ba3c3a3a2be74f44551f5bc03f77ca5010d1c9d8020fcc7c354e3ce9fa25d4900943f564695da56a6e4388d1bc69a68d413fc80e5913d22d2414b081052c216afc67ecce4ed126a8e0762191558cbbdd8d4150af7bae41f4941ed42fdae4fed9a40d70a0159d4a8ae48006022dccb6b4ae05dd484c065dbcce9b253b40d1eb542073fac64dca11b6e1196c5b50fb33d6bb831b9977602bc9d43c80713a5d714116e62fda8e9aa2c790e8d5adbf8025dbfa5f80bb92b661d45fb972eafa25303f1db7839efab7271ae41ce9f7c28a7cb8349a3eb2734c6b13caab678d53eb742fbe84f53b8ec89e6c155ddb0a4908799372123023f4c8d6f97d30fb70ff2d192be0928fdfabb9aedd04f944b2c7e0ef143390a20a5d5ea93d3a8f425470666dbf8733a2e2564f27e1878b2805d71069e55b225e3d133a7cccbe0db3f2b14ee77d717bcef348df09259efe12bfef08cbb0e8d58f08bf10c6a7b0c2820b442c06209e9b8134aa20d05e8e2c763a8b88b5d4ec544aeb1ccd9468901f6ccfc20eb632a75d051a0295590e02f5ded6efb33e54479b7e1104062ccb73bb5bcea1da9d4d659e1ed40a42ec13a6f27606a002097565264954625103e921886df888740b7166640132b428a97062241d083cf8b4892c159902a9cf35921df2966b50f6f31209773d1bd10c61d14409cf1e90a7d06d3fa226a865fb241299a859b8dcc898b4bb1dcf65ea17353a130e71ad184e9e89f0676e666352ff4614d8a7046588d97038a449ec426b67c9ee3101b8091efdd10867366cdcdff3581c0e973ab6ed71c984b33a58c2e3a865ab1bd055e629a21fb07a8a23e998f2db958ccbc89fd1b3512330f2cc69c3d74e16f8848556011881701b213dd39e123aca241c7231052a7bfd4738fd69bbfcd0ab74276907c1be3f75311e395a485d15b592d16d22c33ebf91d64e81d500ea23d53f130804ea1cf51c4bf2b0474f4f62264ac7bd90e6e9dda4d2b627b1e8043dfae64c6920a4f9a14ef15826996035af30cfa59a6a2411c61d16938004e2ae2ec72c32825a44c3200853fce28e36620fd0c71121346aff28e19218899f8e894fd6e477571f2a5c6c850104495a89cdc0998496b979b038f65ebd15ffc477ef22e943358de58612bc734d06bf8b77fac755b64f4c71966c699c43c5c38b7fd647a8b82cf354b1da4d31c9e918e146a997f2b4fc612e2ec73ab8e017728308e589efe326d4a14eeb55de34849938446911b9f3c62ee51992da1ea284650e0fd6f6078301a46948abd7d6ec2ca9ee202f26b9aab99fc29271697d8565a5147631ddbc9295c44812262a9c39cf0df4e08c52749823bd51d117d6cc106d74fe329c658c1034dd7d9a2f073fdf8bec519aa4a2f9caf8d0d719cb37138a0383b263c1c28a8b15c1d18a63e366a9892299b507da6bc9926672289a4f9f679e645db6c85e1cc1f4e6f9581984b2f72283a8efa471614e3e3d4a58921bd0d5feb85dc9cccfe4a7eda2be3d8118b2f4ca35ef6824e1a2d09162fc81791c2e30fba9368022ce8e01e1601944f05a0771b2d3c970f88841389b11eff76c853570c66022c3d35c4e94714277cad948c1a2b3273c6b3d675f9606b8b1a5d1848a8fbaf0f6e108b0029b6fe2ca230cf0e5a24049616db1b5bbaa0116308fe5948c45a4569fb5e3aa0bc5ebfeeffec460935477ce1c06dab0a1e7012859435e9887052293ad68a92df2b498b07dc991f16d04dd96bff885bb49a381b27c7e3701aefedaac8472f9d547da1aa8e226db691fe48c0593c8824803e564f57bf6b1c927742c909666b07f92f77236a2268a6148b517c1d2dea60321ff1baa5f47c5dfc000c8abc1c78384e1d6ad67dd343ad5ca3577b254d0ff85f12953f2100e6ec07d3c87d8cb79538cc75aa60fb936823009a55a1e7642a18efb94ec2eb44724f5b492263070a3e5481d091820459691507ccca318ffdfa4a0f5bc079e4c6a0fd41e715d0a7aae8d1916b44edaeace00053ddd45aea137ec0ca8a9afd497fce05b6fee9d5eb8365f3bbde783e51e29bd399c4611b1bf67487b1a30c1e3097c599bc74a4531ef9f1cb3b9eee69ab195601bf3a75f4abd8cd2475211ae9de36b42c1cea349195a7758dcd6f0b0667012e271143ee765d48348b7ccc47a77ff7780fc071505b5a10c5734abcc07de9016e6efd1839eeb13d050bf1bbe28a119c81578516250eda43ee91effae2b3bde7128a6d0bd2426afbc1feb1f98d64de06d5d6899122013c467c7970e18edbe11a3c4900a182cb03cc149384ecff2833a3eed9e0071b7b3da23fb91302b76d8d386f60ac16e814be0114d16a85f757abd78e63b3b75e7b29b222aefbbb545e1286f10fcec514b9e8071de9861688b6c481e670a6d22a1d4f91f8d690df9e7d4e94b5846203d2fb865fe1a3fe7bfec8fc72c5e42a56cdfcbe6f7d3aedf685450d44f6143d1d64de4b093abd10d298accc5292109f67c4634c2dd973d38e2a4e48154e17f29a8aad0b8e6cf740437557e13c0b6d7fed4252d07e61db7f65e28e968a6dd47c1765973f13ef5d3c47801dd0cca810fe34ba111e3ffe18d3fea4814705c797cea0c53ad14f857e1e16a77eb649cbb3e3eadc613cf25f125f0c6a096b0d3b85b5a7bb53976a817235df8a384f62bf5afc08ae27e52863ac99ce1da62e592d41f5bc13550c6a77923c363cd3f5350a2781182b3469f34d6009a4417cb2bea309a40f5b6ad917c48adb6f9a2a3836d7491a96cea1328b1e0481a00a7b187d27fefceab3a09a2dc4741b699bee6ad6247a67b977dc329a54c05b2ba7dd3558125e73439570427f78e5f74576743e8649a9be6bdbd36c2b6cc070369b706e253ca2fcf2d82689b997873caddc13d1c0d62a98818d7525c11488dcca621cc7603de6be5ad7ea8b708e75361a357aeec7026d3ed9e92ec06057883a8d9afdf8701aa5074d2cd7414fce63bfd807d818fc11cbd31fef12bb398b286ddf68d8cabbc10d90f4ec191030c2fc0846ec8ed207b793be8e71c9d046cb6f39aff164d524386f89efdda65b48607f6b9ff82eb0b6ec7e606c732778c80edead9760b5aacc0eb0f8f9345b8d9b4e539818a7083a366ed5142f4dd8ff427833538545ee93e811e777db349ad85492f6c119491dba95ee5d3d681f8e2602808d366295aa90ae780e87a25408b6bba38fd74573d6e167ca96d29c8c21b648f92e0469b960c7320281dc9cd8aa03d5203e1ff47f9dc7beea4f49fc14b8047c0c40bba27637da755e0f14eea3a899e2c346483e26403ab7d119434c8c9ee35e3819cf578995cd50dcaf108af736e1bb769a64d64152b5dd760c51819b69379f1bf80bc291da1072e0d75b88ecf11e15b3a43d4bb9403359ed87530be3b949c1f11dfbe1f7d04936048039f1cf9b036e056bb451bd31b871b1632d4cc0c01db0caa48625a6a2fc33e94f9afd8afa4710c944b6b6dbcf1f0fdb687575d2f4d334985744a45f0b33ebd57c5557836996c58976850e8fbd6fec193d6d8d9d7b72af74d2db3d0183db111237612e376a2695b55643926c2a0043c40acd9584a36f79656ec1e3ffe48c10a311dff01572e34d74561e3be0cbbe6ed7dcdcb41cc406921130f8dcd49d71300fffcb896623a615120040f5eaf75a2f849d8bd7f1cd10036ffa400f4582f407b09f6cb466fa65d237afae541c378fe972a0829c5081236b2578bfc7e2daff68cbf87f73ba08de3040068984e5b3262160e5fa65450342af94ebd454d9c246de45d7b556207ce35b54661de74cd6bf12edfbabb9d4a67f34d6481712e2ffa08a2b5747cfefc5771148af9c8793b57ecb1d559ea6121ce7c8dcfa4fe5fc86417ade50a38f059719aadbf2546d4831443dbeb8d389c83bbb71be9936dc9f884ef5eeb69a04dcab8ef98f7ff3c57b9411f4b3535c99736fafcaa6f12255329839e34788671572087832ae5b1c65d553b7ca511eff57ebe30b857fbbb42ac2958f09aabe7e7372d406dfe920cbbbda4b3443940db9fa812aac43930b95eab060cfc89fc04c34a93306a5fa51e16bb7a1c29e144428e5a196733cffe4ebe2c72d0343cf40a1a1b103912ff47d699cc688baecb29b774c4611318038c47e3788a82b8f29541f423b31aaac2925d7f1251baef27e73f13cd6895598fe6411f7655ae2474c86c17ef48ae7151938328f2bdcee388efa792a01a2f59cd1bcf17787a093a4fef6f0d37a5693e934b9b869c272ce6e140780268358d6f7a6cec82e48915a46f4bf3368cb6c6ae3ce3d63a4ab497fd20babd3fd51c359daaef77ce8ebe578e9e108c8a251f8f3c7b5412a81aeb806532a02c6880783ac9ef01b2b9ded2bcd81de8da008d0c8303597e826bb64da7b1e59b31009680e09edb2b5e75129192d6e99b745fad01c554492b33bec31a2a10b52441f09ddf130a7b9d305081bc2b0db23cd70fdd91a472bab7804489aa612d8bf003a64b5383191ffa9cb97630dc81d80a616682463836005273f12b357002068d71a04c00739f5007978254db5a560e878697cddb4f6ff641ec98e64f67dcf83d7c1f063874fcbccbf30d9098eda3dc12fd75a9d7585360c9196fd1f6ad7d9607fa4761913be523fcb09d7af1c921065e8b8fa479b15477100219bee47c5e9f00b3e82657b5cd2f125792ac9f78e5a02f3c1c4c43823278c4358e8956569a3f53ff2e5c41f364a7f0d4a489ff59603cfac5680f1e0813ad4e0cd0e1fcdec1768823d8f021bfe6c82b0b8c238c5363820a79378f76f48828879b855ae6e9d6116cbe15c87594b210be86583b5dd0eea790fb4b8be58efb2d14ada79a1fb8a05838f367098ca73cc81dbc78d02de145849c31f22a3a2aa05f68b8435c28f13d3df5020b288c1e72985910bc7b5b6d1d5c14294e4ff20b197aa5afb1c2c9fa12295a6048585a5c68bdd43b3c476a7e80adb8cdde3742445c747994c6c9db4f5cabd6fc0f12385191c300000000000000000000000000000000000000050e1219232d3238', 'hex'), + } + + const algorithms = ['ML-DSA-44', 'ML-DSA-65', 'ML-DSA-87']; + + const vectors = algorithms.map((algorithm) => ({ + publicKeyPem: spki[algorithm], + privateKeyPem: pkcs8[algorithm], + name: algorithm, + data, + signature: signatures[algorithm], + })); + + return vectors; +} diff --git a/test/fixtures/crypto/ml-kem.js b/test/fixtures/crypto/ml-kem.js new file mode 100644 index 00000000000000..8786cea89f1426 --- /dev/null +++ b/test/fixtures/crypto/ml-kem.js @@ -0,0 +1,49 @@ +'use strict'; + +const fixtures = require('../../common/fixtures'); + +function getKeyFileName(type, suffix) { + return `${type.replaceAll('-', '_')}_${suffix}.pem`; +} + +module.exports = function() { + const pkcs8 = { + 'ML-KEM-512': fixtures.readKey(getKeyFileName('ml-kem-512', 'private_seed_only'), 'ascii'), + 'ML-KEM-768': fixtures.readKey(getKeyFileName('ml-kem-768', 'private_seed_only'), 'ascii'), + 'ML-KEM-1024': fixtures.readKey(getKeyFileName('ml-kem-1024', 'private_seed_only'), 'ascii'), + } + + const spki = { + 'ML-KEM-512': fixtures.readKey(getKeyFileName('ml-kem-512', 'public'), 'ascii'), + 'ML-KEM-768': fixtures.readKey(getKeyFileName('ml-kem-768', 'public'), 'ascii'), + 'ML-KEM-1024': fixtures.readKey(getKeyFileName('ml-kem-1024', 'public'), 'ascii'), + } + + /* eslint-disable @stylistic/js/max-len */ + const results = { + 'ML-KEM-512': { + ciphertext: Buffer.from('3b7ac92f6140c9fa0348f112ee8211ee668ce657b8bb6352a076880dd7ff4ca7c0babf635f1d36cab5106b8287504d572fab1d0fa3e086310564bd853bdd96cff460eeaa49be316eb95e47c477eeeec7276422abb44ce349016d80eb28f3519a64f1e1f7df63730a1ae83f6cd88af463fae7552103f1eff1097d2b80fa7539b10355136753d725dafd9311fca3d3b5353e2af9ac7f514c420dd7cf8a7e95eec82a37b39f29806db0c6469e884787054c8e48d1416e28fc5c809e42e0abe4547ba9f183f4ab3ce27da63d858b0a2c0970dce72674b5f2c62d6016fc9557b788685cb0b1c7ed3dbe5263463618edb729823f6afb349c3efd229bd03d94393880e48e9d5c80905702fd8bbaf19050dd67029f77df4d37a053fce22a41bde0a464f3940acada39d96a7e20dfef6f02d8317e40cbbe584fde7eeccd02db0b6e1b16c42eb2895afaafebeba0f628907af2a7de9da22afecf77ae1e3abd9593ed545736c766aafe7b223e3b8ab96084a8e14b81bdeff5b5d6c34a107f5108d99d76940535b0a0d3ffcd7e1085ef3e1c1fb54667af235d12f1b8a153faa5fc033676c81a4f39835c29a5ccc1ebb170c8f1717a9f0660ad353e5c18277e3c5e1c999431e51601f3d0a30b027b529489c83ab8da3e00faefc83c5df551c32446280db37940e347f283ac504b2f6a4ca3de69598dd5b73cbf64a2efac948f4793576ba59aafebff69cbfa53a9833d8a2af9b43bbb31c5c18248ddd3c869f7c4d701bd8050345c33bf87ae52cd97246930d84736723b777ab25d08233501a116ded57c28007944c7f3bfb469f0e7d54e643deb543014b6ce9ca4dce6f56a11af37ec0846bf8023efc5127af080e2ecf1c568626cd19842386f3f722d11f153c0167951692dab5468e5f8bc978fc21cca1fdbb6e1d2b27440bb132014da61536ec9c0175d4a7f6ab07bb57004b929a9efb452c718fbf754afb5433df64ae6b95850a1cdbe1e9d743b216f9e1465e488c3fe813a342138134ff73323210eea5950019b35940f8ab340a42054e4143dad43ebc4e9a363e610b5bef73f2de70642e3aa4a631f07b4', 'hex'), + sharedKey: Buffer.from('f6d03353f51c73a55611f307f15eaa2fc8527213667370df2ccab580a0b50da3', 'hex'), + }, + 'ML-KEM-768': { + ciphertext: Buffer.from('90a9c1f4d9a9ab9b90acd011a9b881c18e692e2019d452c11d81300259d3f2d1df10ea4d352574a4e7f84ba78779631d927d8172dea8118e3a9b16efc28d92f15e75a2a223f68155ad39a8f94e95eeda8e5fe14257f7e3158bb8f12927d48e616318569b8cedaa9d71e5a6c883e6344e2ae5cf55ef0cc484b02ecb1dffcb8146971d6bf043a6b772b2a8aeaf6c2549c7695d389e3e6e106daa4b40a4cb429aff76f04e2524b635d4f4e7e820381da770c9b7b53e37cfb84f3744ca8a37a2e150104b3fe82f2c93d5fbbf77b55ccf2c9ea209cd3a950c3e986ee109d10cb492b1e6e1d39672d4c56ce986fb29c8687c5f8efa8be5cb96d0e11162976f6bd04404cad93598187764b38acde20674ea797e1df86869de406cfb863c7d95c94aa419c32f78caae4c86114906edf4bcc41331fac7ff92c2c695452952b7abf7f726f46d22653ce9e77eedf08696ebcd29b6f9a129f8c66ebf6d41c5066f652d79f1cc0b1d3098e20e34917308b1c3a62a10040bf5d6b761fe78be0502f05f5f11a3c443458d185820e132edbe7effb8247d4e397c832d073b822a611b26830b3d3225ee9c56d00348ed234b71bda05c39f1743573b7252081acc36cdb5966349da9faebb1cc269f9584c6cedc412bdb53e70e27cd1dada840fb5e972a0004edbf58b4828e434b7f1fc87814bbe77a8b36bf42fc8d47a5a09dfc1f1e551e47114fdb688dd3c7dd85ae582f169720e7df0b96896e96df39881e83d7e58f5b55c7ecc2fdd0e20e384942db0bbd10edf28c76a84d29607b394e774b7f528874722c57088fe6ffe41428f6c3618578031632e99b7bb9145f035eadb3e621c39b2a4eae17d4fdfb12eaef29e38d915474866911e6b0a963e25b16cf5b0baab3dd07f46dd5e8bd07e5f8b20c346200b3eba57d01d7e365c9309c73ea8c0a1aecb154bf810e08612220bd03733f7750ff291c07b8c7b8dfc3cb889dcbe268832e80e9637d32a38dfbcccc8600f3f1356718708b7d593a90988f584755e16738b2ca0465a2c5920e7be7f180db854a7b59fedbd5252e435e04f5d800b603e04a844a073ead6c43ffd7aa85633fe54be40e371fdcc19e9596292e3745b5bd5a1bc92948489a558c9089c17ff9c05c93dffbc28ac8f4fbd53a54f2017e68db6609d7a7a20f7ab858ce6a204d1f43aeb4599e66d9e0dea307d253b7882da6a6d18dd87a285c6c1ed31c88193e1a103ee175380a89ca8f0fb91fad3e383c88bcf42c966c36c676a7cad83844360ee4ca4c391569aebf2a0df09274116ee03fdfeb6ef4307928e7aae14b311d450fa2b073afac336a85ee3b1f734534feb75b8eec929175fdaa2a7b96dea45b14f897f15b8085dc126ea49df13edb4cbbbd10aeeb688de0e2809faf3e912a4aadce23df8655326079701f035b6acbb8b22fb2b25649b77249eb9c829405aaedcf470f07f7b3ba5c633c202265018177d2991a5d39a23652fb9fa5cc9f9b6394c788c2a529d706f14ef1c7592066aae6b2369c70622bdf345c5692a8f4', 'hex'), + sharedKey: Buffer.from('e9888adf3af812be74eb1b52a49d37ec1ca0e06c04d47d8ceb05f64f0c979b5f', 'hex'), + }, + 'ML-KEM-1024': { + ciphertext: Buffer.from('3ea6d152dd8883f588a33bc88ccbcf7db6e2ee0278b368eb55c81f5e11409509ce0be7932bf60e7b8c463f6f7763fdc11094480dbb94d5cdbb8eb08445f5404f4b37eb1b7a57533269e32c5dc1c673d4b45ee61e7cac186e4b699615ab92591225a2e4ff419aa3e57488a92f75af6945134f3d77e84dcc38bbc996bbb524e1c2afab4e3bc4812de74309ee4a0011299b5097c7bf468024e34552b3be56a63dec078e0083474a31347f7ebabcbcb261c5c2a8e2b14c61c622016e117b6dd8fb7008d64cee91085c4f3a7a90fc6a76af1214e265ca75bb218d13f9e7142fcb5cbc35f3afdb855eb14ab53738ed6e670473e5480f949b59db466affe2b95c002bdc31e901c9ada3bb969e71e1ca95d816d9adaa7fb9e696b7549ede59defce525356b3bc38afe854e5df16771f7a40bcf0c0eebbb051760ab102a18ecc537d44e4cce2a1827d2c863c3b4341dedc7cdb4881bfa5a228ddf21c615d5c29e9404a08aaa61481fff2665c0b057264a65ab355bfc0c407f36546aa69e0e71563bb9ffea45fdc40a6c91a5b1c58901b8b72ca85f39ae159638bf7cf2cd3c9cf344333ac3caa8ba5b900f3c3fac4cfffcd6767f1f347f0de4d0414d18e70e06beb7293e14e3ae49b01a7a75c6c3bb2458b37c68dc583e0742e80fdb2136c01b47173c33759d13753bd9a75d853422006ee05429a644c62e932343f7bab875b635db94af63f9b64a1b44eacf5b8fcc663f4f54078cfe80438156696a5985548e28ea512ac12c267c9b38c73139a91f5ad3034a7edd76ce5e794cdd2775aaeb90c6b991764874a0729aeb66033db5c22c7de93da7e3f068a16b98a0f0a33968f9778ebde1c0d60fb756c150d17220829b41bf393fd96c58336d6ec73844debee343acad5fdd43fd69bacb11200413dd98d3fc72416a20270081dad8f72ab909c2bb2b5aa4f0fb40d0fd738fde65dcd1acb580813f721048114d32ba1b90b514b54a5be56bd914eecf36265bfc94939e2fd86b71344ffc086258667b7c07c915020e5ac93f1e308377361b3cde1e9f1d9b879e43f7f74d4a565db2f9e6a920f2137568651f68d112be4cb13890e9ca88370df569199624bb0658d9b36729d3fd7a55a7a9c52f8c6c261e7cbb54436c8ab9f3a816ac9456e9ce426012defcb7f23a2ac1178e9cf006ba36bb71d091582b8aac3922dd5bdc5badb6682b1e907bf405d5d8ae3e0418e8b2764266437261b80489186cd88d12f8587782b6edd88c5f063be492d5c3c72cd699d36558415c3fc53ef43ded7916ba91018381e4f4ac504098b5ea3935d6eaf458e3032ee1c263d603ab1b6198738874b8c8c8a463053e8b11b381d48117526031f8f5d97501edbae61b8a461f53004323fe55329513720255f5f65c2f450bcb389ff6af8719388e6c2e6d835b59187de474c27ddd8720e51d8a04c565e1537dc2b95f591e0984303e4c8ffc57233cf4943478835b7b1e84407561e8608719a2c26a81c5d8be381ef4e127d3cbae07c4a4b254dee58f17a18f5d09cdf8d3f9bec22fa0b862993d0254e288000699b2cf86e55c4def365c1ac4481e1db3e3e77c9facc0322689f7064d35fbad128cedd83431b97a9463c0168f8eba1c1262f2aaf7ba01f2a2d25f236b0c10646e66b619bcc1d5b2e2f25ffe59ec87ba15878049cb3c2fa707e608229cf2a558e2e770bef5ce3e716c133cc31b60efac524ed8191f4e8eafe9d7f4760e1fa136dc67d9a34a383387b69d60eb81da221cbb13466e9cd89bbfcb4baf1eed89cd9675a3deefa1fa63bcd70ebe138aa07fcebb3a271683508994fa2fd593e6a98b124372b324610ef1f0dc0ec03dca70f15c9cabae4909be7b0e3f844d7023a58194a1745b6ec3e43d05dce405fab650447fc786e53ae57d65e516985946e6d69a090a0f2351f926aa8e5ecb034843135cf15ae2e3f7336158e0ae5afcd7a8f3f787f3cf2fada0253137b69b93df6e43125e388e76327bde676b0e1827e02c31a5c01f63d8110986d8f5e3d1d6038699cb83d9ab3e05460a59095a011f454bf15a8e5718fe57afe2753443712af3b8d6b967a6fb90e5a5f83b63a67b96df1472fac3930dba1a317882728c14e51ff9437c1a878755ed6a6ccfc5e0631c71062a37390b5de369d8363ec768499f64854d4ddcaecdae560cecb0a4798440c71c9303549a908d10cab296d53aa1978b205277cae0', 'hex'), + sharedKey: Buffer.from('345cee699a756befaf05c60a35591c6df4f91a97a004356dd823fa4053276405', 'hex'), + }, + }; + /* eslint-enable @stylistic/js/max-len */ + + const algorithms = ['ML-KEM-512', 'ML-KEM-768', 'ML-KEM-1024']; + + const vectors = algorithms.map((algorithm) => ({ + publicKeyPem: spki[algorithm], + privateKeyPem: pkcs8[algorithm], + name: algorithm, + results: results[algorithm], + })); + + return vectors; +} diff --git a/test/fixtures/crypto/rsa_pkcs.js b/test/fixtures/crypto/rsa_pkcs.js index 49e202c512d9e2..4630e4af913580 100644 --- a/test/fixtures/crypto/rsa_pkcs.js +++ b/test/fixtures/crypto/rsa_pkcs.js @@ -96,7 +96,34 @@ module.exports = function () { '688c993b58a0ed35e8f0a106d4e8b1b360e334415c742e94675823db0fd25e22cff' + '7a6335c70e193235dcda48add6858626bd96311e60f7e5ea4491b6c1e6248afe12b' + 'bbd54f8869b043a5b0444562813f0a98b300356f306e6b783a29f3bec97ca40ea20' + - '062cab8926ec5d96aa387cc84821a6d72b8ea126e7d', 'hex') + '062cab8926ec5d96aa387cc84821a6d72b8ea126e7d', 'hex'), + 'sha3-256': Buffer.from( + 'be1b476c1911a01d71710fd8a2f3158d6f7839e91443b01bed30dfdd04336d80c6b' + + 'f692c06fad254877901c10a73853e8fb202a29cddefdf16c3adcda1fc123625897d' + + '1b81b32a9dec38957e023be221d8f31e7470ad32e761edce9170eefa37ec19bd0c3' + + 'e0b0ad2a244e98f54a08f873efb63c6fad14d7322b50eb05b6bae767305da92a90a' + + '53cdae52b0d81e158a00003ec626e50423b7377a34a7b28cc7483b55bfde05bd431' + + 'cfa436c38c285531e0d476ee13f151c8ae832ffd51ba00f2ab06f1844e73c0fe0f6' + + 'ce17d966b1e07727af4161368aa0a74a594a6fdb782b46a9ae6098799c366fc0d71' + + '1b2d965cf5eeeed9175b39b1d0bcefdd7df376e8ac9', 'hex'), + 'sha3-384': Buffer.from( + '002eaf5837443f1a33dc03729a308c503888d7a8cc013be424a91bce18105f7334a' + + '499a5eddc5f4fab2fdf80f52988d53bf8bd5e78c3ce1a43abaf3b8146c260b6ce8b' + + 'ffc9857f4b35c190cea85921c46d3ab573113744472d1afb637a0e9ab5021bcb355' + + '7f5b52faf89fa864a7d3bf5799096c54ee53fa139e1bc13842a2a5bf0f1d85f041d' + + 'a4e0e87425b421f22f0240ad62ef77ba6f090e0d48e17c07fd1e477c7e16a3196f5' + + '0142d0f0c5e525a10325569e5a1f50cb4577e782a643972857cc918ae5409587d9e' + + '44e1c1e89540e87deed7dda5005ac63ba609f522fdd92c81d95c1ffa383558a10f3' + + '064f59ca0534bfad31acbf3e2807cb7d3147c59ee4d', 'hex'), + 'sha3-512': Buffer.from( + '561585b621c916453762285c8bb6ede3f303074ad6f2826ca15b3900e49c4d94c07' + + 'aab0b875eaa79049ba2ed97e9a87c44fff9bffe638a1bf8c4db69c627b6adbe8fca' + + '2b38cb8b4c2810a16286bef498327b9db4b53043ed5012c7c58f037edf669baf772' + + '9b58e413e133ebb90a5fcb6dc3936f4f87971c0e85f362189b4279bbb2d9293a427' + + '5653068c1bc8772cebc4733a5d1df0b454d4f628c645c22bb1c8cc601fbc92dc091' + + 'db38fad4a36289ae9ed424c46643a8161a102ae511877d25f2eab7342dff6b92bf3' + + '65951e76ee84c2bd84a595f63d7cc04d00e1589870956491e518b3ba245efc37a28' + + 'ec018d8788a92ab93a90bb314f9ab0788a0b5b50489', 'hex') } const vectors = [ @@ -131,7 +158,31 @@ module.exports = function () { hash: 'SHA-512', plaintext, signature: signatures['sha-512'] - } + }, + { + publicKeyBuffer: spki, + privateKeyBuffer: pkcs8, + algorithm: { name: 'RSASSA-PKCS1-v1_5' }, + hash: 'SHA3-256', + plaintext, + signature: signatures['sha3-256'] + }, + { + publicKeyBuffer: spki, + privateKeyBuffer: pkcs8, + algorithm: { name: 'RSASSA-PKCS1-v1_5' }, + hash: 'SHA3-384', + plaintext, + signature: signatures['sha3-384'] + }, + { + publicKeyBuffer: spki, + privateKeyBuffer: pkcs8, + algorithm: { name: 'RSASSA-PKCS1-v1_5' }, + hash: 'SHA3-512', + plaintext, + signature: signatures['sha3-512'] + }, ]; return vectors; diff --git a/test/fixtures/crypto/rsa_pss.js b/test/fixtures/crypto/rsa_pss.js index effb3605a73f1a..101122b2ffe31c 100644 --- a/test/fixtures/crypto/rsa_pss.js +++ b/test/fixtures/crypto/rsa_pss.js @@ -61,21 +61,131 @@ module.exports = function() { const signatures = { 'sha-1, no salt': Buffer.from( - '1f1cd81ecb3bb31df2e5f0f64c5c0a310c7cf88d19eb512a5078e156d823727af88' + '68a12e5dfbfa976386784ba982c39928789b134952a4a28c6241177bcf2248f2adb' + '60077f545dd17e4f809b3b859fd430d1681e8047126d77369519eed5b618f3297a5' + '75085f0c931ed248cf60bbd7efffa0a8c2b874ba7f81ecd6bf391d01f1e881d827a' + '7b95df874d9adabb7b07f131ab33142a8b0b6d5ca9685671d49b982b67651909eaa' + '17b96b393e04fb36d972f9b258f1b79123df212d39924a4deaec506cf640f1dedd0' + '2d28845f3548d8488652788e2e2146f3ce8a86a556d84b4578f10da29abdb176a68' + '718cc1b2270b0735c2e5ca6c6bb0afac23a5bfa817a', 'hex'), + '1f1cd81ecb3bb31df2e5f0f64c5c0a310c7cf88d19eb512a5078e156d823727af88' + + '68a12e5dfbfa976386784ba982c39928789b134952a4a28c6241177bcf2248f2adb' + + '60077f545dd17e4f809b3b859fd430d1681e8047126d77369519eed5b618f3297a5' + + '75085f0c931ed248cf60bbd7efffa0a8c2b874ba7f81ecd6bf391d01f1e881d827a' + + '7b95df874d9adabb7b07f131ab33142a8b0b6d5ca9685671d49b982b67651909eaa' + + '17b96b393e04fb36d972f9b258f1b79123df212d39924a4deaec506cf640f1dedd0' + + '2d28845f3548d8488652788e2e2146f3ce8a86a556d84b4578f10da29abdb176a68' + + '718cc1b2270b0735c2e5ca6c6bb0afac23a5bfa817a', 'hex'), 'sha-256, no salt': Buffer.from( - '6157d668ed655d978b4c158c8419eb80718dfdfc7d4b34357f9917e9e116b6f3b65' + '040c9d16155c081d6887abcb3ba4ffa0191e4807ee206681aa1d4809ea20de5186b' + '77e3caced07fc9b3d71b9df0ac81b5c3273ff3f74f32a7ad34c65062a31540ced30' + '527efa4b7aa2d27ff7f80535f3e65ce352eb9e18b5054416de959354a4dcccb2542' + 'e33a8358eda620a8653dd6458f56ab94fee1dc01ef42fb8958aa134810e4d8fe1dd' + '4feee6af04742f80da5793875a78a2a4cc08d4e0a68ab03f1c022a0e8a7d3096089' + '92d24ecdd7e8f1895e3e5cd36e49906b531932d9ff958618b1a50f98455f515e0c6' + '3103d2e4e1651afc566eb9cad1e7efae1a9750c3880', 'hex'), + '6157d668ed655d978b4c158c8419eb80718dfdfc7d4b34357f9917e9e116b6f3b65' + + '040c9d16155c081d6887abcb3ba4ffa0191e4807ee206681aa1d4809ea20de5186b' + + '77e3caced07fc9b3d71b9df0ac81b5c3273ff3f74f32a7ad34c65062a31540ced30' + + '527efa4b7aa2d27ff7f80535f3e65ce352eb9e18b5054416de959354a4dcccb2542' + + 'e33a8358eda620a8653dd6458f56ab94fee1dc01ef42fb8958aa134810e4d8fe1dd' + + '4feee6af04742f80da5793875a78a2a4cc08d4e0a68ab03f1c022a0e8a7d3096089' + + '92d24ecdd7e8f1895e3e5cd36e49906b531932d9ff958618b1a50f98455f515e0c6' + + '3103d2e4e1651afc566eb9cad1e7efae1a9750c3880', 'hex'), 'sha-384, no salt': Buffer.from( - '7b95aab6b34c0962d228409e30df9b043c1b0baada08e73d887422552b8f1522e2e' + '42bf2b9ff2c6c9aa3eb0cd2370618e8f1a36873595e00bde75a9ce062ec32b5f639' + '4f2267a3f5c11840ff92e6e15bf31cc53e917ca8efc0895fb112c2ef8f681cbb6a4' + '10152f6e930caff1f260e31f983542e68cd15dea17ed3139cac735106fb05fc163b' + '2ed05a0ded939059a10c5cd7619e21b2d206907994274b34a4daefa1ce59b6b319f' + '73955a0918a5e237e1bbfdadb45c907a50083577e7192818845995b4a6d3ff1978e' + '0f9a42695853282e35c3b78133b3e0c624125aff14a1873d198f6304ffec7fc1cf2' + 'adecc6cd14b1f89b1a637f72ed1ff5de7c6b4d96599', 'hex'), + '7b95aab6b34c0962d228409e30df9b043c1b0baada08e73d887422552b8f1522e2e' + + '42bf2b9ff2c6c9aa3eb0cd2370618e8f1a36873595e00bde75a9ce062ec32b5f639' + + '4f2267a3f5c11840ff92e6e15bf31cc53e917ca8efc0895fb112c2ef8f681cbb6a4' + + '10152f6e930caff1f260e31f983542e68cd15dea17ed3139cac735106fb05fc163b' + + '2ed05a0ded939059a10c5cd7619e21b2d206907994274b34a4daefa1ce59b6b319f' + + '73955a0918a5e237e1bbfdadb45c907a50083577e7192818845995b4a6d3ff1978e' + + '0f9a42695853282e35c3b78133b3e0c624125aff14a1873d198f6304ffec7fc1cf2' + + 'adecc6cd14b1f89b1a637f72ed1ff5de7c6b4d96599', 'hex'), 'sha-512, no salt': Buffer.from( - 'af1bc07fa70add19f3ce1f1bef8dfc6e24af43671cfb97e6b869e86b7ef03550a65' + '81318fff6449afa8b67e73e2a6a14e20677d8b067145a84422574ae0cfd2a5dff70' + 'c6d7e97f6a0e166505079eb4264a43c493f2eb3fb06facc01be60774c277646a280' + '81247679622b220227e9249754867aa8fe1804015c4f98700982eda40e84d0ba033' + '6cf44f582fb8781374804e8fb43eb9d577acf4723587a39a2b4a9e168b767632b7a' + '554f77bc5272821c938c0994b162f7482636f7ffac564a19bd733f4877801dc324d' + 'c47196ef12ca9a8f4921a5496cd6737935ca555b73466ddd817eaff03feda0eb2d6' + '12e3cdb59b1989eeffdc18101d46e56b9ff5c91f95d', 'hex'), + 'af1bc07fa70add19f3ce1f1bef8dfc6e24af43671cfb97e6b869e86b7ef03550a65' + + '81318fff6449afa8b67e73e2a6a14e20677d8b067145a84422574ae0cfd2a5dff70' + + 'c6d7e97f6a0e166505079eb4264a43c493f2eb3fb06facc01be60774c277646a280' + + '81247679622b220227e9249754867aa8fe1804015c4f98700982eda40e84d0ba033' + + '6cf44f582fb8781374804e8fb43eb9d577acf4723587a39a2b4a9e168b767632b7a' + + '554f77bc5272821c938c0994b162f7482636f7ffac564a19bd733f4877801dc324d' + + 'c47196ef12ca9a8f4921a5496cd6737935ca555b73466ddd817eaff03feda0eb2d6' + + '12e3cdb59b1989eeffdc18101d46e56b9ff5c91f95d', 'hex'), 'sha-1, salted': Buffer.from( - '1f608a71d1884cfe2183b49037aa8555b0139a8a1267a5c5b9cce20701f2ad4bbd5' + 'b329740bff31accc34bf9afd1439a0536bb32b6d427d26968dbc9e9c80d2111d948' + 'c481cb1731778acd3110463241c4f23b3e13b855d162cb153851290fd95f781519e' + '2cef93745a413cfeec8e94fba7822b725d4744318458cf6b4a917b65b15ee6f54b9' + 'c391f6064a9e031f7009f592449c0b46d5457a2799cb0ebd78a102a055ee0470b26' + '0c2b3d8ffbdee0fd47644822090ec55ae6233be1062f441c432ed3c275e74d62013' + '2681ec2e801e9b5b6acc1ad71f8935388f7e2c03370d12e944e3418c2ab63bb42ab' + 'e1bb9e69530f02458ba28400b36806ff78da5791ace', 'hex'), + '1f608a71d1884cfe2183b49037aa8555b0139a8a1267a5c5b9cce20701f2ad4bbd5' + + 'b329740bff31accc34bf9afd1439a0536bb32b6d427d26968dbc9e9c80d2111d948' + + 'c481cb1731778acd3110463241c4f23b3e13b855d162cb153851290fd95f781519e' + + '2cef93745a413cfeec8e94fba7822b725d4744318458cf6b4a917b65b15ee6f54b9' + + 'c391f6064a9e031f7009f592449c0b46d5457a2799cb0ebd78a102a055ee0470b26' + + '0c2b3d8ffbdee0fd47644822090ec55ae6233be1062f441c432ed3c275e74d62013' + + '2681ec2e801e9b5b6acc1ad71f8935388f7e2c03370d12e944e3418c2ab63bb42ab' + + 'e1bb9e69530f02458ba28400b36806ff78da5791ace', 'hex'), 'sha-256, salted': Buffer.from( - '8c3d03bde8c42d9453631b0baac89e6296da20543713c004df35bc1a6fae205ab2b' + 'f585369689073cdee345ad6e2783b2dda187b4979ea0457463758156e103eedd0ef' + '1834d35bd6ad540d9b8b225fd1770e514ea0af35f707f2e7a0382be6f5ed9d6b591' + 'd536ce1215b17ef3eeb450bb48a0017497c67be0240470addd2891a81a8f1cf6e80' + 'e3f837fe42376292df555b8b05931b69530597fae36dcd01b1c81767d4ecd4caf06' + 'befc035224bdd2a5e6b89d51539235ac95570e757dbd70fdc15040001b07b937bf0' + '148ccc005f4c272acf5f8fc096a37d26208e96ac341c2d1d212c44d6d5156c934f6' + '6ef42fdbac77a208681550b048b466e32c76c7a7b07', 'hex'), + '8c3d03bde8c42d9453631b0baac89e6296da20543713c004df35bc1a6fae205ab2b' + + 'f585369689073cdee345ad6e2783b2dda187b4979ea0457463758156e103eedd0ef' + + '1834d35bd6ad540d9b8b225fd1770e514ea0af35f707f2e7a0382be6f5ed9d6b591' + + 'd536ce1215b17ef3eeb450bb48a0017497c67be0240470addd2891a81a8f1cf6e80' + + 'e3f837fe42376292df555b8b05931b69530597fae36dcd01b1c81767d4ecd4caf06' + + 'befc035224bdd2a5e6b89d51539235ac95570e757dbd70fdc15040001b07b937bf0' + + '148ccc005f4c272acf5f8fc096a37d26208e96ac341c2d1d212c44d6d5156c934f6' + + '6ef42fdbac77a208681550b048b466e32c76c7a7b07', 'hex'), 'sha-384, salted': Buffer.from( - '79f7284bb4216de68429854edb4218ef78ad1740848567377315db8867a15733c70' + '42e8bf90762e673c90c0e2c58c6c5cef497568bd92a6d219612c4756c55fac45507' + 'f81608bc2720da4eedd5b23e1f3c8740c6b4cd7e4cf0e04342b184c1110199e6508' + '0d73b985e611d66f8e97990816e4917badbb0425dd94383892e2aa96de4db0de093' + '6aee84d5482a3da31b27319f43830fc48703cc7d4eaedb20fd30323dbf3f22608db' + '51637d3b305b3197962658d80935c266d33ccfb297590621f4a967c7245e92b0158' + 'c0dcea943e2ace719ebdb196a9bae7df3ed9cc62765e27b63571743e28a0538db08' + '25cad2539eb5de5e6a320a88b573ec1972c26401530', 'hex'), + '79f7284bb4216de68429854edb4218ef78ad1740848567377315db8867a15733c70' + + '42e8bf90762e673c90c0e2c58c6c5cef497568bd92a6d219612c4756c55fac45507' + + 'f81608bc2720da4eedd5b23e1f3c8740c6b4cd7e4cf0e04342b184c1110199e6508' + + '0d73b985e611d66f8e97990816e4917badbb0425dd94383892e2aa96de4db0de093' + + '6aee84d5482a3da31b27319f43830fc48703cc7d4eaedb20fd30323dbf3f22608db' + + '51637d3b305b3197962658d80935c266d33ccfb297590621f4a967c7245e92b0158' + + 'c0dcea943e2ace719ebdb196a9bae7df3ed9cc62765e27b63571743e28a0538db08' + + '25cad2539eb5de5e6a320a88b573ec1972c26401530', 'hex'), 'sha-512, salted': Buffer.from( - 'b74f3099d80787118b1f9de79fc207893e0d2d75c4110f4b159b85ba07d63a0256f' + 'c3cd0f66ce8d9a2e3cf7a3d5a7b9c0befac6638894a3e36ce75e649ee069dd8dd98' + 'aa8b602474c98b14bb03492de551a9e8e77934ef9b684583934f218d9576be240b5' + 'c4f362eaf5e0140c8ea92639085a6269653505dcfa004226db9f63277653a64a182' + '6e4babb17ab54dd8543dcf1ce809706d6816e6a75ff846a3d4c18d11bdeb1f31b10' + 'd55a3795b6496319e6e751504d86a4e7bb6535b9f0415e815d8c789c5b1e387f2a8' + 'c00fef6e327462cb7e525b8f945be5b17248e0e0a4d855d397e22d067ce4539373d' + 'fba46d1799250afc70f535006cacd2766f5ddcf8f91', 'hex') + 'b74f3099d80787118b1f9de79fc207893e0d2d75c4110f4b159b85ba07d63a0256f' + + 'c3cd0f66ce8d9a2e3cf7a3d5a7b9c0befac6638894a3e36ce75e649ee069dd8dd98' + + 'aa8b602474c98b14bb03492de551a9e8e77934ef9b684583934f218d9576be240b5' + + 'c4f362eaf5e0140c8ea92639085a6269653505dcfa004226db9f63277653a64a182' + + '6e4babb17ab54dd8543dcf1ce809706d6816e6a75ff846a3d4c18d11bdeb1f31b10' + + 'd55a3795b6496319e6e751504d86a4e7bb6535b9f0415e815d8c789c5b1e387f2a8' + + 'c00fef6e327462cb7e525b8f945be5b17248e0e0a4d855d397e22d067ce4539373d' + + 'fba46d1799250afc70f535006cacd2766f5ddcf8f91', 'hex'), + 'sha3-256, no salt': Buffer.from( + '98787732f107a5390abc9ba3c93c2a0e30f6f31c3c76d73afee951a04525897df94' + + '67c7532ff1b5c12601369339edcac4654a173e61780a12a21b5f0500bf16e2445f9' + + 'f7e9adab1ea2bb7e901f615b514965047d53b12ff2ff19f94f320179946bbf1b19d' + + '88248e4fba7f49dc3c5af14de7a892a7718bd5962db33aa2b529c49e75d8fe936de' + + '45e1db225ed875486516cd7398b5ec19043cf6005e06ba2d60f807d34d4ced378ae' + + 'cef2b1f75f6ad52cdd674d944e48d484be2e5f799510f244d089eb3570a674b2585' + + '0616f12641e7e3e38e36fba1eefbed32d7a4809a4b5b1e557582303ab419bc128a1' + + '813857157985f075d5d89e6867b7864dac0369b2513', 'hex'), + 'sha3-384, no salt': Buffer.from( + '1d1399da211efc709b2cea90ff65e7c49162d943cadb59c78186889f7645e5d08f7' + + '490de3f7b65ee5664c140dd61334182ddf45bcdc844845e4d60c917bf00eb1321e7' + + '46cd7fce971af5ceea60b272219ccda2328b89a11b228cd42bdcc4c7eb40f0b6333' + + '5f7496931baf36c0d2497045687ad27bb156c20f7fdae3baa38d57e35918f328bdd' + + '2de7e6b23d6c676a18342a082f7ea019021903f103ccb4a6fddb2d88c30a1284764' + + 'b68c04bfe452c3adc6c10066a915231b7b404727eb6201b4921eb96d9407de2b963' + + '3879ceb71d759d9828d7b4d062f6ef100757d8328187caf57dfb859d1555345207c' + + '1cce7905c3564c08fec78867a53d5a2cf84810e1ffa', 'hex'), + 'sha3-512, no salt': Buffer.from( + 'd2430dc87abeaa7d13f7cec8510f1a296e1c608f44b1696829c59a99e8eefe9b2ee' + + '6ee8ad6fdc93c24fcba2f04d1da195924b6209717e1992c10ed9f4783478765fe34' + + '3e761203bff9d326bb6dc2061b0a7554c8ce0814b29249136c20c8e30054df0c6bc' + + '656509a82845149368896690e32ff5dd32ef01543686f01d6a69bb438b049e66a8b' + + 'df485a13edcd7dc482da4cc57d0b740aca3e56f0da247794e600afd27b22b6da13b' + + 'cc15dd2059b525f8cb6bcd07540aa843f0ae51d4b0eea27045485914b908bdd01d0' + + 'a9d42379f9f7180f4ad162ff73df5fed0200eb02ad01473975d54a77c15a9c61a3c' + + 'b5e27de5d1eecc363d45506f7123a5ddd115c5e4c9e', 'hex'), + 'sha3-256, salted': Buffer.from( + '59cb9cce6ae838eb20d38d6af4acb9b866b0753bb7df9e441037d788512c03279e8' + + '3d9a9cf5c0921fe1c0b6e8e895a8c0ad24a18b123f809b34ef2a3a1f05974030320' + + '435692ef5d378cef4368c3658c098a25371dfaf1c0b6910f653a4ec15f2c08956c1' + + '405136c2aba7f25a808fa7dbf57a4cb2978bd91af710b27ee239d955c8cac7a76ae' + + '9085cefeda2a585a99cc948f064b5da66a9c4aa4f3f767ac905a9f314b47038e05c' + + '3608fbb7e67a278e4f009a62c3cd3fdf43692e759d9361be1217999a76a69d4d119' + + 'f8791a90e207e46b3f6125721f56fd819292d06a3cdae2c62c9a1dc0d964a06036c' + + '8c18661cc6c873532a3536ab51e1ce210926db299e2', 'hex'), + 'sha3-384, salted': Buffer.from( + '8d1f9297c8169f27f0c58827dba991d862de58c1155f612ad2995d2bf862d051c4a' + + '91b48571849b0412384382e5b77990de6a3c84010046b35c4a504f175a3479483d9' + + '5c58f86bb96d53a27e59d6f67fddaae295ce90610f5086acc711557c2c85aac32d3' + + '24199cff2367ae44e1d91307a98c8cbfb085a8bce6b1c20714711bc15b0eddb7881' + + '823227d4be477ffdad8093663a6a1fc62eb39c49c2c3a821c2b202cf7904b49ca92' + + '3c83819602bb13931577354a80f99309030044935b1cd41f0513160e661db1959fb' + + '1ec15f087f3d288e875d54cbf070ec860b0aeecc951ea65e97cd5460750d4b7de52' + + '22cb9e7466b1f506ecf6a81fc399dfd8334160f9084', 'hex'), + 'sha3-512, salted': Buffer.from( + '7b6d7be418c5d37cc8070698b8b03d818ecd8b673d047d34921913f6d59c69cb496' + + '172d6118207d9ff92b8e1246acf0e03a845d935a70f8a82c3d5d6db6a1a0e337269' + + '4b904372413dcbaa7ac5486bc8ccaf70d7e9470be82b928a90017e272cf9761ed26' + + 'c160fe874a2675a4fb2acad72c50fbfffdd70b5a6f2919553d7ea1829934670f8de' + + 'f2a5c2816404b1aa153323c92c58400622f184b9b0463fa48d6b27091f68c287e3f' + + '6d9ab9eb451711a5d984c547f3d56f14a686a89ddf36c47ce25092b8c6530904de9' + + '5df7fc602fe9394315f1b1847aae304cb5ad71e2cb78acfbc997a87a9d62a6898bb' + + '6d84a81bb89b50186265f4be171a93d837a4bf777c8', 'hex') } const vectors = [ @@ -142,6 +252,54 @@ module.exports = function() { hash: 'SHA-512', plaintext, signature: signatures['sha-512, salted'] + }, + { + publicKeyBuffer: spki, + privateKeyBuffer: pkcs8, + algorithm: { name: 'RSA-PSS', saltLength: 0 }, + hash: 'SHA3-256', + plaintext, + signature: signatures['sha3-256, no salt'] + }, + { + publicKeyBuffer: spki, + privateKeyBuffer: pkcs8, + algorithm: { name: 'RSA-PSS', saltLength: 0 }, + hash: 'SHA3-384', + plaintext, + signature: signatures['sha3-384, no salt'] + }, + { + publicKeyBuffer: spki, + privateKeyBuffer: pkcs8, + algorithm: { name: 'RSA-PSS', saltLength: 0 }, + hash: 'SHA3-512', + plaintext, + signature: signatures['sha3-512, no salt'] + }, + { + publicKeyBuffer: spki, + privateKeyBuffer: pkcs8, + algorithm: { name: 'RSA-PSS', saltLength: 32 }, + hash: 'SHA3-256', + plaintext, + signature: signatures['sha3-256, salted'] + }, + { + publicKeyBuffer: spki, + privateKeyBuffer: pkcs8, + algorithm: { name: 'RSA-PSS', saltLength: 48 }, + hash: 'SHA3-384', + plaintext, + signature: signatures['sha3-384, salted'] + }, + { + publicKeyBuffer: spki, + privateKeyBuffer: pkcs8, + algorithm: { name: 'RSA-PSS', saltLength: 64 }, + hash: 'SHA3-512', + plaintext, + signature: signatures['sha3-512, salted'] } ]; diff --git a/test/fixtures/keys/Makefile b/test/fixtures/keys/Makefile index 7ab71970483183..e0d488e56a1533 100644 --- a/test/fixtures/keys/Makefile +++ b/test/fixtures/keys/Makefile @@ -110,6 +110,18 @@ all: \ ml_dsa_87_private_seed_only.pem \ ml_dsa_87_private_priv_only.pem \ ml_dsa_87_public.pem \ + ml_kem_512_private.pem \ + ml_kem_512_private_seed_only.pem \ + ml_kem_512_private_priv_only.pem \ + ml_kem_512_public.pem \ + ml_kem_768_private.pem \ + ml_kem_768_private_seed_only.pem \ + ml_kem_768_private_priv_only.pem \ + ml_kem_768_public.pem \ + ml_kem_1024_private.pem \ + ml_kem_1024_private_seed_only.pem \ + ml_kem_1024_private_priv_only.pem \ + ml_kem_1024_public.pem \ # # Create Certificate Authority: ca1 @@ -903,6 +915,42 @@ ml_dsa_87_private_priv_only.pem: ml_dsa_87_private.pem ml_dsa_87_public.pem: ml_dsa_87_private.pem openssl pkey -in ml_dsa_87_private.pem -pubout -out ml_dsa_87_public.pem +ml_kem_512_private.pem: + openssl genpkey -algorithm ml-kem-512 -out ml_kem_512_private.pem + +ml_kem_512_private_seed_only.pem: ml_kem_512_private.pem + openssl pkey -in ml_kem_512_private.pem -provparam ml-kem.output_formats=seed-only -out ml_kem_512_private_seed_only.pem + +ml_kem_512_private_priv_only.pem: ml_kem_512_private.pem + openssl pkey -in ml_kem_512_private.pem -provparam ml-kem.output_formats=priv-only -out ml_kem_512_private_priv_only.pem + +ml_kem_512_public.pem: ml_kem_512_private.pem + openssl pkey -in ml_kem_512_private.pem -pubout -out ml_kem_512_public.pem + +ml_kem_768_private.pem: + openssl genpkey -algorithm ml-kem-768 -out ml_kem_768_private.pem + +ml_kem_768_private_seed_only.pem: ml_kem_768_private.pem + openssl pkey -in ml_kem_768_private.pem -provparam ml-kem.output_formats=seed-only -out ml_kem_768_private_seed_only.pem + +ml_kem_768_private_priv_only.pem: ml_kem_768_private.pem + openssl pkey -in ml_kem_768_private.pem -provparam ml-kem.output_formats=priv-only -out ml_kem_768_private_priv_only.pem + +ml_kem_768_public.pem: ml_kem_768_private.pem + openssl pkey -in ml_kem_768_private.pem -pubout -out ml_kem_768_public.pem + +ml_kem_1024_private.pem: + openssl genpkey -algorithm ml-kem-1024 -out ml_kem_1024_private.pem + +ml_kem_1024_private_seed_only.pem: ml_kem_1024_private.pem + openssl pkey -in ml_kem_1024_private.pem -provparam ml-kem.output_formats=seed-only -out ml_kem_1024_private_seed_only.pem + +ml_kem_1024_private_priv_only.pem: ml_kem_1024_private.pem + openssl pkey -in ml_kem_1024_private.pem -provparam ml-kem.output_formats=priv-only -out ml_kem_1024_private_priv_only.pem + +ml_kem_1024_public.pem: ml_kem_1024_private.pem + openssl pkey -in ml_kem_1024_private.pem -pubout -out ml_kem_1024_public.pem + x448_private.pem: openssl genpkey -algorithm x448 -out x448_private.pem diff --git a/test/fixtures/keys/ml_kem_1024_private.pem b/test/fixtures/keys/ml_kem_1024_private.pem new file mode 100644 index 00000000000000..0daacd8379d0c8 --- /dev/null +++ b/test/fixtures/keys/ml_kem_1024_private.pem @@ -0,0 +1,71 @@ +-----BEGIN PRIVATE KEY----- +MIIMvgIBADALBglghkgBZQMEBAMEggyqMIIMpgRAXwegslUGcMnzu1c1NccsyRmB +aObWSESIGO+ftccj5MvAwjLDAJV/u5KhD/rnJLoLOu0eNGt4p3Oh6Mg5ttxICwSC +DGC6yqqzFn2iimHWUAJ2YSO9yXGDFmUd95txwKHE6UP46qy3gU0zazze90sNekNS +KsbeAVGIFarJLHF2tYKpUVw6CyXqR7+c1KJtGGFtF29xzGOi/CvpMx632HD4M1KT +uVLXkSYTCgffBlHdcG10ysQg26VlC0UckAhiGEBUQBQL2DRlvLuR5RfVNgiSdAUU +6rILpwGsZVY+MxbCwcU4IZVSuxq1QHpIxltZl2ZqEVQe/Gz1PJyyE34wKk4teB0g +5j1MNBd8ZHMcG2gGmyeJZkHSgbyMhrod1ylwvABY6LqG6r6J9n0BUlwdJHvsolge +qRzFzJrnpJE9C2Er836NRjdJKJrZUrfRsHy22XUIZiCyW0QwGY6pUGvWZ7c7+xxW +NTA/Ikz2gl8KUmUya0WOpGzBZMGGZlEWVm5pXHMj+WHi63TZdwIpDF0h+cBhMYa+ +S8yZyTF6dlz3dkoHpLVjoYJBY6jmIRrUxSz/tiWkM21hAj9G5Zl8EBibcV9MWGcH +27wdtDaZIUAlhpKC24AOHIx8ZztV0CiSvIAd6biRpzzhbGzBNQr1GbZQtc0nUETm +y1x5gTu22YBCN8fB0WVsV8AHMGC5prgZN5Yt8JPSVyiAmpqw0QFj4EXEcnwCDHd4 +mqp4QitB/C8OpcKCBm2h91ECMpevJ49E1xe11UIFiLpaFy0jxxAgGYhRy3XXfM71 +PKuzcb+MMgGnBw9T6sMQts/JiHp78Ya6nL2bJ57DNHrt9zA255nYPI0zdKkMi8W5 +VFIgORRUSsGAKIaPayMagI6Nw2lHYoxtmr3ZmGf17LEZ2bK4RF2Q4ZN0iaUAWD6v +9QbeQLeYZEiwyysty7NLV3ArqWQBcl1AMnx2hphd5hHIu55ZmIheapLRpsQAosz1 +m67Mhx4uylTzpIOGhZl0CwjK6rkXYzGKzHLcac3JRwrq8HKrJaTWoaTj9Qaq5k61 +Eo4mtaB463hHx5Q+SlzY8blBWzWqXIWikrvESSC32XwA+cezZXOYIAIM+Q+1dDSe +VxCVpVcwF0g8MIHzonhfFgQiOYHZhmd1uR5bUc09QBRqpctUMMn4sbSexVmhAUM+ +WVnJhprIOkTSDB9QiSWZQ07+gTI+qYuKwT3QBGc2G4bZqgQhHIMzqLsTZpEmG3gD +0cC8GSIceTfvkUUgGBR5iQ8Cu8TpFTyfO8N7dxCqZ59X16FKMLwOWnbsJEM94WoB +AUBbJl9UB0dx24lvY6czAH7wxxVW6iQidHmp2VX4oqTEFAclOiQxFIh8/G0jyj2l +WAKEl2vagmj5vCB50DIEEjda+7xZia/MTLCHFX3yAcXn0hRdIlkvMlTCcmXHMLI2 ++6vCIjKhqXoj9HLb+GKk6ErGk5sapp7pXMYWFlTchjnszMOYUope2Sem6gW9FHYN +KWLVOSAEIG5kZDxw4lU1aaMtBSyVGIA0C66rIRAdEmjD9IuftDif/D8ABylnupCy +ZsXvRiIoa1b0mGOdNVXppl8hNJaW91yd+cIQiKisd7cGnDj2iBQ2trgsfKdWYLgC +RE+fMGxgeXZBW0j6pTNXtQsJshkpyciqSKk7JDIJ1px+AKVEyA/ISD51Q2svSjb8 +YknxSGEdA5TIEwzMvLZccLwGhFkZFMZ+1ceReb/wpKiTA5rHXKMDMCQVjBbNlQaq +W6MP8w2lXK1cVlWf6VmtCqfT/Dv3eCT2vAinfDL1i3KxRIR0aon1BzSK5nU1Rocq +2hMcVT9gZLu1iGs0W7oVQFPbC45o3G/a57VMis5wqwDcRjQzx2bAhFrcpHNkhmrI +Q283pK9zRBwy5V7b8avxi8zQGQJSM7X7yaYEmxoQZhnDhZ8XYaCAEqJT90oolErA +6qs2Rc9Vawi5IZ/fLEvHKYTDNmv082S5CEvQSpX0ogbLGQ0E+hjKqhCnLKtNeHPo +UwJyPH+UFat65D5CAQ42WJNLdzAoB77U54zh6wj6VQPX9GQ4pmfTWkVJ7C8Do4Fn +kY5L8rSLFI++BJEjzAYxcoJlrA2gB3QwvDYeOMY1ojZduMhz1iajGn7xC1Vus2wQ +qSwN14Enh6q9lyYxaF6hVYs2EQXzVrgZkKsTdlzZo2DK9jRp0nJyIISwWwS9/LdZ +JcC5GQKHHJm1qV7VkLRFxVei4VscS3qYCKbN+BpcCiMSRXKWk4g6mk9RmRXwhGJn +gi60+6sM2pFd1jd9CZhbATFVIV+hIUOBOCcJS8q9OTv3JCg1C5w5Fn0cXA+P8rv5 +OnIZuALsYSUy26LisEdKfLQEIkxl5p8bzCor0FxdE3TpKbGpE2qxHH0csk3CmGvs +gVwyVlMIjGvGgxyR+Qx7QGJVICNU1ogHlG9yIltyZHbGpk8Rg1NVUoC4jJom+Ysd +AS/45sp/BSfPpY3Q0ZmOGnv8pgDFijBeOm5OEwYa65JtXIaHwljMIzHaeD9PRnF4 +Mb+UByYcpFOlx3M1OFeey6H1hXMPppCnIJxpirF4ZVCh7H565ATI9zo+pyzJ8ctb +GWA+6gLHin8EC6ckIBLP+2MKVSF6JBwJpgCYyZLKU4SVDCyi91f27L804n0GZj1w +NIw+domEWG8jEVHWM2DT1kK5dFgkOIjJYErZzMDe+31lWj2jVCczVJmO9jIKgE7b +cscZJkq4fD8hI55Nwy/vqoT/iQ2WlcuS0HHFeY80EnutkL/Fp6a+QZLzOMZfYGp/ +twYPMpBsSFaTei+m1XUWY3ZfKRHsOQRruk6fqIqkIHnII6vgAczRWIqqhrtnczKx +KKdOJqcQpWxUsEfRPAq3hzyDSKEJBohNM60XZC2NgxIgbMeepU1pwnzI3ENFUsE6 +pjsbapctOnxhS5nZ8Jd68FwJBhA15hCVZRG08y+2Ii+kiIhmBzvfZ8UZOFnfxsfj +EbkpHEPXGrWAtZBnCRgW+GgVI2xVKl+MgklJo8PvqcuT8p8CVAylBjRJcLOMoZV6 +h6FPt4evClDL6hmhOEKZhlMjwTmSFMKTqHx+KzxYDAqdMotVW4cvYFUNlUbfiWne +5H9tQY0O2A1CMlbUty0LprDPScC2xlRW6RuzhUz8y36TNkE+2gM91MNkoYYaJDdQ +kC7OKw2B2iaKWg4WVYNYCZK9xgT+oBWdXDrotyEy2QZmYQZIJa0hanFCU0M29jSe +rAZSHMfyu6qWy2LfSMq7zITz8nZF2Q8qhCt7clxCVmdZos6+gn2eGmVztSAb97bb +3HVeCJNJLL+8tMU9aD+ikzpmEYPAxnAUw5GBwTGPYcSeWTJUiZl4oC1T4ZEKAIEo +U8smI14tIJ80VIa7U0WgFg/GQ4EKtQJfmVm3lSbWtil56ZHmi0rsqH+0YkhBSWiB +4wlMgZLt1kvPQSKaYZpp5cDCE3kk8FeSIo+FWFdfi1hve5pJixBu1GLDRF0SHKyn +oTJSuiV/46o4TBuIsmgmI3eGBw/625fxlch2E2abIzMPiUh2lmaSQDEP0zrgKQrp +gUO2EICQe3YTqqAHaWLCLBtc12pTuAf31XbCXJxisoxoolTnHLPJpAwxQyxxqkYH +wYZEo2FUnDPORrYDEba0qj6aGWUjxmtxC4c4xCjbWMliShvrGA6EW6UCVn7+JxNA +Z1nj6DqnOTjSKH2sNZ/FRjIk03rv6DnSO8sg4mslARiNyDhNcMG4VqEy1M2o6m8B +rMcUN6pXWJDPSYcGkww+8aL4azka8EbiQryb10xcW1Qe9FtOh6ZbaMDNcWPOZbQE +SRaz5Yy9vAEvenyYSUdnO41kKynkSkTMioOyCS28scJrkhU3kKs9TMkL1WcxhisN +cgEXjI4ua0XFCbFB0XxFQlzQ7LVooIDRtyGSK4siJbfm00x3rAMUly2kugwbEiU2 +2GhZNhhh4U0AR6m9l07+Izg0F3mUAJL2mrK69XHkaYakVKw8eXK4xYwdC7Ifm1/m +0IxuBMKQWy8DGjRq21pJWRgrpq1IGVLxcHSktCp39yPhqA7LlhItsAKmwjYp8nV6 +koWDIHbv/JV/tprjFC3ySy77KauAG59WOD3ak3MbZ1ILRZlHiyZL9GZPFkmO8FNU +Fs3CuKWmVlE4e1aOOQgE3LM1s27sOzVz4mjtYDf1YTRrDJhzgRUhMbWSE1LmhQuD +4Be9dO3BeZSfsa0bFe2V8GkTftEmGbbU9iXtZCmkbdzQ+X9UGO0iaD48tdL3YyIk +9kSBP9gy0nz+Aak/tD5v+9XiwMIywwCVf7uSoQ/65yS6CzrtHjRreKdzoejIObbc +SAs= +-----END PRIVATE KEY----- diff --git a/test/fixtures/keys/ml_kem_1024_private_priv_only.pem b/test/fixtures/keys/ml_kem_1024_private_priv_only.pem new file mode 100644 index 00000000000000..5e8bdf80d9c213 --- /dev/null +++ b/test/fixtures/keys/ml_kem_1024_private_priv_only.pem @@ -0,0 +1,69 @@ +-----BEGIN PRIVATE KEY----- +MIIMeAIBADALBglghkgBZQMEBAMEggxkBIIMYLrKqrMWfaKKYdZQAnZhI73JcYMW +ZR33m3HAocTpQ/jqrLeBTTNrPN73Sw16Q1Iqxt4BUYgVqskscXa1gqlRXDoLJepH +v5zUom0YYW0Xb3HMY6L8K+kzHrfYcPgzUpO5UteRJhMKB98GUd1wbXTKxCDbpWUL +RRyQCGIYQFRAFAvYNGW8u5HlF9U2CJJ0BRTqsgunAaxlVj4zFsLBxTghlVK7GrVA +ekjGW1mXZmoRVB78bPU8nLITfjAqTi14HSDmPUw0F3xkcxwbaAabJ4lmQdKBvIyG +uh3XKXC8AFjouobqvon2fQFSXB0ke+yiWB6pHMXMmuekkT0LYSvzfo1GN0komtlS +t9GwfLbZdQhmILJbRDAZjqlQa9Zntzv7HFY1MD8iTPaCXwpSZTJrRY6kbMFkwYZm +URZWbmlccyP5YeLrdNl3AikMXSH5wGExhr5LzJnJMXp2XPd2SgektWOhgkFjqOYh +GtTFLP+2JaQzbWECP0blmXwQGJtxX0xYZwfbvB20NpkhQCWGkoLbgA4cjHxnO1XQ +KJK8gB3puJGnPOFsbME1CvUZtlC1zSdQRObLXHmBO7bZgEI3x8HRZWxXwAcwYLmm +uBk3li3wk9JXKICamrDRAWPgRcRyfAIMd3iaqnhCK0H8Lw6lwoIGbaH3UQIyl68n +j0TXF7XVQgWIuloXLSPHECAZiFHLddd8zvU8q7Nxv4wyAacHD1PqwxC2z8mIenvx +hrqcvZsnnsM0eu33MDbnmdg8jTN0qQyLxblUUiA5FFRKwYAoho9rIxqAjo3DaUdi +jG2avdmYZ/XssRnZsrhEXZDhk3SJpQBYPq/1Bt5At5hkSLDLKy3Ls0tXcCupZAFy +XUAyfHaGmF3mEci7nlmYiF5qktGmxACizPWbrsyHHi7KVPOkg4aFmXQLCMrquRdj +MYrMctxpzclHCurwcqslpNahpOP1BqrmTrUSjia1oHjreEfHlD5KXNjxuUFbNapc +haKSu8RJILfZfAD5x7Nlc5ggAgz5D7V0NJ5XEJWlVzAXSDwwgfOieF8WBCI5gdmG +Z3W5HltRzT1AFGqly1QwyfixtJ7FWaEBQz5ZWcmGmsg6RNIMH1CJJZlDTv6BMj6p +i4rBPdAEZzYbhtmqBCEcgzOouxNmkSYbeAPRwLwZIhx5N++RRSAYFHmJDwK7xOkV +PJ87w3t3EKpnn1fXoUowvA5aduwkQz3hagEBQFsmX1QHR3HbiW9jpzMAfvDHFVbq +JCJ0eanZVfiipMQUByU6JDEUiHz8bSPKPaVYAoSXa9qCaPm8IHnQMgQSN1r7vFmJ +r8xMsIcVffIBxefSFF0iWS8yVMJyZccwsjb7q8IiMqGpeiP0ctv4YqToSsaTmxqm +nulcxhYWVNyGOezMw5hSil7ZJ6bqBb0Udg0pYtU5IAQgbmRkPHDiVTVpoy0FLJUY +gDQLrqshEB0SaMP0i5+0OJ/8PwAHKWe6kLJmxe9GIihrVvSYY501VemmXyE0lpb3 +XJ35whCIqKx3twacOPaIFDa2uCx8p1ZguAJET58wbGB5dkFbSPqlM1e1CwmyGSnJ +yKpIqTskMgnWnH4ApUTID8hIPnVDay9KNvxiSfFIYR0DlMgTDMy8tlxwvAaEWRkU +xn7Vx5F5v/CkqJMDmsdcowMwJBWMFs2VBqpbow/zDaVcrVxWVZ/pWa0Kp9P8O/d4 +JPa8CKd8MvWLcrFEhHRqifUHNIrmdTVGhyraExxVP2Bku7WIazRbuhVAU9sLjmjc +b9rntUyKznCrANxGNDPHZsCEWtykc2SGashDbzekr3NEHDLlXtvxq/GLzNAZAlIz +tfvJpgSbGhBmGcOFnxdhoIASolP3SiiUSsDqqzZFz1VrCLkhn98sS8cphMM2a/Tz +ZLkIS9BKlfSiBssZDQT6GMqqEKcsq014c+hTAnI8f5QVq3rkPkIBDjZYk0t3MCgH +vtTnjOHrCPpVA9f0ZDimZ9NaRUnsLwOjgWeRjkvytIsUj74EkSPMBjFygmWsDaAH +dDC8Nh44xjWiNl24yHPWJqMafvELVW6zbBCpLA3XgSeHqr2XJjFoXqFVizYRBfNW +uBmQqxN2XNmjYMr2NGnScnIghLBbBL38t1klwLkZAoccmbWpXtWQtEXFV6LhWxxL +epgIps34GlwKIxJFcpaTiDqaT1GZFfCEYmeCLrT7qwzakV3WN30JmFsBMVUhX6Eh +Q4E4JwlLyr05O/ckKDULnDkWfRxcD4/yu/k6chm4AuxhJTLbouKwR0p8tAQiTGXm +nxvMKivQXF0TdOkpsakTarEcfRyyTcKYa+yBXDJWUwiMa8aDHJH5DHtAYlUgI1TW +iAeUb3IiW3JkdsamTxGDU1VSgLiMmib5ix0BL/jmyn8FJ8+ljdDRmY4ae/ymAMWK +MF46bk4TBhrrkm1chofCWMwjMdp4P09GcXgxv5QHJhykU6XHczU4V57LofWFcw+m +kKcgnGmKsXhlUKHsfnrkBMj3Oj6nLMnxy1sZYD7qAseKfwQLpyQgEs/7YwpVIXok +HAmmAJjJkspThJUMLKL3V/bsvzTifQZmPXA0jD52iYRYbyMRUdYzYNPWQrl0WCQ4 +iMlgStnMwN77fWVaPaNUJzNUmY72MgqATttyxxkmSrh8PyEjnk3DL++qhP+JDZaV +y5LQccV5jzQSe62Qv8Wnpr5BkvM4xl9gan+3Bg8ykGxIVpN6L6bVdRZjdl8pEew5 +BGu6Tp+oiqQgecgjq+ABzNFYiqqGu2dzMrEop04mpxClbFSwR9E8CreHPINIoQkG +iE0zrRdkLY2DEiBsx56lTWnCfMjcQ0VSwTqmOxtqly06fGFLmdnwl3rwXAkGEDXm +EJVlEbTzL7YiL6SIiGYHO99nxRk4Wd/Gx+MRuSkcQ9catYC1kGcJGBb4aBUjbFUq +X4yCSUmjw++py5PynwJUDKUGNElws4yhlXqHoU+3h68KUMvqGaE4QpmGUyPBOZIU +wpOofH4rPFgMCp0yi1Vbhy9gVQ2VRt+Jad7kf21BjQ7YDUIyVtS3LQumsM9JwLbG +VFbpG7OFTPzLfpM2QT7aAz3Uw2ShhhokN1CQLs4rDYHaJopaDhZVg1gJkr3GBP6g +FZ1cOui3ITLZBmZhBkglrSFqcUJTQzb2NJ6sBlIcx/K7qpbLYt9IyrvMhPPydkXZ +DyqEK3tyXEJWZ1mizr6CfZ4aZXO1IBv3ttvcdV4Ik0ksv7y0xT1oP6KTOmYRg8DG +cBTDkYHBMY9hxJ5ZMlSJmXigLVPhkQoAgShTyyYjXi0gnzRUhrtTRaAWD8ZDgQq1 +Al+ZWbeVJta2KXnpkeaLSuyof7RiSEFJaIHjCUyBku3WS89BIpphmmnlwMITeSTw +V5Iij4VYV1+LWG97mkmLEG7UYsNEXRIcrKehMlK6JX/jqjhMG4iyaCYjd4YHD/rb +l/GVyHYTZpsjMw+JSHaWZpJAMQ/TOuApCumBQ7YQgJB7dhOqoAdpYsIsG1zXalO4 +B/fVdsJcnGKyjGiiVOccs8mkDDFDLHGqRgfBhkSjYVScM85GtgMRtrSqPpoZZSPG +a3ELhzjEKNtYyWJKG+sYDoRbpQJWfv4nE0BnWePoOqc5ONIofaw1n8VGMiTTeu/o +OdI7yyDiayUBGI3IOE1wwbhWoTLUzajqbwGsxxQ3qldYkM9JhwaTDD7xovhrORrw +RuJCvJvXTFxbVB70W06HpltowM1xY85ltARJFrPljL28AS96fJhJR2c7jWQrKeRK +RMyKg7IJLbyxwmuSFTeQqz1MyQvVZzGGKw1yAReMji5rRcUJsUHRfEVCXNDstWig +gNG3IZIriyIlt+bTTHesAxSXLaS6DBsSJTbYaFk2GGHhTQBHqb2XTv4jODQXeZQA +kvaasrr1ceRphqRUrDx5crjFjB0Lsh+bX+bQjG4EwpBbLwMaNGrbWklZGCumrUgZ +UvFwdKS0Knf3I+GoDsuWEi2wAqbCNinydXqShYMgdu/8lX+2muMULfJLLvspq4Ab +n1Y4PdqTcxtnUgtFmUeLJkv0Zk8WSY7wU1QWzcK4paZWUTh7Vo45CATcszWzbuw7 +NXPiaO1gN/VhNGsMmHOBFSExtZITUuaFC4PgF7107cF5lJ+xrRsV7ZXwaRN+0SYZ +ttT2Je1kKaRt3ND5f1QY7SJoPjy10vdjIiT2RIE/2DLSfP4BqT+0Pm/71eLAwjLD +AJV/u5KhD/rnJLoLOu0eNGt4p3Oh6Mg5ttxICw== +-----END PRIVATE KEY----- diff --git a/test/fixtures/keys/ml_kem_1024_private_seed_only.pem b/test/fixtures/keys/ml_kem_1024_private_seed_only.pem new file mode 100644 index 00000000000000..8192b81556633b --- /dev/null +++ b/test/fixtures/keys/ml_kem_1024_private_seed_only.pem @@ -0,0 +1,4 @@ +-----BEGIN PRIVATE KEY----- +MFQCAQAwCwYJYIZIAWUDBAQDBEKAQF8HoLJVBnDJ87tXNTXHLMkZgWjm1khEiBjv +n7XHI+TLwMIywwCVf7uSoQ/65yS6CzrtHjRreKdzoejIObbcSAs= +-----END PRIVATE KEY----- diff --git a/test/fixtures/keys/ml_kem_1024_public.pem b/test/fixtures/keys/ml_kem_1024_public.pem new file mode 100644 index 00000000000000..a659eac9c370f4 --- /dev/null +++ b/test/fixtures/keys/ml_kem_1024_public.pem @@ -0,0 +1,36 @@ +-----BEGIN PUBLIC KEY----- +MIIGMjALBglghkgBZQMEBAMDggYhAA3XgSeHqr2XJjFoXqFVizYRBfNWuBmQqxN2 +XNmjYMr2NGnScnIghLBbBL38t1klwLkZAoccmbWpXtWQtEXFV6LhWxxLepgIps34 +GlwKIxJFcpaTiDqaT1GZFfCEYmeCLrT7qwzakV3WN30JmFsBMVUhX6EhQ4E4JwlL +yr05O/ckKDULnDkWfRxcD4/yu/k6chm4AuxhJTLbouKwR0p8tAQiTGXmnxvMKivQ +XF0TdOkpsakTarEcfRyyTcKYa+yBXDJWUwiMa8aDHJH5DHtAYlUgI1TWiAeUb3Ii +W3JkdsamTxGDU1VSgLiMmib5ix0BL/jmyn8FJ8+ljdDRmY4ae/ymAMWKMF46bk4T +Bhrrkm1chofCWMwjMdp4P09GcXgxv5QHJhykU6XHczU4V57LofWFcw+mkKcgnGmK +sXhlUKHsfnrkBMj3Oj6nLMnxy1sZYD7qAseKfwQLpyQgEs/7YwpVIXokHAmmAJjJ +kspThJUMLKL3V/bsvzTifQZmPXA0jD52iYRYbyMRUdYzYNPWQrl0WCQ4iMlgStnM +wN77fWVaPaNUJzNUmY72MgqATttyxxkmSrh8PyEjnk3DL++qhP+JDZaVy5LQccV5 +jzQSe62Qv8Wnpr5BkvM4xl9gan+3Bg8ykGxIVpN6L6bVdRZjdl8pEew5BGu6Tp+o +iqQgecgjq+ABzNFYiqqGu2dzMrEop04mpxClbFSwR9E8CreHPINIoQkGiE0zrRdk +LY2DEiBsx56lTWnCfMjcQ0VSwTqmOxtqly06fGFLmdnwl3rwXAkGEDXmEJVlEbTz +L7YiL6SIiGYHO99nxRk4Wd/Gx+MRuSkcQ9catYC1kGcJGBb4aBUjbFUqX4yCSUmj +w++py5PynwJUDKUGNElws4yhlXqHoU+3h68KUMvqGaE4QpmGUyPBOZIUwpOofH4r +PFgMCp0yi1Vbhy9gVQ2VRt+Jad7kf21BjQ7YDUIyVtS3LQumsM9JwLbGVFbpG7OF +TPzLfpM2QT7aAz3Uw2ShhhokN1CQLs4rDYHaJopaDhZVg1gJkr3GBP6gFZ1cOui3 +ITLZBmZhBkglrSFqcUJTQzb2NJ6sBlIcx/K7qpbLYt9IyrvMhPPydkXZDyqEK3ty +XEJWZ1mizr6CfZ4aZXO1IBv3ttvcdV4Ik0ksv7y0xT1oP6KTOmYRg8DGcBTDkYHB +MY9hxJ5ZMlSJmXigLVPhkQoAgShTyyYjXi0gnzRUhrtTRaAWD8ZDgQq1Al+ZWbeV +Jta2KXnpkeaLSuyof7RiSEFJaIHjCUyBku3WS89BIpphmmnlwMITeSTwV5Iij4VY +V1+LWG97mkmLEG7UYsNEXRIcrKehMlK6JX/jqjhMG4iyaCYjd4YHD/rbl/GVyHYT +ZpsjMw+JSHaWZpJAMQ/TOuApCumBQ7YQgJB7dhOqoAdpYsIsG1zXalO4B/fVdsJc +nGKyjGiiVOccs8mkDDFDLHGqRgfBhkSjYVScM85GtgMRtrSqPpoZZSPGa3ELhzjE +KNtYyWJKG+sYDoRbpQJWfv4nE0BnWePoOqc5ONIofaw1n8VGMiTTeu/oOdI7yyDi +ayUBGI3IOE1wwbhWoTLUzajqbwGsxxQ3qldYkM9JhwaTDD7xovhrORrwRuJCvJvX +TFxbVB70W06HpltowM1xY85ltARJFrPljL28AS96fJhJR2c7jWQrKeRKRMyKg7IJ +LbyxwmuSFTeQqz1MyQvVZzGGKw1yAReMji5rRcUJsUHRfEVCXNDstWiggNG3IZIr +iyIlt+bTTHesAxSXLaS6DBsSJTbYaFk2GGHhTQBHqb2XTv4jODQXeZQAkvaasrr1 +ceRphqRUrDx5crjFjB0Lsh+bX+bQjG4EwpBbLwMaNGrbWklZGCumrUgZUvFwdKS0 +Knf3I+GoDsuWEi2wAqbCNinydXqShYMgdu/8lX+2muMULfJLLvspq4Abn1Y4PdqT +cxtnUgtFmUeLJkv0Zk8WSY7wU1QWzcK4paZWUTh7Vo45CATcszWzbuw7NXPiaO1g +N/VhNGsMmHOBFSExtZITUuaFC4PgF7107cF5lJ+xrRsV7ZXwaRN+0SYZttT2Je1k +KaRt3ND5 +-----END PUBLIC KEY----- diff --git a/test/fixtures/keys/ml_kem_512_private.pem b/test/fixtures/keys/ml_kem_512_private.pem new file mode 100644 index 00000000000000..13cd20b2bd12f6 --- /dev/null +++ b/test/fixtures/keys/ml_kem_512_private.pem @@ -0,0 +1,39 @@ +-----BEGIN PRIVATE KEY----- +MIIGvgIBADALBglghkgBZQMEBAEEggaqMIIGpgRAsjVhZaMDzdhH7rMaw7Z9HH+7 +LfipY2weXpCqr0I3CMzOt/jB1xtlVqz7oLTcOAx1JNA+E7yXLnGPgZdplLmOaASC +BmApuZBSi2uN402Zdyjh8Z4ZChsL+iA+5sMj0Y8AC5HzADcXQX9UCio1OWW2ip9m +4l5FJh5RlbzUtH8lWYjwu6Mk0ksXsFyTMDmd+s2e4lxPYhXFRYWOm2cIiipkxpHr +6IoKU86vdFujScgAZCRNMrhQQQTzWwI6plyJYQikeTXN08s5Kh5R6A1OlEqMYWiQ +Mnd94I2cEmrHe4698J8n9k7lbEokCLxdIiuURImVYyuT2FIM2zt0t8/KxMVuuaK1 +dJ4WR0KAZjBzGmcLhDRoW2ME8G8KkEPPbIpqFQ77oD3cmoKVN05PY4moB2FYS5Re +h3mMQZaDasezI2toeRoCthe8u5VPRXuOJrqkM1KfhGZAAyjgg6EuOhnxTGjydiq8 ++CtSE5CpK2zh/B5EIwToEGuNdoVeIGNRAzm9LCBDOoWvfBgdbMoEM7Zo1nx1VpzQ +ITZfyKr1s2ivNq+GMrVkJKg0CNBEOsVVCzdP0bglS7oOh2MOOpmV9CpoKptqYlpe +Ym5dbFiVkH3kihPV5KcV+M7+ZmgeE5nQonrOMGs7tgljAH96Q6ERHBUBlzCnCLgf +EBV3tMzH0moaxUVPV14YppYDam+ulz844Y/g4kws5EOtsAbEsVIjNc31qDOCoL19 +2ZY6cVYUsQHfYLJPMlm60rxNYa2IUgv69imOAHlNaxzVEczgOYhheJZAmbp/Q4++ +g3SpeCxpR8qkWai2GXliRbXX6kRcGV2aEqI+ciCsM5BcIpXKeXay2aOAYjst/GT4 +qnRmFjzIeKzgqXc/8Zqq1wG91H1AWG+qccuKZpZtd3jiDG6oSZaZp6+SKSbqBpjq +BTCQWr9910MxRlTtJ02WKYbM8lNotKoaJrwlWBrRtj0jN2+04ZNm0nBv3Gop7F+A +ckcegigAqaWzFycOKMVBpqpG1R3dgUxANbnxrFIp9z1gO0KMfEZFN8/sOjIpVphN +RoQuy2YPOKOD98ij9k34e5ZtySDPBzfKyLBS7IikNQEw5KRGNyMCqxKP9hQegiEA +5LvbJZnuYSFsomikeZlheMYRFCscEcJCoFOGPBVTmC9M9ao+6oCjpwdas6scIG31 +oZLI87YGszTDs7VRkmxp0yaFElo683jYC8Po655u9iR78hM7KLatsT/PDDrjyjHU +kXxkyxNHymmP+pf+qIBhoULOmoYbnH+zC5gGyG7CdJnamKU5NDnvt1w4c1QNRnm2 +N095y7GSlD9A6GEYkcrOhVjViLDjOk8P2QovE7sHNRtMtw1lKzlUEnlSRrDLix+N +BrhA1GigwhUKAbshtVhP7K+BeWN0QbF8Fimx7B0ddYIXsQe0SaViwinfGU+NJsTo +UUtAt2pCyIjmqi2bgkY8Ia+VCyLUAcPJo8LVkiYviEMbwhew/E9/B8xLs6fkC2f3 +zDExCj1VKLOmmzssV84xhEfoWZiWNs/0GVZ6xjEZeRnUNHNrS0qs9pkyMCShFKsZ +uwdqHB3I20xWeKtXJLc7BaYcdIc7I1/AqjZZZUsra7ynGJWLR3sEITHuZbii0aEv ++I+fyyOJ5hYZG8nsvLRTxqt4CwV7cpu4e52cAwXDoDfwewhZtsVSOQVvsgm/hY42 +5SgKqkOSc5iMEpRbZT6q1p/P46ssNs860ELSIMZOPH3SwscbtaAJu7B85LUZVyPA +0hy2rGNcsH5WwQZc6Z3Ne8nr0yWUQH1qwFzUC0Dzokfx9UzIBhbXGwRsM7oqykFw +S1y6wl69IpIUaCDRPGslZZle3JnzdcEupWfYAbpZyoTjpnHm02GtFjttIhUs8Atr +p8s8fGY7QIbSUJOUOL5nAH8ZQHvFgGxNyKCggG012D8GWoARUbruObTOWDcoMw1v +J8IFUSlaMDGriabkOMxwVphpeJS/RGdqahIyYblNdTodsgLdlxezwVf7k36y6zKP +TCsqTIkjq8K8g6YCcYi8RCPmtz7SpFllRciI52PasFiT6cBi+rDqm32H0VVLaDiy +JochlGyPOjlHSHRq1Y/RySpM57XdcYzTuS9LjCONgyBrBwB80JQ+dH64x2zLnC4Y +PH4VZvqBGeFaw+DVLtb4HaCvFI8jCi1ONzWwoHd5SrSlrh4wivgljavGcFJixEUp +efLruJerXq9nVIHP6PC5+WAyzrf4wdcbZVas+6C03DgMdSTQPhO8ly5xj4GXaZS5 +jmg= +-----END PRIVATE KEY----- diff --git a/test/fixtures/keys/ml_kem_512_private_priv_only.pem b/test/fixtures/keys/ml_kem_512_private_priv_only.pem new file mode 100644 index 00000000000000..85de2f88d18ca2 --- /dev/null +++ b/test/fixtures/keys/ml_kem_512_private_priv_only.pem @@ -0,0 +1,37 @@ +-----BEGIN PRIVATE KEY----- +MIIGeAIBADALBglghkgBZQMEBAEEggZkBIIGYCm5kFKLa43jTZl3KOHxnhkKGwv6 +ID7mwyPRjwALkfMANxdBf1QKKjU5ZbaKn2biXkUmHlGVvNS0fyVZiPC7oyTSSxew +XJMwOZ36zZ7iXE9iFcVFhY6bZwiKKmTGkevoigpTzq90W6NJyABkJE0yuFBBBPNb +AjqmXIlhCKR5Nc3TyzkqHlHoDU6USoxhaJAyd33gjZwSasd7jr3wnyf2TuVsSiQI +vF0iK5REiZVjK5PYUgzbO3S3z8rExW65orV0nhZHQoBmMHMaZwuENGhbYwTwbwqQ +Q89simoVDvugPdyagpU3Tk9jiagHYVhLlF6HeYxBloNqx7Mja2h5GgK2F7y7lU9F +e44muqQzUp+EZkADKOCDoS46GfFMaPJ2Krz4K1ITkKkrbOH8HkQjBOgQa412hV4g +Y1EDOb0sIEM6ha98GB1sygQztmjWfHVWnNAhNl/IqvWzaK82r4YytWQkqDQI0EQ6 +xVULN0/RuCVLug6HYw46mZX0Kmgqm2piWl5ibl1sWJWQfeSKE9XkpxX4zv5maB4T +mdCies4wazu2CWMAf3pDoREcFQGXMKcIuB8QFXe0zMfSahrFRU9XXhimlgNqb66X +Pzjhj+DiTCzkQ62wBsSxUiM1zfWoM4KgvX3ZljpxVhSxAd9gsk8yWbrSvE1hrYhS +C/r2KY4AeU1rHNURzOA5iGF4lkCZun9Dj76DdKl4LGlHyqRZqLYZeWJFtdfqRFwZ +XZoSoj5yIKwzkFwilcp5drLZo4BiOy38ZPiqdGYWPMh4rOCpdz/xmqrXAb3UfUBY +b6pxy4pmlm13eOIMbqhJlpmnr5IpJuoGmOoFMJBav33XQzFGVO0nTZYphszyU2i0 +qhomvCVYGtG2PSM3b7Thk2bScG/cainsX4ByRx6CKACppbMXJw4oxUGmqkbVHd2B +TEA1ufGsUin3PWA7Qox8RkU3z+w6MilWmE1GhC7LZg84o4P3yKP2Tfh7lm3JIM8H +N8rIsFLsiKQ1ATDkpEY3IwKrEo/2FB6CIQDku9slme5hIWyiaKR5mWF4xhEUKxwR +wkKgU4Y8FVOYL0z1qj7qgKOnB1qzqxwgbfWhksjztgazNMOztVGSbGnTJoUSWjrz +eNgLw+jrnm72JHvyEzsotq2xP88MOuPKMdSRfGTLE0fKaY/6l/6ogGGhQs6ahhuc +f7MLmAbIbsJ0mdqYpTk0Oe+3XDhzVA1GebY3T3nLsZKUP0DoYRiRys6FWNWIsOM6 +Tw/ZCi8Tuwc1G0y3DWUrOVQSeVJGsMuLH40GuEDUaKDCFQoBuyG1WE/sr4F5Y3RB +sXwWKbHsHR11ghexB7RJpWLCKd8ZT40mxOhRS0C3akLIiOaqLZuCRjwhr5ULItQB +w8mjwtWSJi+IQxvCF7D8T38HzEuzp+QLZ/fMMTEKPVUos6abOyxXzjGER+hZmJY2 +z/QZVnrGMRl5GdQ0c2tLSqz2mTIwJKEUqxm7B2ocHcjbTFZ4q1cktzsFphx0hzsj +X8CqNlllSytrvKcYlYtHewQhMe5luKLRoS/4j5/LI4nmFhkbyey8tFPGq3gLBXty +m7h7nZwDBcOgN/B7CFm2xVI5BW+yCb+FjjblKAqqQ5JzmIwSlFtlPqrWn8/jqyw2 +zzrQQtIgxk48fdLCxxu1oAm7sHzktRlXI8DSHLasY1ywflbBBlzpnc17yevTJZRA +fWrAXNQLQPOiR/H1TMgGFtcbBGwzuirKQXBLXLrCXr0ikhRoINE8ayVlmV7cmfN1 +wS6lZ9gBulnKhOOmcebTYa0WO20iFSzwC2unyzx8ZjtAhtJQk5Q4vmcAfxlAe8WA +bE3IoKCAbTXYPwZagBFRuu45tM5YNygzDW8nwgVRKVowMauJpuQ4zHBWmGl4lL9E +Z2pqEjJhuU11Oh2yAt2XF7PBV/uTfrLrMo9MKypMiSOrwryDpgJxiLxEI+a3PtKk +WWVFyIjnY9qwWJPpwGL6sOqbfYfRVUtoOLImhyGUbI86OUdIdGrVj9HJKkzntd1x +jNO5L0uMI42DIGsHAHzQlD50frjHbMucLhg8fhVm+oEZ4VrD4NUu1vgdoK8UjyMK +LU43NbCgd3lKtKWuHjCK+CWNq8ZwUmLERSl58uu4l6ter2dUgc/o8Ln5YDLOt/jB +1xtlVqz7oLTcOAx1JNA+E7yXLnGPgZdplLmOaA== +-----END PRIVATE KEY----- diff --git a/test/fixtures/keys/ml_kem_512_private_seed_only.pem b/test/fixtures/keys/ml_kem_512_private_seed_only.pem new file mode 100644 index 00000000000000..c7fb5d3da22ba2 --- /dev/null +++ b/test/fixtures/keys/ml_kem_512_private_seed_only.pem @@ -0,0 +1,4 @@ +-----BEGIN PRIVATE KEY----- +MFQCAQAwCwYJYIZIAWUDBAQBBEKAQLI1YWWjA83YR+6zGsO2fRx/uy34qWNsHl6Q +qq9CNwjMzrf4wdcbZVas+6C03DgMdSTQPhO8ly5xj4GXaZS5jmg= +-----END PRIVATE KEY----- diff --git a/test/fixtures/keys/ml_kem_512_public.pem b/test/fixtures/keys/ml_kem_512_public.pem new file mode 100644 index 00000000000000..2924fbdb4d6927 --- /dev/null +++ b/test/fixtures/keys/ml_kem_512_public.pem @@ -0,0 +1,20 @@ +-----BEGIN PUBLIC KEY----- +MIIDMjALBglghkgBZQMEBAEDggMhANslme5hIWyiaKR5mWF4xhEUKxwRwkKgU4Y8 +FVOYL0z1qj7qgKOnB1qzqxwgbfWhksjztgazNMOztVGSbGnTJoUSWjrzeNgLw+jr +nm72JHvyEzsotq2xP88MOuPKMdSRfGTLE0fKaY/6l/6ogGGhQs6ahhucf7MLmAbI +bsJ0mdqYpTk0Oe+3XDhzVA1GebY3T3nLsZKUP0DoYRiRys6FWNWIsOM6Tw/ZCi8T +uwc1G0y3DWUrOVQSeVJGsMuLH40GuEDUaKDCFQoBuyG1WE/sr4F5Y3RBsXwWKbHs +HR11ghexB7RJpWLCKd8ZT40mxOhRS0C3akLIiOaqLZuCRjwhr5ULItQBw8mjwtWS +Ji+IQxvCF7D8T38HzEuzp+QLZ/fMMTEKPVUos6abOyxXzjGER+hZmJY2z/QZVnrG +MRl5GdQ0c2tLSqz2mTIwJKEUqxm7B2ocHcjbTFZ4q1cktzsFphx0hzsjX8CqNlll +SytrvKcYlYtHewQhMe5luKLRoS/4j5/LI4nmFhkbyey8tFPGq3gLBXtym7h7nZwD +BcOgN/B7CFm2xVI5BW+yCb+FjjblKAqqQ5JzmIwSlFtlPqrWn8/jqyw2zzrQQtIg +xk48fdLCxxu1oAm7sHzktRlXI8DSHLasY1ywflbBBlzpnc17yevTJZRAfWrAXNQL +QPOiR/H1TMgGFtcbBGwzuirKQXBLXLrCXr0ikhRoINE8ayVlmV7cmfN1wS6lZ9gB +ulnKhOOmcebTYa0WO20iFSzwC2unyzx8ZjtAhtJQk5Q4vmcAfxlAe8WAbE3IoKCA +bTXYPwZagBFRuu45tM5YNygzDW8nwgVRKVowMauJpuQ4zHBWmGl4lL9EZ2pqEjJh +uU11Oh2yAt2XF7PBV/uTfrLrMo9MKypMiSOrwryDpgJxiLxEI+a3PtKkWWVFyIjn +Y9qwWJPpwGL6sOqbfYfRVUtoOLImhyGUbI86OUdIdGrVj9HJKkzntd1xjNO5L0uM +I42DIGsHAHzQlD50frjHbMucLhg8fhVm+oEZ4VrD4NUu1vgdoK8UjyMKLU43NbCg +d3lKtKWu +-----END PUBLIC KEY----- diff --git a/test/fixtures/keys/ml_kem_768_private.pem b/test/fixtures/keys/ml_kem_768_private.pem new file mode 100644 index 00000000000000..fcc74fa87c8082 --- /dev/null +++ b/test/fixtures/keys/ml_kem_768_private.pem @@ -0,0 +1,55 @@ +-----BEGIN PRIVATE KEY----- +MIIJvgIBADALBglghkgBZQMEBAIEggmqMIIJpgRAkTfye65c20Ez4T1rDv5YsC/k +LexfNxNQHRZohALP3OhhjjiwFoBsHg0+HRLIWO3Nx5OwGhsUToPjfW0OWIU34ASC +CWC/uW2uBK2Y0oF0tIjItKqsoa70i0yBp1Emq0OWqw//u5a5cFbkkmZzBJrlUHPd +kRyd3MS94bSP1QDy3H8LxaEkCL3EAlJt9VwQ+XRZW7VtrFxYq0vFQwcjhH1GOJUj +uJVH8K1+h2Gz4JvwimmPdGEK2H0enMDOMzMolgFv4x3dgZd48g6w6LBuAF5okYyY +S6ahWEYz0UCJ6JJdayecM0RmI0JyG6+i9rgqWzk/1LS7kijLurXtZF0dOoxnkaFP +E4RL1ICnNLk6Zahf+bedFBJ4wUs5LCosyFJIBV21dUVtsETnYpvQKK5rJady4JTO +WGLZc8FJw5DZuDISw7DQQF3QArskxQjD+ryB5KVMXA1D8GYsAAgJVTiVWxyaGLTN +187QCp5PdZ4OYpE8qxMU8kGp8hYZ0SqCtTrVylExi5E+NjycK11zAUaJ2Jeq8Koi +UG07YmUToDyAqRdwkU8DWF76AM8H5QNN6IegW0z99ZQpEC8hd4TSdqXakr0je4XH +ES8okkdY2BMdkwxseg6qABitKGn5XG+Dd57HOLFm4oUDonv/KV55F6rcc01ibAGO +KDxf/IVVV83Kl69eWMUlMRIVUQC8qhE90QhVd6kVPM5vg0gwq3mIpJh+BAAdUY0c +kGXPeB/BIqcOMJXGAlUBiVHuZRj6Qcf9ZoQnAK0B416bin8RFrYtUJXesKkuZ0oZ +lItOJlcYqQtt8Anu6EXf6wabIwIA1Z5hqmrt0oPDaGGYM7J8sJGrZ3yZ4HREHJl8 +ocNWsATUNYBzyWRD+XKb8jIb1yA/xIBz+KjBRLxl4B99UmF0i5JEtDt6VxyJ1K2u +jBZdpLIwcMpCKFYOxKBoAskga819qLHyp1EfJXCTx5TZmDjuwcFI+1j2ABjgRbsv +9k8ijBihN1L6GwaiI0RilatuEqWXx0UTAm+AE4dWQLiDd5JW8Sz7OH9gRmtMHC0N +2Ay51qKrURP0u4n81oDiGFWgEkAh0hD06b4vVav9oGBfWHzmM8Eh2xlKesZ8KMbT +iVzXcq0OFlDWtaifwQlMeDY1lrabZAaWSb9pclGDjDRky34n1EFJwEP5NyBdCcsg +0reTiS6qJQpZiIcGWctEIKZdCK9KJiGCa3IbpkG/mB0el0UFqzab6Y08pXqT1ZKS +aol5p8LNJXafVLsIk3ThOjD8Cl4Bl4mmhsnZIludp2fnfFwf+rWsazRpWc0KxpVK +g8NamSF+SLOlV5s0yHl5wT4XgkeldQOpk5xHaWp6aVw5VF6byiygy5sWoJecgU8U +ZHWtOBO0pxY+lBn9ZUDJ97SiE6KcIhPZjHt5pWoq+jl9EiWz0s0ZUM55mrJ6gWZf +tjcsNivTw1z4sIP0hBJKIRfe8V58knCVFMyr0y71MUvF6jeUwjrr9WJZjEiwhATn +NX7IwJ5rELPk4US7B1vscaXy4WP5CFe8cG1AVjHe2Qh8qVSkRU5P8Wr/KQi2CsUB +YA3zow9U94UnxpaYHI+ghZ5/Vsc/xCsR9UOGwb8VBTnIK1QZpkPKmRigugf+u7Hm +RIPP1T+BmBqLMI6auohOEjCNGq8mU7xSgUpWsneE5D5Ser2MwocwnDGwbMMs4DK7 +5pVtC0niyWGfwq1dR8XXqWfZ3EXZy8kOVQdCussouKkwoDHkomHxgzAyEnNTChHJ +IcaK0KKSBEMxCjMn5IuaJl3hRjpZ9pEvZEdg2RFbFL0juIh6BbcsVz+v5So/e57D +p1xdBcpg6MyDW701iD8Ms7p/q8HzRCfEkj2Ix7MqaJAdpKiRYW5OxjPMdBxuwiDp +igIa9JBCdKHOFEkl2FQXGhNmbFkAFoYnXJbQSXs38FaosGUBU8ZsnIYq85Dg+izS +MYzv0az9Q5sl9HKRMUrpOL18YBOdVL3yUm71+RsJgZlOx5uq8UQPoZ75Bwy7k1O0 +aIawPINjqKo2xLWup8D6Qn5JqhYO6hwBQWqREcbWdWYRkbjEVmcCOWw7Z26IO8Ps +KsToI19VFpqnJmHxBs9eZV7kRAfSMRk41zojts54trOdKsJy3LXGgXa0KKDE5Axu +vGWdJqetmCKwGmyvFVamJAPc5cPDh72eNaCIQmGE9wUmlZhLVbRK5ShUmRRPrJH6 +m6T8Q2r3YJ7pdYWKtXAfJaYYJqL5pyXX8pKEUkL8By4dVAtYAIkbHIhVLBASkxOH +UHyyNHrBVp8IkqTR5M26pDAKB32btQor7LrVTGk31GvF98GcqVyGkwmRqiKYjFSE +QSlPU4uHZmjRAh5jBafnYnEM6HsWUY9ng3xrGwRvNUoDEy6IdbEf5klNpZlTOMr5 +WZb5aYx0kJ8JoXrcPAYtNh1aIGHchiopETHrWwlPwXcKyScv+YwsuUefZnsvWXO1 +9W05dyShhHmbA3XJvCeSvEn+k6q/waBuURTUZCcGMcYF1oTywayoWYOf+s9KaovY +uma/97jUtCPsbFvxc0y0CoOg4zQIEXgpRw+aOb5eNlIMk4VXGhzs1xhaRaLJuZb1 +FJOBUB3NiR6nsICKGowP9HR6Iap6FYLVlMHn+aQmGy1tbKMntCzpt2XOrL+FZ15l +5DL2wbGc3IX+WKTap8YApMvBJWEnMGePameAEqJhSarwzGXY+DfGNVfvfKznWBdf +d8RLUsJa5cZW3H11uWdhIDYs4JmqpnBldY81YKQS9JhDLAcz5HA3Z1qfgXu/U7nd +twPaIsY7Fc736btswHu3HCkFF1LlosP1UmNgKafKZxcY5Bf21J2y111fujlR8HrG +tl5cqD4UzJi2cDMzx4na9mfX4hjJaiKWvM/F+0J/a5VT3DYEtmYsJ0e89E+0icUn +xLzTYoMG8mEmtQCYeCgj26iuNLA5ugOmOsgA2miL+3simiJLVECSh8PBPHL/Bxe3 +aQFDVodKgGG6RoiNlkIHCGUTcT6txoum1R/AaSDddQtwu7GDcX4ZI1hZUB9+5BL6 +hScitk6YKsJPfGTi0jPLhBfY+UOm6hiOYKwlBDqjqB+ZNg1nN8hn6ESjHB8V4bIM +1lmP6nKBulo6Jja3V3ITQjo8yneZQqwcyRpemVihgbuwvLVG+MAhIWSZfILrOYKH +mTdCEHth/KAD6rPRndxRsh97Nwl1p+BqozJHy1KwbIok7REqUhFgesdPUub6SVFB +A8UaJ8jOtbQez1k6qHsYIbOBYY44sBaAbB4NPh0SyFjtzceTsBobFE6D431tDliF +N+A= +-----END PRIVATE KEY----- diff --git a/test/fixtures/keys/ml_kem_768_private_priv_only.pem b/test/fixtures/keys/ml_kem_768_private_priv_only.pem new file mode 100644 index 00000000000000..01b8ec19baaedf --- /dev/null +++ b/test/fixtures/keys/ml_kem_768_private_priv_only.pem @@ -0,0 +1,53 @@ +-----BEGIN PRIVATE KEY----- +MIIJeAIBADALBglghkgBZQMEBAIEgglkBIIJYL+5ba4ErZjSgXS0iMi0qqyhrvSL +TIGnUSarQ5arD/+7lrlwVuSSZnMEmuVQc92RHJ3cxL3htI/VAPLcfwvFoSQIvcQC +Um31XBD5dFlbtW2sXFirS8VDByOEfUY4lSO4lUfwrX6HYbPgm/CKaY90YQrYfR6c +wM4zMyiWAW/jHd2Bl3jyDrDosG4AXmiRjJhLpqFYRjPRQInokl1rJ5wzRGYjQnIb +r6L2uCpbOT/UtLuSKMu6te1kXR06jGeRoU8ThEvUgKc0uTplqF/5t50UEnjBSzks +KizIUkgFXbV1RW2wROdim9Aormslp3LglM5YYtlzwUnDkNm4MhLDsNBAXdACuyTF +CMP6vIHkpUxcDUPwZiwACAlVOJVbHJoYtM3XztAKnk91ng5ikTyrExTyQanyFhnR +KoK1OtXKUTGLkT42PJwrXXMBRonYl6rwqiJQbTtiZROgPICpF3CRTwNYXvoAzwfl +A03oh6BbTP31lCkQLyF3hNJ2pdqSvSN7hccRLyiSR1jYEx2TDGx6DqoAGK0oaflc +b4N3nsc4sWbihQOie/8pXnkXqtxzTWJsAY4oPF/8hVVXzcqXr15YxSUxEhVRALyq +ET3RCFV3qRU8zm+DSDCreYikmH4EAB1RjRyQZc94H8Eipw4wlcYCVQGJUe5lGPpB +x/1mhCcArQHjXpuKfxEWti1Qld6wqS5nShmUi04mVxipC23wCe7oRd/rBpsjAgDV +nmGqau3Sg8NoYZgzsnywkatnfJngdEQcmXyhw1awBNQ1gHPJZEP5cpvyMhvXID/E +gHP4qMFEvGXgH31SYXSLkkS0O3pXHInUra6MFl2ksjBwykIoVg7EoGgCySBrzX2o +sfKnUR8lcJPHlNmYOO7BwUj7WPYAGOBFuy/2TyKMGKE3UvobBqIjRGKVq24SpZfH +RRMCb4ATh1ZAuIN3klbxLPs4f2BGa0wcLQ3YDLnWoqtRE/S7ifzWgOIYVaASQCHS +EPTpvi9Vq/2gYF9YfOYzwSHbGUp6xnwoxtOJXNdyrQ4WUNa1qJ/BCUx4NjWWtptk +BpZJv2lyUYOMNGTLfifUQUnAQ/k3IF0JyyDSt5OJLqolClmIhwZZy0Qgpl0Ir0om +IYJrchumQb+YHR6XRQWrNpvpjTylepPVkpJqiXmnws0ldp9UuwiTdOE6MPwKXgGX +iaaGydkiW52nZ+d8XB/6taxrNGlZzQrGlUqDw1qZIX5Is6VXmzTIeXnBPheCR6V1 +A6mTnEdpanppXDlUXpvKLKDLmxagl5yBTxRkda04E7SnFj6UGf1lQMn3tKITopwi +E9mMe3mlair6OX0SJbPSzRlQznmasnqBZl+2Nyw2K9PDXPiwg/SEEkohF97xXnyS +cJUUzKvTLvUxS8XqN5TCOuv1YlmMSLCEBOc1fsjAnmsQs+ThRLsHW+xxpfLhY/kI +V7xwbUBWMd7ZCHypVKRFTk/xav8pCLYKxQFgDfOjD1T3hSfGlpgcj6CFnn9Wxz/E +KxH1Q4bBvxUFOcgrVBmmQ8qZGKC6B/67seZEg8/VP4GYGoswjpq6iE4SMI0aryZT +vFKBSlayd4TkPlJ6vYzChzCcMbBswyzgMrvmlW0LSeLJYZ/CrV1HxdepZ9ncRdnL +yQ5VB0K6yyi4qTCgMeSiYfGDMDISc1MKEckhxorQopIEQzEKMyfki5omXeFGOln2 +kS9kR2DZEVsUvSO4iHoFtyxXP6/lKj97nsOnXF0FymDozINbvTWIPwyzun+rwfNE +J8SSPYjHsypokB2kqJFhbk7GM8x0HG7CIOmKAhr0kEJ0oc4USSXYVBcaE2ZsWQAW +hidcltBJezfwVqiwZQFTxmychirzkOD6LNIxjO/RrP1DmyX0cpExSuk4vXxgE51U +vfJSbvX5GwmBmU7Hm6rxRA+hnvkHDLuTU7RohrA8g2OoqjbEta6nwPpCfkmqFg7q +HAFBapERxtZ1ZhGRuMRWZwI5bDtnbog7w+wqxOgjX1UWmqcmYfEGz15lXuREB9Ix +GTjXOiO2zni2s50qwnLctcaBdrQooMTkDG68ZZ0mp62YIrAabK8VVqYkA9zlw8OH +vZ41oIhCYYT3BSaVmEtVtErlKFSZFE+skfqbpPxDavdgnul1hYq1cB8lphgmovmn +JdfykoRSQvwHLh1UC1gAiRsciFUsEBKTE4dQfLI0esFWnwiSpNHkzbqkMAoHfZu1 +CivsutVMaTfUa8X3wZypXIaTCZGqIpiMVIRBKU9Ti4dmaNECHmMFp+dicQzoexZR +j2eDfGsbBG81SgMTLoh1sR/mSU2lmVM4yvlZlvlpjHSQnwmhetw8Bi02HVogYdyG +KikRMetbCU/BdwrJJy/5jCy5R59mey9Zc7X1bTl3JKGEeZsDdcm8J5K8Sf6Tqr/B +oG5RFNRkJwYxxgXWhPLBrKhZg5/6z0pqi9i6Zr/3uNS0I+xsW/FzTLQKg6DjNAgR +eClHD5o5vl42UgyThVcaHOzXGFpFosm5lvUUk4FQHc2JHqewgIoajA/0dHohqnoV +gtWUwef5pCYbLW1soye0LOm3Zc6sv4VnXmXkMvbBsZzchf5YpNqnxgCky8ElYScw +Z49qZ4ASomFJqvDMZdj4N8Y1V+98rOdYF193xEtSwlrlxlbcfXW5Z2EgNizgmaqm +cGV1jzVgpBL0mEMsBzPkcDdnWp+Be79Tud23A9oixjsVzvfpu2zAe7ccKQUXUuWi +w/VSY2App8pnFxjkF/bUnbLXXV+6OVHwesa2XlyoPhTMmLZwMzPHidr2Z9fiGMlq +Ipa8z8X7Qn9rlVPcNgS2ZiwnR7z0T7SJxSfEvNNigwbyYSa1AJh4KCPbqK40sDm6 +A6Y6yADaaIv7eyKaIktUQJKHw8E8cv8HF7dpAUNWh0qAYbpGiI2WQgcIZRNxPq3G +i6bVH8BpIN11C3C7sYNxfhkjWFlQH37kEvqFJyK2Tpgqwk98ZOLSM8uEF9j5Q6bq +GI5grCUEOqOoH5k2DWc3yGfoRKMcHxXhsgzWWY/qcoG6WjomNrdXchNCOjzKd5lC +rBzJGl6ZWKGBu7C8tUb4wCEhZJl8gus5goeZN0IQe2H8oAPqs9Gd3FGyH3s3CXWn +4GqjMkfLUrBsiiTtESpSEWB6x09S5vpJUUEDxRonyM61tB7PWTqoexghs4Fhjjiw +FoBsHg0+HRLIWO3Nx5OwGhsUToPjfW0OWIU34A== +-----END PRIVATE KEY----- diff --git a/test/fixtures/keys/ml_kem_768_private_seed_only.pem b/test/fixtures/keys/ml_kem_768_private_seed_only.pem new file mode 100644 index 00000000000000..120bf57589626d --- /dev/null +++ b/test/fixtures/keys/ml_kem_768_private_seed_only.pem @@ -0,0 +1,4 @@ +-----BEGIN PRIVATE KEY----- +MFQCAQAwCwYJYIZIAWUDBAQCBEKAQJE38nuuXNtBM+E9aw7+WLAv5C3sXzcTUB0W +aIQCz9zoYY44sBaAbB4NPh0SyFjtzceTsBobFE6D431tDliFN+A= +-----END PRIVATE KEY----- diff --git a/test/fixtures/keys/ml_kem_768_public.pem b/test/fixtures/keys/ml_kem_768_public.pem new file mode 100644 index 00000000000000..78d81897bb4e85 --- /dev/null +++ b/test/fixtures/keys/ml_kem_768_public.pem @@ -0,0 +1,28 @@ +-----BEGIN PUBLIC KEY----- +MIIEsjALBglghkgBZQMEBAIDggShAM/VP4GYGoswjpq6iE4SMI0aryZTvFKBSlay +d4TkPlJ6vYzChzCcMbBswyzgMrvmlW0LSeLJYZ/CrV1HxdepZ9ncRdnLyQ5VB0K6 +yyi4qTCgMeSiYfGDMDISc1MKEckhxorQopIEQzEKMyfki5omXeFGOln2kS9kR2DZ +EVsUvSO4iHoFtyxXP6/lKj97nsOnXF0FymDozINbvTWIPwyzun+rwfNEJ8SSPYjH +sypokB2kqJFhbk7GM8x0HG7CIOmKAhr0kEJ0oc4USSXYVBcaE2ZsWQAWhidcltBJ +ezfwVqiwZQFTxmychirzkOD6LNIxjO/RrP1DmyX0cpExSuk4vXxgE51UvfJSbvX5 +GwmBmU7Hm6rxRA+hnvkHDLuTU7RohrA8g2OoqjbEta6nwPpCfkmqFg7qHAFBapER +xtZ1ZhGRuMRWZwI5bDtnbog7w+wqxOgjX1UWmqcmYfEGz15lXuREB9IxGTjXOiO2 +zni2s50qwnLctcaBdrQooMTkDG68ZZ0mp62YIrAabK8VVqYkA9zlw8OHvZ41oIhC +YYT3BSaVmEtVtErlKFSZFE+skfqbpPxDavdgnul1hYq1cB8lphgmovmnJdfykoRS +QvwHLh1UC1gAiRsciFUsEBKTE4dQfLI0esFWnwiSpNHkzbqkMAoHfZu1CivsutVM +aTfUa8X3wZypXIaTCZGqIpiMVIRBKU9Ti4dmaNECHmMFp+dicQzoexZRj2eDfGsb +BG81SgMTLoh1sR/mSU2lmVM4yvlZlvlpjHSQnwmhetw8Bi02HVogYdyGKikRMetb +CU/BdwrJJy/5jCy5R59mey9Zc7X1bTl3JKGEeZsDdcm8J5K8Sf6Tqr/BoG5RFNRk +JwYxxgXWhPLBrKhZg5/6z0pqi9i6Zr/3uNS0I+xsW/FzTLQKg6DjNAgReClHD5o5 +vl42UgyThVcaHOzXGFpFosm5lvUUk4FQHc2JHqewgIoajA/0dHohqnoVgtWUwef5 +pCYbLW1soye0LOm3Zc6sv4VnXmXkMvbBsZzchf5YpNqnxgCky8ElYScwZ49qZ4AS +omFJqvDMZdj4N8Y1V+98rOdYF193xEtSwlrlxlbcfXW5Z2EgNizgmaqmcGV1jzVg +pBL0mEMsBzPkcDdnWp+Be79Tud23A9oixjsVzvfpu2zAe7ccKQUXUuWiw/VSY2Ap +p8pnFxjkF/bUnbLXXV+6OVHwesa2XlyoPhTMmLZwMzPHidr2Z9fiGMlqIpa8z8X7 +Qn9rlVPcNgS2ZiwnR7z0T7SJxSfEvNNigwbyYSa1AJh4KCPbqK40sDm6A6Y6yADa +aIv7eyKaIktUQJKHw8E8cv8HF7dpAUNWh0qAYbpGiI2WQgcIZRNxPq3Gi6bVH8Bp +IN11C3C7sYNxfhkjWFlQH37kEvqFJyK2Tpgqwk98ZOLSM8uEF9j5Q6bqGI5grCUE +OqOoH5k2DWc3yGfoRKMcHxXhsgzWWY/qcoG6WjomNrdXchNCOjzKd5lCrBzJGl6Z +WKGBu7C8tUb4wCEhZJl8gus5goeZN0IQe2H8oAPqs9Gd3FGyH3s3CXWn4GqjMkfL +UrBsiiTt +-----END PUBLIC KEY----- diff --git a/test/fixtures/options-as-flags/.test.env b/test/fixtures/options-as-flags/.test.env new file mode 100644 index 00000000000000..6c92e1684b2ed2 --- /dev/null +++ b/test/fixtures/options-as-flags/.test.env @@ -0,0 +1 @@ +NODE_OPTIONS=--v8-pool-size=8 diff --git a/test/fixtures/options-as-flags/fixture.cjs b/test/fixtures/options-as-flags/fixture.cjs new file mode 100644 index 00000000000000..e2a61b52c7b176 --- /dev/null +++ b/test/fixtures/options-as-flags/fixture.cjs @@ -0,0 +1,4 @@ +const { getOptionsAsFlagsFromBinding } = require('internal/options'); + +const flags = getOptionsAsFlagsFromBinding(); +console.log(JSON.stringify(flags.sort())); diff --git a/test/fixtures/options-as-flags/test-config.json b/test/fixtures/options-as-flags/test-config.json new file mode 100644 index 00000000000000..c80ffa4069fbf5 --- /dev/null +++ b/test/fixtures/options-as-flags/test-config.json @@ -0,0 +1,9 @@ +{ + "nodeOptions": { + "experimental-transform-types": true, + "max-http-header-size": 8192 + }, + "testRunner": { + "test-isolation": "none" + } +} diff --git a/test/fixtures/sea-exec-argv-empty.js b/test/fixtures/sea-exec-argv-empty.js new file mode 100644 index 00000000000000..1c98860ba34495 --- /dev/null +++ b/test/fixtures/sea-exec-argv-empty.js @@ -0,0 +1,6 @@ +const assert = require('assert'); + +console.log('process.argv:', JSON.stringify(process.argv)); +assert.strictEqual(process.argv[2], 'user-arg'); +assert.deepStrictEqual(process.execArgv, []); +console.log('empty execArgv test passed'); diff --git a/test/fixtures/sea-exec-argv-extension-cli.js b/test/fixtures/sea-exec-argv-extension-cli.js new file mode 100644 index 00000000000000..e9585483fcc21d --- /dev/null +++ b/test/fixtures/sea-exec-argv-extension-cli.js @@ -0,0 +1,14 @@ +const assert = require('assert'); + +console.log('process.argv:', JSON.stringify(process.argv)); +console.log('process.execArgv:', JSON.stringify(process.execArgv)); + +// Should have execArgv from SEA config + CLI --node-options +assert.deepStrictEqual(process.execArgv, ['--no-warnings', '--max-old-space-size=1024']); + +assert.deepStrictEqual(process.argv.slice(2), [ + 'user-arg1', + 'user-arg2' +]); + +console.log('execArgvExtension cli test passed'); diff --git a/test/fixtures/sea-exec-argv-extension-env.js b/test/fixtures/sea-exec-argv-extension-env.js new file mode 100644 index 00000000000000..1d706dfe7cfe11 --- /dev/null +++ b/test/fixtures/sea-exec-argv-extension-env.js @@ -0,0 +1,19 @@ +const assert = require('assert'); + +process.emitWarning('This warning should not be shown in the output', 'TestWarning'); + +console.log('process.argv:', JSON.stringify(process.argv)); +console.log('process.execArgv:', JSON.stringify(process.execArgv)); + +// Should have execArgv from SEA config. +// Note that flags from NODE_OPTIONS are not included in process.execArgv no matter it's +// an SEA or not, but we can test whether it works by checking that the warning emitted +// above was silenced. +assert.deepStrictEqual(process.execArgv, ['--no-warnings']); + +assert.deepStrictEqual(process.argv.slice(2), [ + 'user-arg1', + 'user-arg2' +]); + +console.log('execArgvExtension env test passed'); diff --git a/test/fixtures/sea-exec-argv-extension-none.js b/test/fixtures/sea-exec-argv-extension-none.js new file mode 100644 index 00000000000000..c089b065677091 --- /dev/null +++ b/test/fixtures/sea-exec-argv-extension-none.js @@ -0,0 +1,14 @@ +const assert = require('assert'); + +console.log('process.argv:', JSON.stringify(process.argv)); +console.log('process.execArgv:', JSON.stringify(process.execArgv)); + +// Should only have execArgv from SEA config, no NODE_OPTIONS +assert.deepStrictEqual(process.execArgv, ['--no-warnings']); + +assert.deepStrictEqual(process.argv.slice(2), [ + 'user-arg1', + 'user-arg2' +]); + +console.log('execArgvExtension none test passed'); diff --git a/test/fixtures/sea-exec-argv.js b/test/fixtures/sea-exec-argv.js new file mode 100644 index 00000000000000..7f0f8ece575138 --- /dev/null +++ b/test/fixtures/sea-exec-argv.js @@ -0,0 +1,18 @@ +const assert = require('assert'); + +process.emitWarning('This warning should not be shown in the output', 'TestWarning'); + +console.log('process.argv:', JSON.stringify(process.argv)); +console.log('process.execArgv:', JSON.stringify(process.execArgv)); + +assert.deepStrictEqual(process.execArgv, [ '--no-warnings', '--max-old-space-size=2048' ]); + +// We start from 2, because in SEA, the index 1 would be the same as the execPath +// to accommodate the general expectation that index 1 is the path to script for +// applications. +assert.deepStrictEqual(process.argv.slice(2), [ + 'user-arg1', + 'user-arg2' +]); + +console.log('multiple execArgv test passed'); diff --git a/test/fixtures/test-runner/flag-propagation/.env b/test/fixtures/test-runner/flag-propagation/.env new file mode 100644 index 00000000000000..e69de29bb2d1d6 diff --git a/test/fixtures/test-runner/flag-propagation/index.js b/test/fixtures/test-runner/flag-propagation/index.js new file mode 100644 index 00000000000000..77dc08acfe464d --- /dev/null +++ b/test/fixtures/test-runner/flag-propagation/index.js @@ -0,0 +1 @@ +// Empty file used by test/parallel/test-runner-flag-propagation.js \ No newline at end of file diff --git a/test/fixtures/test-runner/flag-propagation/node.config.json b/test/fixtures/test-runner/flag-propagation/node.config.json new file mode 100644 index 00000000000000..54bcbfef04a947 --- /dev/null +++ b/test/fixtures/test-runner/flag-propagation/node.config.json @@ -0,0 +1,5 @@ +{ + "nodeOptions": { + "max-http-header-size": 10 + } +} diff --git a/test/fixtures/test-runner/flag-propagation/runner.mjs b/test/fixtures/test-runner/flag-propagation/runner.mjs new file mode 100644 index 00000000000000..162c7c8566a858 --- /dev/null +++ b/test/fixtures/test-runner/flag-propagation/runner.mjs @@ -0,0 +1,33 @@ +import { run } from 'node:test'; +import { tap } from 'node:test/reporters'; +import { parseArgs } from 'node:util'; + +const options = { + flag: { + type: 'string', + default: '', + }, + expected: { + type: 'string', + default: '', + }, + description: { + type: 'string', + default: 'flag propagation test', + }, +}; + +const { values } = parseArgs({ args: process.argv.slice(2), options }); + +const argv = [ + `--flag=${values.flag}`, + `--expected=${values.expected}`, + `--description="${values.description}"`, +].filter(Boolean); + +run({ + files: ['./test.mjs'], + cwd: process.cwd(), + argv, + isolation: 'process', +}).compose(tap).pipe(process.stdout); diff --git a/test/fixtures/test-runner/flag-propagation/test.mjs b/test/fixtures/test-runner/flag-propagation/test.mjs new file mode 100644 index 00000000000000..c47f7053ffda2b --- /dev/null +++ b/test/fixtures/test-runner/flag-propagation/test.mjs @@ -0,0 +1,46 @@ +import { test } from 'node:test'; +import { deepStrictEqual } from 'node:assert'; +import internal from 'internal/options'; +import { parseArgs } from 'node:util'; + +const options = { + flag: { + type: 'string', + default: '', + }, + expected: { + type: 'string', + default: '', + }, + description: { + type: 'string', + default: 'flag propagation test', + }, +}; + + +const { values } = parseArgs({ args: process.argv.slice(2), options }); + +let { flag, expected, description } = values; + +test(description, () => { + const optionValue = internal.getOptionValue(flag); + const isArrayOption = Array.isArray(optionValue); + + if (isArrayOption) { + expected = [expected]; + } + + console.error(`testing flag: ${flag}, found value: ${optionValue}, expected: ${expected}`); + + const isNumber = !isNaN(Number(expected)); + const booleanValue = expected === 'true' || expected === 'false'; + if (booleanValue) { + deepStrictEqual(optionValue, expected === 'true'); + return; + } else if (isNumber) { + deepStrictEqual(Number(optionValue), Number(expected)); + } else{ + deepStrictEqual(optionValue, expected); + } +}); diff --git a/test/fixtures/test-runner/rerun.js b/test/fixtures/test-runner/rerun.js new file mode 100644 index 00000000000000..94f3708431990a --- /dev/null +++ b/test/fixtures/test-runner/rerun.js @@ -0,0 +1,25 @@ +const { test } = require('node:test') + +test('should fail on first two attempts', ({ attempt }) => { + if (attempt < 2) { + throw new Error('This test is expected to fail on the first two attempts'); + } +}); + +test('ok', ({ attempt }) => { + if (attempt > 0) { + throw new Error('Test should not rerun once it has passed'); + } +}); + + +function ambiguousTest(expectedAttempts) { + test(`ambiguous (expectedAttempts=${expectedAttempts})`, ({ attempt }) => { + if (attempt < expectedAttempts) { + throw new Error(`This test is expected to fail on the first ${expectedAttempts} attempts`); + } + }); +} + +ambiguousTest(0); +ambiguousTest(1); \ No newline at end of file diff --git a/test/fixtures/webcrypto/supports-level-2.mjs b/test/fixtures/webcrypto/supports-level-2.mjs new file mode 100644 index 00000000000000..7a8f8cd9d5dd9e --- /dev/null +++ b/test/fixtures/webcrypto/supports-level-2.mjs @@ -0,0 +1,236 @@ +const { subtle } = globalThis.crypto; + +const RSA_KEY_GEN = { + modulusLength: 2048, + publicExponent: new Uint8Array([1, 0, 1]) +}; + +const [ECDH, X448, X25519] = await Promise.all([ + subtle.generateKey({ name: 'ECDH', namedCurve: 'P-256' }, false, ['deriveBits', 'deriveKey']), + subtle.generateKey('X448', false, ['deriveBits', 'deriveKey']), + subtle.generateKey('X25519', false, ['deriveBits', 'deriveKey']), +]); + +const boringSSL = process.features.openssl_is_boringssl; + +export const vectors = { + 'encrypt': [ + [false, 'Invalid'], + [false, 'Ed25519'], + [true, { name: 'AES-CBC', iv: Buffer.alloc(16) }], + [false, 'AES-CBC'], + [true, { name: 'AES-GCM', iv: Buffer.alloc(12) }], + [false, 'AES-GCM'], + [true, { name: 'AES-CTR', counter: Buffer.alloc(16), length: 128 }], + [false, 'AES-CTR'], + [true, 'RSA-OAEP'], + [true, { name: 'RSA-OAEP', label: Buffer.alloc(0) }], + [true, 'RSA-OAEP'], + [false, { name: 'RSA-OAEP', label: null }], + ], + 'sign': [ + [false, 'Invalid'], + [false, 'SHA-1'], + + [true, 'Ed25519'], + + [true, 'Ed448'], + [true, { name: 'Ed448', context: Buffer.alloc(0) }], + [false, { name: 'Ed448', context: Buffer.alloc(1) }], + + [true, 'RSASSA-PKCS1-v1_5'], + + [true, { name: 'RSA-PSS', saltLength: 32 }], + [false, 'RSA-PSS'], + + [true, { name: 'ECDSA', hash: 'SHA-256' }], + [false, { name: 'ECDSA', hash: 'Invalid' }], + [false, { name: 'ECDSA', hash: 'Ed25519' }], + [false, 'ECDSA'], + + [true, 'HMAC'], + ], + 'digest': [ + [true, 'SHA-1'], + [true, 'SHA-256'], + [true, 'SHA-384'], + [true, 'SHA-512'], + [false, 'Invalid'], + [false, 'Ed25519'], + ], + 'generateKey': [ + [false, 'SHA-1'], + [false, 'Invalid'], + [false, 'HKDF'], + [false, 'PBKDF2'], + [true, 'X25519'], + [true, 'X448'], + [true, 'Ed25519'], + [true, 'Ed448'], + [true, { name: 'HMAC', hash: 'SHA-256' }], + [true, { name: 'HMAC', hash: 'SHA-256', length: 256 }], + [false, { name: 'HMAC', hash: 'SHA-256', length: 25 }], + [true, { name: 'RSASSA-PKCS1-v1_5', hash: 'SHA-256', ...RSA_KEY_GEN }], + [true, { name: 'RSA-PSS', hash: 'SHA-256', ...RSA_KEY_GEN }], + [true, { name: 'RSA-OAEP', hash: 'SHA-256', ...RSA_KEY_GEN }], + [true, { name: 'ECDSA', namedCurve: 'P-256' }], + [false, { name: 'ECDSA', namedCurve: 'X25519' }], + [true, { name: 'AES-CTR', length: 128 }], + [false, { name: 'AES-CTR', length: 25 }], + [true, { name: 'AES-CBC', length: 128 }], + [false, { name: 'AES-CBC', length: 25 }], + [true, { name: 'AES-GCM', length: 128 }], + [false, { name: 'AES-GCM', length: 25 }], + [!boringSSL, { name: 'AES-KW', length: 128 }], + [false, { name: 'AES-KW', length: 25 }], + [true, { name: 'HMAC', hash: 'SHA-256' }], + [true, { name: 'HMAC', hash: 'SHA-256', length: 256 }], + [false, { name: 'HMAC', hash: 'SHA-256', length: 25 }], + [false, { name: 'HMAC', hash: 'SHA-256', length: 0 }], + ], + 'deriveKey': [ + [true, + { name: 'HKDF', hash: 'SHA-256', salt: Buffer.alloc(0), info: Buffer.alloc(0) }, + { name: 'AES-CBC', length: 128 }], + [true, + { name: 'HKDF', hash: 'SHA-256', salt: Buffer.alloc(0), info: Buffer.alloc(0) }, + { name: 'HMAC', hash: 'SHA-256' }], + [false, + { name: 'HKDF', hash: 'SHA-256', salt: Buffer.alloc(0), info: Buffer.alloc(0) }, + 'HKDF'], + [true, + { name: 'PBKDF2', hash: 'SHA-256', salt: Buffer.alloc(0), iterations: 1 }, + { name: 'AES-CBC', length: 128 }], + [true, + { name: 'PBKDF2', hash: 'SHA-256', salt: Buffer.alloc(0), iterations: 1 }, + { name: 'HMAC', hash: 'SHA-256' }], + [false, + { name: 'PBKDF2', hash: 'SHA-256', salt: Buffer.alloc(0), iterations: 1 }, + 'HKDF'], + [true, + { name: 'X25519', public: X25519.publicKey }, + { name: 'AES-CBC', length: 128 }], + [true, + { name: 'X25519', public: X25519.publicKey }, + { name: 'HMAC', hash: 'SHA-256' }], + [true, + { name: 'X25519', public: X25519.publicKey }, + 'HKDF'], + [true, + { name: 'X448', public: X448.publicKey }, + { name: 'AES-CBC', length: 128 }], + [true, + { name: 'X448', public: X448.publicKey }, + { name: 'HMAC', hash: 'SHA-256' }], + [true, + { name: 'X448', public: X448.publicKey }, + 'HKDF'], + [true, + { name: 'ECDH', public: ECDH.publicKey }, + { name: 'AES-CBC', length: 128 }], + [true, + { name: 'ECDH', public: ECDH.publicKey }, + { name: 'HMAC', hash: 'SHA-256' }], + [true, + { name: 'ECDH', public: ECDH.publicKey }, + 'HKDF'], + [false, + { name: 'X25519', public: X25519.publicKey }, + 'SHA-256'], + [false, + { name: 'X25519', public: X25519.publicKey }, + 'AES-CBC'], + ], + 'deriveBits': [ + [true, { name: 'HKDF', hash: 'SHA-256', salt: Buffer.alloc(0), info: Buffer.alloc(0) }, 8], + [true, { name: 'HKDF', hash: 'SHA-256', salt: Buffer.alloc(0), info: Buffer.alloc(0) }, 0], + [false, { name: 'HKDF', hash: 'SHA-256', salt: Buffer.alloc(0), info: Buffer.alloc(0) }, null], + [false, { name: 'HKDF', hash: 'SHA-256', salt: Buffer.alloc(0), info: Buffer.alloc(0) }, 7], + [false, { name: 'HKDF', hash: 'Invalid', salt: Buffer.alloc(0), info: Buffer.alloc(0) }, 8], + [false, { name: 'HKDF', hash: 'Ed25519', salt: Buffer.alloc(0), info: Buffer.alloc(0) }, 8], + + [true, { name: 'PBKDF2', hash: 'SHA-256', salt: Buffer.alloc(0), iterations: 1 }, 8], + [true, { name: 'PBKDF2', hash: 'SHA-256', salt: Buffer.alloc(0), iterations: 1 }, 0], + [false, { name: 'PBKDF2', hash: 'SHA-256', salt: Buffer.alloc(0), iterations: 0 }, 8], + [false, { name: 'PBKDF2', hash: 'SHA-256', salt: Buffer.alloc(0), iterations: 1 }, null], + [false, { name: 'PBKDF2', hash: 'SHA-256', salt: Buffer.alloc(0), iterations: 1 }, 7], + [false, { name: 'PBKDF2', hash: 'Invalid', salt: Buffer.alloc(0), iterations: 1 }, 8], + [false, { name: 'PBKDF2', hash: 'Ed25519', salt: Buffer.alloc(0), iterations: 1 }, 8], + + [true, + { name: 'ECDH', public: ECDH.publicKey }], + [false, { name: 'ECDH', public: X448.publicKey }], + [false, { name: 'ECDH', public: ECDH.privateKey }], + [false, 'ECDH'], + + [true, { name: 'X25519', public: X25519.publicKey }], + [false, { name: 'X25519', public: X448.publicKey }], + [false, { name: 'X25519', public: X25519.privateKey }], + [false, 'X25519'], + + [true, { name: 'X448', public: X448.publicKey }], + [false, { name: 'X448', public: X25519.publicKey }], + [false, { name: 'X448', public: X448.privateKey }], + [false, 'X448'], + ], + 'importKey': [ + [false, 'SHA-1'], + [false, 'Invalid'], + [true, 'X25519'], + [true, 'X448'], + [true, 'Ed25519'], + [true, 'Ed448'], + [true, { name: 'HMAC', hash: 'SHA-256' }], + [true, { name: 'HMAC', hash: 'SHA-256', length: 256 }], + [false, { name: 'HMAC', hash: 'SHA-256', length: 25 }], + [true, { name: 'RSASSA-PKCS1-v1_5', hash: 'SHA-256', ...RSA_KEY_GEN }], + [true, { name: 'RSA-PSS', hash: 'SHA-256', ...RSA_KEY_GEN }], + [true, { name: 'RSA-OAEP', hash: 'SHA-256', ...RSA_KEY_GEN }], + [true, { name: 'ECDSA', namedCurve: 'P-256' }], + [false, { name: 'ECDSA', namedCurve: 'X25519' }], + [true, 'AES-CTR'], + [true, 'AES-CBC'], + [true, 'AES-GCM'], + [!boringSSL, 'AES-KW'], + [true, { name: 'HMAC', hash: 'SHA-256' }], + [true, { name: 'HMAC', hash: 'SHA-256', length: 256 }], + [false, { name: 'HMAC', hash: 'SHA-256', length: 25 }], + [false, { name: 'HMAC', hash: 'SHA-256', length: 0 }], + [true, 'HKDF'], + [true, 'PBKDF2'], + ], + 'exportKey': [ + [false, 'SHA-1'], + [false, 'Invalid'], + [false, 'HKDF'], + [false, 'PBKDF2'], + [true, 'RSASSA-PKCS1-v1_5'], + [true, 'RSA-PSS'], + [true, 'RSA-OAEP'], + [true, 'ECDSA'], + [true, 'ECDH'], + [true, 'HMAC'], + [true, 'AES-CTR'], + [true, 'AES-CBC'], + [true, 'AES-GCM'], + [!boringSSL, 'AES-KW'], + [true, 'Ed25519'], + [true, 'X25519'], + ], + 'wrapKey': [ + [false, 'AES-KW'], + [!boringSSL, 'AES-KW', 'AES-CTR'], + [!boringSSL, 'AES-KW', 'HMAC'], + ], + 'unwrapKey': [ + [false, 'AES-KW'], + [!boringSSL, 'AES-KW', 'AES-CTR'], + ], + 'unsupported operation': [ + [false, ''], + [false, 'Ed25519'], + ], + 'get key length': [ + [false, { name: 'HMAC', hash: 'SHA-256' }], + ], +}; diff --git a/test/fixtures/webcrypto/supports-modern-algorithms.mjs b/test/fixtures/webcrypto/supports-modern-algorithms.mjs new file mode 100644 index 00000000000000..8d0df6c7014ffa --- /dev/null +++ b/test/fixtures/webcrypto/supports-modern-algorithms.mjs @@ -0,0 +1,132 @@ +import * as crypto from 'node:crypto' + +import { hasOpenSSL } from '../../common/crypto.js' + +const pqc = hasOpenSSL(3, 5); +const shake128 = crypto.getHashes().includes('shake128'); +const shake256 = crypto.getHashes().includes('shake256'); +const chacha = crypto.getCiphers().includes('chacha20-poly1305'); +const ocb = hasOpenSSL(3); + +export const vectors = { + 'digest': [ + [false, 'cSHAKE128'], + [shake128, { name: 'cSHAKE128', length: 128 }], + [shake128, { name: 'cSHAKE128', length: 128, functionName: Buffer.alloc(0), customization: Buffer.alloc(0) }], + [false, { name: 'cSHAKE128', length: 128, functionName: Buffer.alloc(1) }], + [false, { name: 'cSHAKE128', length: 128, customization: Buffer.alloc(1) }], + [false, { name: 'cSHAKE128', length: 127 }], + [false, 'cSHAKE256'], + [shake256, { name: 'cSHAKE256', length: 256 }], + [shake256, { name: 'cSHAKE256', length: 256, functionName: Buffer.alloc(0), customization: Buffer.alloc(0) }], + [false, { name: 'cSHAKE256', length: 256, functionName: Buffer.alloc(1) }], + [false, { name: 'cSHAKE256', length: 256, customization: Buffer.alloc(1) }], + [false, { name: 'cSHAKE256', length: 255 }], + ], + 'sign': [ + [pqc, 'ML-DSA-44'], + [pqc, 'ML-DSA-65'], + [pqc, 'ML-DSA-87'], + ], + 'generateKey': [ + [pqc, 'ML-DSA-44'], + [pqc, 'ML-DSA-65'], + [pqc, 'ML-DSA-87'], + [pqc, 'ML-KEM-512'], + [pqc, 'ML-KEM-768'], + [pqc, 'ML-KEM-1024'], + [chacha, 'ChaCha20-Poly1305'], + [ocb, { name: 'AES-OCB', length: 128 }], + ], + 'importKey': [ + [pqc, 'ML-DSA-44'], + [pqc, 'ML-DSA-65'], + [pqc, 'ML-DSA-87'], + [pqc, 'ML-KEM-512'], + [pqc, 'ML-KEM-768'], + [pqc, 'ML-KEM-1024'], + [chacha, 'ChaCha20-Poly1305'], + [ocb, { name: 'AES-OCB', length: 128 }], + ], + 'exportKey': [ + [pqc, 'ML-DSA-44'], + [pqc, 'ML-DSA-65'], + [pqc, 'ML-DSA-87'], + [pqc, 'ML-KEM-512'], + [pqc, 'ML-KEM-768'], + [pqc, 'ML-KEM-1024'], + [chacha, 'ChaCha20-Poly1305'], + [ocb, 'AES-OCB'], + ], + 'getPublicKey': [ + [true, 'RSA-OAEP'], + [true, 'RSA-PSS'], + [true, 'RSASSA-PKCS1-v1_5'], + [true, 'X25519'], + [true, 'X448'], + [true, 'Ed25519'], + [true, 'Ed448'], + [true, 'ECDH'], + [true, 'ECDSA'], + [pqc, 'ML-DSA-44'], + [pqc, 'ML-DSA-65'], + [pqc, 'ML-DSA-87'], + [pqc, 'ML-KEM-512'], + [pqc, 'ML-KEM-768'], + [pqc, 'ML-KEM-1024'], + [false, 'AES-CTR'], + [false, 'AES-CBC'], + [false, 'AES-GCM'], + [false, 'AES-OCB'], + [false, 'AES-KW'], + [false, 'ChaCha20-Poly1305'], + ], + 'encrypt': [ + [chacha, { name: 'ChaCha20-Poly1305', iv: Buffer.alloc(12) }], + [false, { name: 'ChaCha20-Poly1305', iv: Buffer.alloc(16) }], + [chacha, { name: 'ChaCha20-Poly1305', iv: Buffer.alloc(12), tagLength: 128 }], + [false, { name: 'ChaCha20-Poly1305', iv: Buffer.alloc(12), tagLength: 64 }], + [false, 'ChaCha20-Poly1305'], + [ocb, { name: 'AES-OCB', iv: Buffer.alloc(15) }], + [false, { name: 'AES-OCB', iv: Buffer.alloc(16) }], + [ocb, { name: 'AES-OCB', iv: Buffer.alloc(12), tagLength: 128 }], + [ocb, { name: 'AES-OCB', iv: Buffer.alloc(12), tagLength: 96 }], + [ocb, { name: 'AES-OCB', iv: Buffer.alloc(12), tagLength: 64 }], + [false, { name: 'AES-OCB', iv: Buffer.alloc(12), tagLength: 32 }], + [false, 'AES-OCB'], + ], + 'encapsulateBits': [ + [pqc, 'ML-KEM-512'], + [pqc, 'ML-KEM-768'], + [pqc, 'ML-KEM-1024'], + ], + 'encapsulateKey': [ + [pqc, 'ML-KEM-512', 'AES-KW'], + [pqc, 'ML-KEM-512', 'AES-GCM'], + [pqc, 'ML-KEM-512', 'AES-CTR'], + [pqc, 'ML-KEM-512', 'AES-CBC'], + [pqc, 'ML-KEM-512', 'ChaCha20-Poly1305'], + [pqc, 'ML-KEM-512', 'HKDF'], + [pqc, 'ML-KEM-512', 'PBKDF2'], + [pqc, 'ML-KEM-512', { name: 'HMAC', hash: 'SHA-256' }], + [pqc, 'ML-KEM-512', { name: 'HMAC', hash: 'SHA-256', length: 256 }], + [false, 'ML-KEM-512', { name: 'HMAC', hash: 'SHA-256', length: 128 }], + ], + 'decapsulateBits': [ + [pqc, 'ML-KEM-512'], + [pqc, 'ML-KEM-768'], + [pqc, 'ML-KEM-1024'], + ], + 'decapsulateKey': [ + [pqc, 'ML-KEM-512', 'AES-KW'], + [pqc, 'ML-KEM-512', 'AES-GCM'], + [pqc, 'ML-KEM-512', 'AES-CTR'], + [pqc, 'ML-KEM-512', 'AES-CBC'], + [pqc, 'ML-KEM-512', 'ChaCha20-Poly1305'], + [pqc, 'ML-KEM-512', 'HKDF'], + [pqc, 'ML-KEM-512', 'PBKDF2'], + [pqc, 'ML-KEM-512', { name: 'HMAC', hash: 'SHA-256' }], + [pqc, 'ML-KEM-512', { name: 'HMAC', hash: 'SHA-256', length: 256 }], + [false, 'ML-KEM-512', { name: 'HMAC', hash: 'SHA-256', length: 128 }], + ], +}; diff --git a/test/fixtures/webcrypto/supports-secure-curves.mjs b/test/fixtures/webcrypto/supports-secure-curves.mjs new file mode 100644 index 00000000000000..eed95aa1f98b0d --- /dev/null +++ b/test/fixtures/webcrypto/supports-secure-curves.mjs @@ -0,0 +1,41 @@ +const { subtle } = globalThis.crypto; + +const boringSSL = process.features.openssl_is_boringssl; + +const X25519 = await subtle.generateKey('X25519', false, ['deriveBits', 'deriveKey']); +let X448; +if (!boringSSL) { + X448 = await subtle.generateKey('X448', false, ['deriveBits', 'deriveKey']) +} + +export const vectors = { + 'generateKey': [ + [!boringSSL, 'X448'], + [!boringSSL, 'Ed448'], + ], + 'deriveKey': [ + [!boringSSL, + { name: 'X448', public: X448?.publicKey }, + { name: 'AES-CBC', length: 128 }], + [!boringSSL, + { name: 'X448', public: X448?.publicKey }, + { name: 'HMAC', hash: 'SHA-256' }], + [!boringSSL, + { name: 'X448', public: X448?.publicKey }, + 'HKDF'], + ], + 'deriveBits': [ + [!boringSSL, { name: 'X448', public: X448?.publicKey }], + [false, { name: 'X448', public: X25519.publicKey }], + [false, { name: 'X448', public: X448?.privateKey }], + [false, 'X448'], + ], + 'importKey': [ + [!boringSSL, 'X448'], + [!boringSSL, 'Ed448'], + ], + 'exportKey': [ + [!boringSSL, 'Ed448'], + [!boringSSL, 'X448'], + ], +}; diff --git a/test/fixtures/webcrypto/supports-sha3.mjs b/test/fixtures/webcrypto/supports-sha3.mjs new file mode 100644 index 00000000000000..db1aa0e2ac4c0e --- /dev/null +++ b/test/fixtures/webcrypto/supports-sha3.mjs @@ -0,0 +1,103 @@ +const { subtle } = globalThis.crypto; + +const boringSSL = process.features.openssl_is_boringssl; + +const RSA_KEY_GEN = { + modulusLength: 2048, + publicExponent: new Uint8Array([1, 0, 1]) +}; + +const [ECDH, X25519] = await Promise.all([ + subtle.generateKey({ name: 'ECDH', namedCurve: 'P-256' }, false, ['deriveBits', 'deriveKey']), + subtle.generateKey('X25519', false, ['deriveBits', 'deriveKey']), +]); + +export const vectors = { + 'digest': [ + [!boringSSL, 'SHA3-256'], + [!boringSSL, 'SHA3-384'], + [!boringSSL, 'SHA3-512'], + ], + 'generateKey': [ + [!boringSSL, { name: 'HMAC', hash: 'SHA3-256', length: 256 }], + [false, { name: 'HMAC', hash: 'SHA3-256', length: 25 }], + [!boringSSL, { name: 'RSASSA-PKCS1-v1_5', hash: 'SHA3-256', ...RSA_KEY_GEN }], + [!boringSSL, { name: 'RSA-PSS', hash: 'SHA3-256', ...RSA_KEY_GEN }], + [!boringSSL, { name: 'RSA-OAEP', hash: 'SHA3-256', ...RSA_KEY_GEN }], + [!boringSSL, { name: 'HMAC', hash: 'SHA3-256', length: 256 }], + [false, { name: 'HMAC', hash: 'SHA3-256', length: 25 }], + [false, { name: 'HMAC', hash: 'SHA3-256', length: 0 }], + + // This interaction is not defined for now. + // https://github.com/WICG/webcrypto-modern-algos/issues/23 + [false, { name: 'HMAC', hash: 'SHA3-256' }], + ], + 'deriveKey': [ + [!boringSSL, + { name: 'HKDF', hash: 'SHA3-256', salt: Buffer.alloc(0), info: Buffer.alloc(0) }, + { name: 'AES-CBC', length: 128 }], + [!boringSSL, + { name: 'HKDF', hash: 'SHA3-256', salt: Buffer.alloc(0), info: Buffer.alloc(0) }, + { name: 'HMAC', hash: 'SHA3-256', length: 256 }], + [false, + { name: 'HKDF', hash: 'SHA3-256', salt: Buffer.alloc(0), info: Buffer.alloc(0) }, + 'HKDF'], + [!boringSSL, + { name: 'PBKDF2', hash: 'SHA3-256', salt: Buffer.alloc(0), iterations: 1 }, + { name: 'AES-CBC', length: 128 }], + [!boringSSL, + { name: 'PBKDF2', hash: 'SHA3-256', salt: Buffer.alloc(0), iterations: 1 }, + { name: 'HMAC', hash: 'SHA3-256', length: 256 }], + [false, + { name: 'PBKDF2', hash: 'SHA3-256', salt: Buffer.alloc(0), iterations: 1 }, + 'HKDF'], + [!boringSSL, + { name: 'X25519', public: X25519.publicKey }, + { name: 'HMAC', hash: 'SHA3-256', length: 256 }], + [!boringSSL, + { name: 'ECDH', public: ECDH.publicKey }, + { name: 'HMAC', hash: 'SHA3-256', length: 256 }], + + // This interaction is not defined for now. + // https://github.com/WICG/webcrypto-modern-algos/issues/23 + [false, + { name: 'HKDF', hash: 'SHA3-256', salt: Buffer.alloc(0), info: Buffer.alloc(0) }, + { name: 'HMAC', hash: 'SHA3-256' }], + [false, + { name: 'PBKDF2', hash: 'SHA3-256', salt: Buffer.alloc(0), iterations: 1 }, + { name: 'HMAC', hash: 'SHA3-256' }], + [false, + { name: 'X25519', public: X25519.publicKey }, + { name: 'HMAC', hash: 'SHA3-256' }], + [false, + { name: 'ECDH', public: ECDH.publicKey }, + { name: 'HMAC', hash: 'SHA3-256' }], + ], + 'deriveBits': [ + [!boringSSL, { name: 'HKDF', hash: 'SHA3-256', salt: Buffer.alloc(0), info: Buffer.alloc(0) }, 8], + [!boringSSL, { name: 'HKDF', hash: 'SHA3-256', salt: Buffer.alloc(0), info: Buffer.alloc(0) }, 0], + [false, { name: 'HKDF', hash: 'SHA3-256', salt: Buffer.alloc(0), info: Buffer.alloc(0) }, null], + [false, { name: 'HKDF', hash: 'SHA3-256', salt: Buffer.alloc(0), info: Buffer.alloc(0) }, 7], + + [!boringSSL, { name: 'PBKDF2', hash: 'SHA3-256', salt: Buffer.alloc(0), iterations: 1 }, 8], + [!boringSSL, { name: 'PBKDF2', hash: 'SHA3-256', salt: Buffer.alloc(0), iterations: 1 }, 0], + [false, { name: 'PBKDF2', hash: 'SHA3-256', salt: Buffer.alloc(0), iterations: 0 }, 8], + [false, { name: 'PBKDF2', hash: 'SHA3-256', salt: Buffer.alloc(0), iterations: 1 }, null], + [false, { name: 'PBKDF2', hash: 'SHA3-256', salt: Buffer.alloc(0), iterations: 1 }, 7], + ], + 'importKey': [ + [!boringSSL, { name: 'HMAC', hash: 'SHA3-256' }], + [!boringSSL, { name: 'HMAC', hash: 'SHA3-256', length: 256 }], + [false, { name: 'HMAC', hash: 'SHA3-256', length: 25 }], + [!boringSSL, { name: 'RSASSA-PKCS1-v1_5', hash: 'SHA3-256', ...RSA_KEY_GEN }], + [!boringSSL, { name: 'RSA-PSS', hash: 'SHA3-256', ...RSA_KEY_GEN }], + [!boringSSL, { name: 'RSA-OAEP', hash: 'SHA3-256', ...RSA_KEY_GEN }], + [!boringSSL, { name: 'HMAC', hash: 'SHA3-256' }], + [!boringSSL, { name: 'HMAC', hash: 'SHA3-256', length: 256 }], + [false, { name: 'HMAC', hash: 'SHA3-256', length: 25 }], + [false, { name: 'HMAC', hash: 'SHA3-256', length: 0 }], + ], + 'get key length': [ + [false, { name: 'HMAC', hash: 'SHA3-256' }], + ], +}; diff --git a/test/fixtures/wpt/README.md b/test/fixtures/wpt/README.md index 06872e85e556f2..4044b555c7a0d5 100644 --- a/test/fixtures/wpt/README.md +++ b/test/fixtures/wpt/README.md @@ -34,7 +34,7 @@ Last update: - wasm/jsapi: https://github.com/web-platform-tests/wpt/tree/cde25e7e3c/wasm/jsapi - wasm/webapi: https://github.com/web-platform-tests/wpt/tree/fd1b23eeaa/wasm/webapi - web-locks: https://github.com/web-platform-tests/wpt/tree/10a122a6bc/web-locks -- WebCryptoAPI: https://github.com/web-platform-tests/wpt/tree/1d2c5fb36a/WebCryptoAPI +- WebCryptoAPI: https://github.com/web-platform-tests/wpt/tree/ff26d9b307/WebCryptoAPI - webidl/ecmascript-binding/es-exceptions: https://github.com/web-platform-tests/wpt/tree/2f96fa1996/webidl/ecmascript-binding/es-exceptions - webmessaging/broadcastchannel: https://github.com/web-platform-tests/wpt/tree/6495c91853/webmessaging/broadcastchannel - webstorage: https://github.com/web-platform-tests/wpt/tree/1d2c5fb36a/webstorage diff --git a/test/fixtures/wpt/WebCryptoAPI/import_export/okp_importKey_failures_fixtures.js b/test/fixtures/wpt/WebCryptoAPI/import_export/okp_importKey_failures_fixtures.js index cac6db1dcac758..6a7f583e0165c5 100644 --- a/test/fixtures/wpt/WebCryptoAPI/import_export/okp_importKey_failures_fixtures.js +++ b/test/fixtures/wpt/WebCryptoAPI/import_export/okp_importKey_failures_fixtures.js @@ -432,7 +432,7 @@ var mismatchedKtyField = { // The 'kty' field doesn't match the key algorithm. var mismatchedCrvField = { "Ed25519": "X25519", - "X25519": "Ed448", - "Ed448": "X25519", - "X448": "Ed25519", + "X25519": "Ed25519", + "Ed448": "X448", + "X448": "Ed448", } diff --git a/test/fixtures/wpt/versions.json b/test/fixtures/wpt/versions.json index 5c4c0305254155..521ec897f9a7be 100644 --- a/test/fixtures/wpt/versions.json +++ b/test/fixtures/wpt/versions.json @@ -96,7 +96,7 @@ "path": "web-locks" }, "WebCryptoAPI": { - "commit": "1d2c5fb36a6e477c8f915bde7eca027be6abe792", + "commit": "ff26d9b307b981b3f1b88f454268e6bb8933e6c2", "path": "WebCryptoAPI" }, "webidl/ecmascript-binding/es-exceptions": { diff --git a/test/parallel/parallel.status b/test/parallel/parallel.status index 76e46b80719e07..a46d3c52b1500e 100644 --- a/test/parallel/parallel.status +++ b/test/parallel/parallel.status @@ -26,12 +26,9 @@ test-async-context-frame: PASS, FLAKY test-runner-run-watch: PASS, FLAKY # https://github.com/nodejs/node/pull/59408#issuecomment-3170650933 test-fs-cp-sync-error-on-exist: PASS, FLAKY -test-fs-cp-sync-copy-symlink-not-pointing-to-folder: PASS, FLAKY test-fs-cp-sync-symlink-points-to-dest-error: PASS, FLAKY -test-fs-cp-sync-resolve-relative-symlinks-false: PASS, FLAKY test-fs-cp-async-symlink-points-to-dest: PASS, FLAKY test-fs-cp-sync-unicode-folder-names: PASS, FLAKY -test-fs-cp-sync-resolve-relative-symlinks-default: PASS, FLAKY # https://github.com/nodejs/node/issues/56751 test-without-async-context-frame: PASS, FLAKY diff --git a/test/parallel/test-abortsignal-cloneable.js b/test/parallel/test-abortsignal-cloneable.js index ffffcc5f43fa0d..cfd194909db38e 100644 --- a/test/parallel/test-abortsignal-cloneable.js +++ b/test/parallel/test-abortsignal-cloneable.js @@ -35,7 +35,7 @@ test('Can create a transferable abort controller', async () => { mc.port2.postMessage(ac.signal, [ac.signal]); - // Can be cloned/transferd multiple times and they all still work + // Can be cloned/transferred multiple times and they all still work mc.port2.postMessage(ac.signal, [ac.signal]); // Although we're using transfer semantics, the local AbortSignal diff --git a/test/parallel/test-abortsignal-drop-settled-signals.mjs b/test/parallel/test-abortsignal-drop-settled-signals.mjs index b300b0e223fc93..bb7448eacb1c34 100644 --- a/test/parallel/test-abortsignal-drop-settled-signals.mjs +++ b/test/parallel/test-abortsignal-drop-settled-signals.mjs @@ -104,17 +104,17 @@ const limit = 10_000; describe('when there is a long-lived signal', () => { it('drops settled dependant signals', (t, done) => { - makeSubsequentCalls(limit, (signal, depandantSignalsKey) => { + makeSubsequentCalls(limit, (signal, dependantSignalsKey) => { setImmediate(() => { - t.assert.strictEqual(signal[depandantSignalsKey].size, 0); + t.assert.strictEqual(signal[dependantSignalsKey].size, 0); done(); }); }); }); it('keeps all active dependant signals', (t, done) => { - makeSubsequentCalls(limit, (signal, depandantSignalsKey) => { - t.assert.strictEqual(signal[depandantSignalsKey].size, limit); + makeSubsequentCalls(limit, (signal, dependantSignalsKey) => { + t.assert.strictEqual(signal[dependantSignalsKey].size, limit); done(); }, true); diff --git a/test/parallel/test-child-process-send-returns-boolean.js b/test/parallel/test-child-process-send-returns-boolean.js index 8c3ef46438379c..3d2e913dc265a6 100644 --- a/test/parallel/test-child-process-send-returns-boolean.js +++ b/test/parallel/test-child-process-send-returns-boolean.js @@ -33,19 +33,19 @@ const subScript = fixtures.path('child-process-persistent.js'); // Sending a handle and not giving the tickQueue time to acknowledge should // create the internal backlog, but leave it empty. - const rv1 = s.send('one', handle, (err) => { if (err) assert.fail(err); }); + const rv1 = s.send('one', handle, assert.ifError); assert.strictEqual(rv1, true); // Since the first `send` included a handle (should be unacknowledged), // we can safely queue up only one more message. - const rv2 = s.send('two', (err) => { if (err) assert.fail(err); }); + const rv2 = s.send('two', assert.ifError); assert.strictEqual(rv2, true); // The backlog should now be indicate to backoff. - const rv3 = s.send('three', (err) => { if (err) assert.fail(err); }); + const rv3 = s.send('three', assert.ifError); assert.strictEqual(rv3, false); const rv4 = s.send('four', (err) => { - if (err) assert.fail(err); + assert.ifError(err); // `send` queue should have been drained. - const rv5 = s.send('5', handle, (err) => { if (err) assert.fail(err); }); + const rv5 = s.send('5', handle, assert.ifError); assert.strictEqual(rv5, true); // End test and cleanup. diff --git a/test/parallel/test-cli-options-as-flags.js b/test/parallel/test-cli-options-as-flags.js new file mode 100644 index 00000000000000..c9d92b69f72a45 --- /dev/null +++ b/test/parallel/test-cli-options-as-flags.js @@ -0,0 +1,111 @@ +'use strict'; + +const { + spawnPromisified, +} = require('../common'); +const fixtures = require('../common/fixtures'); +const { strictEqual } = require('node:assert'); +const { describe, it } = require('node:test'); +const path = require('node:path'); + +const fixtureFile = fixtures.path(path.join('options-as-flags', 'fixture.cjs')); +const configFile = fixtures.path(path.join('options-as-flags', 'test-config.json')); +const envFile = fixtures.path(path.join('options-as-flags', '.test.env')); + +describe('getOptionsAsFlagsFromBinding', () => { + it('should extract flags from command line arguments', async () => { + const result = await spawnPromisified(process.execPath, [ + '--no-warnings', + '--expose-internals', + '--stack-trace-limit=512', + fixtureFile, + ]); + + strictEqual(result.code, 0); + const flags = JSON.parse(result.stdout.trim()); + + strictEqual(flags.includes('--no-warnings'), true); + strictEqual(flags.includes('--stack-trace-limit=512'), true); + }); + + it('should extract flags from NODE_OPTIONS environment variable', async () => { + const result = await spawnPromisified(process.execPath, [ + '--no-warnings', + '--expose-internals', + fixtureFile, + ], { + env: { + ...process.env, + NODE_OPTIONS: '--stack-trace-limit=4096' + } + }); + + strictEqual(result.code, 0); + const flags = JSON.parse(result.stdout.trim()); + + // Should contain the flag from NODE_OPTIONS + strictEqual(flags.includes('--stack-trace-limit=4096'), true); + // Should also contain command line flags + strictEqual(flags.includes('--no-warnings'), true); + }); + + it('should extract flags from config file', async () => { + const result = await spawnPromisified(process.execPath, [ + '--no-warnings', + '--expose-internals', + '--experimental-config-file', + configFile, + fixtureFile, + ]); + + strictEqual(result.code, 0); + const flags = JSON.parse(result.stdout.trim()); + + // Should contain flags from config file + strictEqual(flags.includes('--experimental-transform-types'), true); + strictEqual(flags.includes('--max-http-header-size=8192'), true); + strictEqual(flags.includes('--test-isolation=none'), true); + // Should also contain command line flags + strictEqual(flags.includes('--no-warnings'), true); + }); + + it('should extract flags from config file and command line', async () => { + const result = await spawnPromisified(process.execPath, [ + '--no-warnings', + '--expose-internals', + '--stack-trace-limit=512', + '--experimental-config-file', + configFile, + fixtureFile, + ]); + + strictEqual(result.code, 0); + const flags = JSON.parse(result.stdout.trim()); + + // Should contain flags from command line arguments + strictEqual(flags.includes('--no-warnings'), true); + strictEqual(flags.includes('--stack-trace-limit=512'), true); + + // Should contain flags from config file + strictEqual(flags.includes('--experimental-transform-types'), true); + strictEqual(flags.includes('--max-http-header-size=8192'), true); + strictEqual(flags.includes('--test-isolation=none'), true); + }); + + it('should extract flags from .env file', async () => { + const result = await spawnPromisified(process.execPath, [ + '--no-warnings', + '--expose-internals', + `--env-file=${envFile}`, + fixtureFile, + ]); + + strictEqual(result.code, 0); + const flags = JSON.parse(result.stdout.trim()); + + // Should contain flags from .env file (NODE_OPTIONS) + strictEqual(flags.includes('--v8-pool-size=8'), true); + // Should also contain command line flags + strictEqual(flags.includes('--no-warnings'), true); + }); +}); diff --git a/test/parallel/test-crypto-argon2-unsupported.js b/test/parallel/test-crypto-argon2-unsupported.js new file mode 100644 index 00000000000000..e0764d3293bd88 --- /dev/null +++ b/test/parallel/test-crypto-argon2-unsupported.js @@ -0,0 +1,14 @@ +'use strict'; +const common = require('../common'); +if (!common.hasCrypto) + common.skip('missing crypto'); + +const { hasOpenSSL } = require('../common/crypto'); + +if (hasOpenSSL(3, 2)) + common.skip('requires OpenSSL < 3.2'); + +const assert = require('node:assert'); +const crypto = require('node:crypto'); + +assert.throws(() => crypto.argon2(), { code: 'ERR_CRYPTO_ARGON2_NOT_SUPPORTED' }); diff --git a/test/parallel/test-crypto-argon2.js b/test/parallel/test-crypto-argon2.js new file mode 100644 index 00000000000000..268b722cf81bb2 --- /dev/null +++ b/test/parallel/test-crypto-argon2.js @@ -0,0 +1,139 @@ +'use strict'; +const common = require('../common'); +if (!common.hasCrypto) + common.skip('missing crypto'); + +const { hasOpenSSL } = require('../common/crypto'); + +if (!hasOpenSSL(3, 2)) + common.skip('requires OpenSSL >= 3.2'); + +const assert = require('node:assert'); +const crypto = require('node:crypto'); + +function runArgon2(algorithm, options) { + const syncResult = crypto.argon2Sync(algorithm, options); + + crypto.argon2(algorithm, options, + common.mustSucceed((asyncResult) => { + assert.deepStrictEqual(asyncResult, syncResult); + })); + + return syncResult; +} + +const message = Buffer.alloc(32, 0x01); +const nonce = Buffer.alloc(16, 0x02); +const secret = Buffer.alloc(8, 0x03); +const associatedData = Buffer.alloc(12, 0x04); +const defaults = { message, nonce, parallelism: 1, tagLength: 64, memory: 8, passes: 3 }; + +const good = [ + // Test vectors from RFC 9106 https://www.rfc-editor.org/rfc/rfc9106.html#name-test-vectors + // and OpenSSL 3.2 https://github.com/openssl/openssl/blob/6dfa998f7ea150f9c6d4e4727cf6d5c82a68a8da/test/recipes/30-test_evp_data/evpkdf_argon2.txt + // + // OpenSSL defaults are: + // - outlen: 64 + // - passes: 3 + // - parallelism: 1 + // - memory: 8 + // https://github.com/openssl/openssl/blob/6dfa998f7ea150f9c6d4e4727cf6d5c82a68a8da/providers/implementations/kdfs/argon2.c#L77-L82 + [ + 'argon2d', + { secret, associatedData, parallelism: 4, tagLength: 32, memory: 32 }, + '512b391b6f1162975371d30919734294f868e3be3984f3c1a13a4db9fabe4acb', + ], + [ + 'argon2i', + { secret, associatedData, parallelism: 4, tagLength: 32, memory: 32 }, + 'c814d9d1dc7f37aa13f0d77f2494bda1c8de6b016dd388d29952a4c4672b6ce8', + ], + [ + 'argon2id', + { secret, associatedData, parallelism: 4, tagLength: 32, memory: 32 }, + '0d640df58d78766c08c037a34a8b53c9d01ef0452d75b65eb52520e96b01e659', + ], + [ + 'argon2d', + { message: '1234567890', nonce: 'saltsalt' }, + 'd16ad773b1c6400d3193bc3e66271603e9de72bace20af3f89c236f5434cdec9' + + '9072ddfc6b9c77ea9f386c0e8d7cb0c37cec6ec3277a22c92d5be58ef67c7eaa', + ], + [ + 'argon2id', + { message: '', parallelism: 4, tagLength: 32, memory: 32 }, + '0a34f1abde67086c82e785eaf17c68382259a264f4e61b91cd2763cb75ac189a', + ], + [ + 'argon2d', + { message: '1234567890', nonce: 'saltsalt', parallelism: 2, memory: 65536 }, + '5ca0ab135de1241454840172696c301c7b8fd99a788cd11cf9699044cadf7fca' + + '0a6e3762cb3043a71adf6553db3fd7925101b0ccf8868b098492a4addb2486bc', + ], + [ + 'argon2i', + { parallelism: 4, tagLength: 32, memory: 32 }, + 'a9a7510e6db4d588ba3414cd0e094d480d683f97b9ccb612a544fe8ef65ba8e0', + ], + [ + 'argon2id', + { parallelism: 4, tagLength: 32, memory: 32 }, + '03aab965c12001c9d7d0d2de33192c0494b684bb148196d73c1df1acaf6d0c2e', + ], + [ + 'argon2d', + { message: '1234567890', nonce: 'saltsalt', parallelism: 2, tagLength: 128, memory: 65536 }, + 'a86c83a19f0b234ecba8c275d16d059153f961e4c39ec9b1be98b3e73d791789' + + '363682443ad594334048634e91c493affed0bc29fd329a0e553c00149d6db19a' + + 'f4e4a354aec14dbd575d78ba87d4a4bc4746666e7a4e6ee1572bbffc2eba308a' + + '2d825cb7b41fde3a95d5cff0dfa2d0fdd636b32aea8b4a3c532742d330bd1b90', + ], +]; + +// Test vectors that should fail. +const bad = [ + ['argon2id', { nonce: nonce.subarray(0, 7) }, 'parameters.nonce.byteLength'], // nonce.byteLength < 8 + ['argon2id', { tagLength: 3 }, 'parameters.tagLength'], // tagLength < 4 + ['argon2id', { tagLength: 2 ** 32 }, 'parameters.tagLength'], // tagLength > 2^(32)-1 + ['argon2id', { passes: 0 }, 'parameters.passes'], // passes < 2 + ['argon2id', { passes: 2 ** 32 }, 'parameters.passes'], // passes > 2^(32)-1 + ['argon2id', { parallelism: 0 }, 'parameters.parallelism'], // parallelism < 1 + ['argon2id', { parallelism: 2 ** 24 }, 'parameters.parallelism'], // Parallelism > 2^(24)-1 + ['argon2id', { parallelism: 4, memory: 16 }, 'parameters.memory'], // Memory < 8 * parallelism + ['argon2id', { memory: 2 ** 32 }, 'parameters.memory'], // memory > 2^(32)-1 +]; + +for (const [algorithm, overrides, expected] of good) { + const parameters = { ...defaults, ...overrides }; + const actual = runArgon2(algorithm, parameters); + assert.strictEqual(actual.toString('hex'), expected); +} + +for (const [algorithm, overrides, param] of bad) { + const expected = { + code: 'ERR_OUT_OF_RANGE', + message: new RegExp(`The value of "${param}" is out of range`), + }; + const parameters = { ...defaults, ...overrides }; + assert.throws(() => crypto.argon2(algorithm, parameters, () => {}), expected); + assert.throws(() => crypto.argon2Sync(algorithm, parameters), expected); +} + +for (const key of Object.keys(defaults)) { + const expected = { + code: 'ERR_INVALID_ARG_TYPE', + message: new RegExp(`"parameters\\.${key}"`), + }; + const parameters = { ...defaults }; + delete parameters[key]; + assert.throws(() => crypto.argon2('argon2id', parameters, () => {}), expected); + assert.throws(() => crypto.argon2Sync('argon2id', parameters), expected); +} + +{ + const expected = { code: 'ERR_INVALID_ARG_TYPE' }; + assert.throws(() => crypto.argon2(), expected); + assert.throws(() => crypto.argon2('argon2id', null), expected); + assert.throws(() => crypto.argon2('argon2id', defaults, null), expected); + assert.throws(() => crypto.argon2('argon2id', defaults, {}), expected); +} diff --git a/test/parallel/test-crypto-encap-decap.js b/test/parallel/test-crypto-encap-decap.js new file mode 100644 index 00000000000000..2c2ccd42ca2365 --- /dev/null +++ b/test/parallel/test-crypto-encap-decap.js @@ -0,0 +1,211 @@ +'use strict'; +const common = require('../common'); +if (!common.hasCrypto) + common.skip('missing crypto'); + +const assert = require('assert'); +const crypto = require('crypto'); +const fixtures = require('../common/fixtures'); +const { hasOpenSSL } = require('../common/crypto'); +const { promisify } = require('util'); + +if (!hasOpenSSL(3)) { + assert.throws(() => crypto.encapsulate(), { code: 'ERR_CRYPTO_KEM_NOT_SUPPORTED' }); + return; +} + +assert.throws(() => crypto.encapsulate(), { code: 'ERR_INVALID_ARG_TYPE', + message: /The "key" argument must be of type/ }); +assert.throws(() => crypto.decapsulate(), { code: 'ERR_INVALID_ARG_TYPE', + message: /The "key" argument must be of type/ }); + +const keys = { + 'rsa': { + supported: hasOpenSSL(3), // RSASVE was added in 3.0 + publicKey: fixtures.readKey('rsa_public_2048.pem', 'ascii'), + privateKey: fixtures.readKey('rsa_private_2048.pem', 'ascii'), + sharedSecretLength: 256, + ciphertextLength: 256, + }, + 'rsa-pss': { + supported: false, // Only raw RSA is supported + publicKey: fixtures.readKey('rsa_pss_public_2048.pem', 'ascii'), + privateKey: fixtures.readKey('rsa_pss_private_2048.pem', 'ascii'), + }, + 'p-256': { + supported: hasOpenSSL(3, 2), // DHKEM was added in 3.2 + publicKey: fixtures.readKey('ec_p256_public.pem', 'ascii'), + privateKey: fixtures.readKey('ec_p256_private.pem', 'ascii'), + sharedSecretLength: 32, + ciphertextLength: 65, + }, + 'p-384': { + supported: hasOpenSSL(3, 2), // DHKEM was added in 3.2 + publicKey: fixtures.readKey('ec_p384_public.pem', 'ascii'), + privateKey: fixtures.readKey('ec_p384_private.pem', 'ascii'), + sharedSecretLength: 48, + ciphertextLength: 97, + }, + 'p-521': { + supported: hasOpenSSL(3, 2), // DHKEM was added in 3.2 + publicKey: fixtures.readKey('ec_p521_public.pem', 'ascii'), + privateKey: fixtures.readKey('ec_p521_private.pem', 'ascii'), + sharedSecretLength: 64, + ciphertextLength: 133, + }, + 'secp256k1': { + supported: false, // only P-256, P-384, and P-521 are supported + publicKey: fixtures.readKey('ec_secp256k1_public.pem', 'ascii'), + privateKey: fixtures.readKey('ec_secp256k1_private.pem', 'ascii'), + }, + 'x25519': { + supported: hasOpenSSL(3, 2), // DHKEM was added in 3.2 + publicKey: fixtures.readKey('x25519_public.pem', 'ascii'), + privateKey: fixtures.readKey('x25519_private.pem', 'ascii'), + sharedSecretLength: 32, + ciphertextLength: 32, + }, + 'x448': { + supported: hasOpenSSL(3, 2), // DHKEM was added in 3.2 + publicKey: fixtures.readKey('x448_public.pem', 'ascii'), + privateKey: fixtures.readKey('x448_private.pem', 'ascii'), + sharedSecretLength: 64, + ciphertextLength: 56, + }, + 'ml-kem-512': { + supported: hasOpenSSL(3, 5), + publicKey: fixtures.readKey('ml_kem_512_public.pem', 'ascii'), + privateKey: fixtures.readKey('ml_kem_512_private.pem', 'ascii'), + sharedSecretLength: 32, + ciphertextLength: 768, + }, + 'ml-kem-768': { + supported: hasOpenSSL(3, 5), + publicKey: fixtures.readKey('ml_kem_768_public.pem', 'ascii'), + privateKey: fixtures.readKey('ml_kem_768_private.pem', 'ascii'), + sharedSecretLength: 32, + ciphertextLength: 1088, + }, + 'ml-kem-1024': { + supported: hasOpenSSL(3, 5), + publicKey: fixtures.readKey('ml_kem_1024_public.pem', 'ascii'), + privateKey: fixtures.readKey('ml_kem_1024_private.pem', 'ascii'), + sharedSecretLength: 32, + ciphertextLength: 1568, + }, +}; + +for (const [name, { supported, publicKey, privateKey, sharedSecretLength, ciphertextLength }] of Object.entries(keys)) { + if (!supported) { + assert.throws(() => crypto.encapsulate(publicKey), + { code: /ERR_OSSL_EVP_DECODE_ERROR|ERR_CRYPTO_OPERATION_FAILED/ }); + continue; + } + + { + assert.throws(() => crypto.decapsulate(privateKey, null), + { + code: 'ERR_INVALID_ARG_TYPE', + message: /instance of ArrayBuffer, Buffer, TypedArray, or DataView\. Received null/ + }); + } + + function formatKeyAs(key, params) { + return { ...params, key: key.export(params) }; + } + + const keyObjects = { + publicKey: crypto.createPublicKey(publicKey), + privateKey: crypto.createPrivateKey(privateKey), + }; + + const keyPairs = [ + keyObjects, + { publicKey, privateKey }, + { + publicKey: formatKeyAs(keyObjects.publicKey, { format: 'der', type: 'spki' }), + privateKey: formatKeyAs(keyObjects.privateKey, { format: 'der', type: 'pkcs8' }) + }, + ]; + + // TODO(@panva): ML-KEM does not have a JWK format defined yet, add once standardized + if (!keyObjects.privateKey.asymmetricKeyType.startsWith('ml')) { + keyPairs.push({ + publicKey: formatKeyAs(keyObjects.publicKey, { format: 'jwk' }), + privateKey: formatKeyAs(keyObjects.privateKey, { format: 'jwk' }) + }); + } + + for (const kp of keyPairs) { + // sync + { + const { sharedKey, ciphertext } = crypto.encapsulate(kp.publicKey); + assert(Buffer.isBuffer(sharedKey)); + assert.strictEqual(sharedKey.byteLength, sharedSecretLength); + assert(Buffer.isBuffer(ciphertext)); + assert.strictEqual(ciphertext.byteLength, ciphertextLength); + const sharedKey2 = crypto.decapsulate(kp.privateKey, ciphertext); + assert(Buffer.isBuffer(sharedKey2)); + assert.strictEqual(sharedKey2.byteLength, sharedSecretLength); + assert(sharedKey.equals(sharedKey2)); + } + + // async + { + crypto.encapsulate(kp.publicKey, common.mustSucceed(({ sharedKey, ciphertext }) => { + assert(Buffer.isBuffer(sharedKey)); + assert.strictEqual(sharedKey.byteLength, sharedSecretLength); + assert(Buffer.isBuffer(ciphertext)); + assert.strictEqual(ciphertext.byteLength, ciphertextLength); + crypto.decapsulate(kp.privateKey, ciphertext, common.mustSucceed((sharedKey2) => { + assert(Buffer.isBuffer(sharedKey2)); + assert.strictEqual(sharedKey2.byteLength, sharedSecretLength); + assert(sharedKey.equals(sharedKey2)); + })); + })); + } + + // promisified + (async () => { + const { sharedKey, ciphertext } = await promisify(crypto.encapsulate)(kp.publicKey); + assert(Buffer.isBuffer(sharedKey)); + assert.strictEqual(sharedKey.byteLength, sharedSecretLength); + assert(Buffer.isBuffer(ciphertext)); + assert.strictEqual(ciphertext.byteLength, ciphertextLength); + const sharedKey2 = await promisify(crypto.decapsulate)(kp.privateKey, ciphertext); + assert(Buffer.isBuffer(sharedKey2)); + assert.strictEqual(sharedKey2.byteLength, sharedSecretLength); + assert(sharedKey.equals(sharedKey2)); + })().then(common.mustCall()); + } + + let wrongPrivateKey; + if (name.startsWith('x')) { + wrongPrivateKey = name === 'x448' ? keys.x25519.privateKey : keys.x448.privateKey; + } else if (name.startsWith('p-')) { + wrongPrivateKey = name === 'p-256' ? keys['p-384'].privateKey : keys['p-256'].privateKey; + } else if (name.startsWith('ml-')) { + wrongPrivateKey = name === 'ml-kem-512' ? keys['ml-kem-768'].privateKey : keys['ml-kem-512'].privateKey; + } else { + wrongPrivateKey = keys.x25519.privateKey; + } + + // sync errors + { + const { ciphertext } = crypto.encapsulate(publicKey); + assert.throws(() => crypto.decapsulate(wrongPrivateKey, ciphertext), { + message: /Failed to (initialize|perform) decapsulation/, + code: 'ERR_CRYPTO_OPERATION_FAILED', + }); + } + + // async errors + { + crypto.encapsulate(publicKey, common.mustSucceed(({ ciphertext }) => { + crypto.decapsulate(wrongPrivateKey, ciphertext, common.mustCall((err) => { + assert(err); + assert.strictEqual(err.message, 'Deriving bits failed'); + })); + })); + } +} diff --git a/test/parallel/test-crypto-key-objects-to-crypto-key.js b/test/parallel/test-crypto-key-objects-to-crypto-key.js index 1656f37a3c58b5..0fe1b06996c19d 100644 --- a/test/parallel/test-crypto-key-objects-to-crypto-key.js +++ b/test/parallel/test-crypto-key-objects-to-crypto-key.js @@ -4,6 +4,8 @@ const common = require('../common'); if (!common.hasCrypto) common.skip('missing crypto'); +const { hasOpenSSL } = require('../common/crypto'); + const assert = require('assert'); const { createSecretKey, @@ -23,13 +25,16 @@ function assertCryptoKey(cryptoKey, keyObject, algorithm, extractable, usages) { { for (const length of [128, 192, 256]) { - const aes = createSecretKey(randomBytes(length >> 3)); - for (const algorithm of ['AES-CTR', 'AES-CBC', 'AES-GCM', 'AES-KW']) { + const key = createSecretKey(randomBytes(length >> 3)); + const algorithms = ['AES-CTR', 'AES-CBC', 'AES-GCM', 'AES-KW']; + if (length === 256) + algorithms.push('ChaCha20-Poly1305'); + for (const algorithm of algorithms) { const usages = algorithm === 'AES-KW' ? ['wrapKey', 'unwrapKey'] : ['encrypt', 'decrypt']; for (const extractable of [true, false]) { - const cryptoKey = aes.toCryptoKey(algorithm, extractable, usages); - assertCryptoKey(cryptoKey, aes, algorithm, extractable, usages); - assert.strictEqual(cryptoKey.algorithm.length, length); + const cryptoKey = key.toCryptoKey(algorithm, extractable, usages); + assertCryptoKey(cryptoKey, key, algorithm, extractable, usages); + assert.strictEqual(cryptoKey.algorithm.length, algorithm !== 'ChaCha20-Poly1305' ? length : undefined); } } } @@ -180,3 +185,23 @@ function assertCryptoKey(cryptoKey, keyObject, algorithm, extractable, usages) { } } } + +if (hasOpenSSL(3, 5)) { + for (const name of ['ML-DSA-44', 'ML-DSA-65', 'ML-DSA-87']) { + const { publicKey, privateKey } = generateKeyPairSync(name.toLowerCase()); + assert.throws(() => { + privateKey.toCryptoKey(name, true, []); + }, { + name: 'SyntaxError', + message: 'Usages cannot be empty when importing a private key.' + }); + for (const key of [publicKey, privateKey]) { + const usages = key.type === 'public' ? ['verify'] : ['sign']; + for (const extractable of [true, false]) { + const cryptoKey = key.toCryptoKey({ name }, extractable, usages); + assertCryptoKey(cryptoKey, key, name, extractable, usages); + assert.strictEqual(cryptoKey.algorithm.name, name); + } + } + } +} diff --git a/test/parallel/test-crypto-pqc-key-objects-ml-kem.js b/test/parallel/test-crypto-pqc-key-objects-ml-kem.js new file mode 100644 index 00000000000000..293c0c87265eea --- /dev/null +++ b/test/parallel/test-crypto-pqc-key-objects-ml-kem.js @@ -0,0 +1,86 @@ +'use strict'; + +const common = require('../common'); +if (!common.hasCrypto) + common.skip('missing crypto'); + +const { hasOpenSSL } = require('../common/crypto'); + +const assert = require('assert'); +const { + createPublicKey, + createPrivateKey, +} = require('crypto'); + +const fixtures = require('../common/fixtures'); + +function getKeyFileName(type, suffix) { + return `${type.replaceAll('-', '_')}_${suffix}.pem`; +} + +for (const asymmetricKeyType of ['ml-kem-512', 'ml-kem-768', 'ml-kem-1024']) { + const keys = { + public: fixtures.readKey(getKeyFileName(asymmetricKeyType, 'public'), 'ascii'), + private: fixtures.readKey(getKeyFileName(asymmetricKeyType, 'private'), 'ascii'), + private_seed_only: fixtures.readKey(getKeyFileName(asymmetricKeyType, 'private_seed_only'), 'ascii'), + private_priv_only: fixtures.readKey(getKeyFileName(asymmetricKeyType, 'private_priv_only'), 'ascii'), + }; + + function assertKey(key) { + assert.deepStrictEqual(key.asymmetricKeyDetails, {}); + assert.strictEqual(key.asymmetricKeyType, asymmetricKeyType); + assert.strictEqual(key.equals(key), true); + assert.deepStrictEqual(key, key); + } + + function assertPublicKey(key) { + assertKey(key); + assert.strictEqual(key.type, 'public'); + assert.strictEqual(key.export({ format: 'pem', type: 'spki' }), keys.public); + key.export({ format: 'der', type: 'spki' }); + assert.throws(() => key.export({ format: 'jwk' }), + { code: 'ERR_CRYPTO_JWK_UNSUPPORTED_KEY_TYPE', message: 'Unsupported JWK Key Type.' }); + } + + function assertPrivateKey(key, hasSeed) { + assertKey(key); + assert.strictEqual(key.type, 'private'); + assertPublicKey(createPublicKey(key)); + key.export({ format: 'der', type: 'pkcs8' }); + if (hasSeed) { + assert.strictEqual(key.export({ format: 'pem', type: 'pkcs8' }), keys.private); + } else { + assert.strictEqual(key.export({ format: 'pem', type: 'pkcs8' }), keys.private_priv_only); + } + assert.throws(() => key.export({ format: 'jwk' }), + { code: 'ERR_CRYPTO_JWK_UNSUPPORTED_KEY_TYPE', message: 'Unsupported JWK Key Type.' }); + } + + if (!hasOpenSSL(3, 5)) { + assert.throws(() => createPublicKey(keys.public), { + code: hasOpenSSL(3) ? 'ERR_OSSL_EVP_DECODE_ERROR' : 'ERR_OSSL_EVP_UNSUPPORTED_ALGORITHM', + }); + + for (const pem of [keys.private, keys.private_seed_only, keys.private_priv_only]) { + assert.throws(() => createPrivateKey(pem), { + code: hasOpenSSL(3) ? 'ERR_OSSL_UNSUPPORTED' : 'ERR_OSSL_EVP_UNSUPPORTED_ALGORITHM', + }); + } + } else { + const publicKey = createPublicKey(keys.public); + assertPublicKey(publicKey); + + { + for (const [pem, hasSeed] of [ + [keys.private, true], + [keys.private_seed_only, true], + [keys.private_priv_only, false], + ]) { + const pubFromPriv = createPublicKey(pem); + assertPublicKey(pubFromPriv); + assertPrivateKey(createPrivateKey(pem), hasSeed); + assert.strictEqual(pubFromPriv.equals(publicKey), true); + } + } + } +} diff --git a/test/parallel/test-crypto-pqc-keygen-ml-kem.js b/test/parallel/test-crypto-pqc-keygen-ml-kem.js new file mode 100644 index 00000000000000..fbbbc4f8de9663 --- /dev/null +++ b/test/parallel/test-crypto-pqc-keygen-ml-kem.js @@ -0,0 +1,46 @@ +'use strict'; + +const common = require('../common'); +if (!common.hasCrypto) + common.skip('missing crypto'); + +const { hasOpenSSL } = require('../common/crypto'); + +const assert = require('assert'); +const { + generateKeyPair, +} = require('crypto'); + +if (!hasOpenSSL(3, 5)) { + for (const asymmetricKeyType of ['ml-kem-512', 'ml-kem-768', 'ml-kem-1024']) { + assert.throws(() => generateKeyPair(asymmetricKeyType, common.mustNotCall()), { + code: 'ERR_INVALID_ARG_VALUE', + message: /The argument 'type' must be a supported key type/ + }); + } +} else { + for (const asymmetricKeyType of ['ml-kem-512', 'ml-kem-768', 'ml-kem-1024']) { + for (const [publicKeyEncoding, validate] of [ + [undefined, (publicKey) => { + assert.strictEqual(publicKey.type, 'public'); + assert.strictEqual(publicKey.asymmetricKeyType, asymmetricKeyType); + assert.deepStrictEqual(publicKey.asymmetricKeyDetails, {}); + }], + [{ format: 'pem', type: 'spki' }, (publicKey) => assert.strictEqual(typeof publicKey, 'string')], + [{ format: 'der', type: 'spki' }, (publicKey) => assert.strictEqual(Buffer.isBuffer(publicKey), true)], + ]) { + generateKeyPair(asymmetricKeyType, { publicKeyEncoding }, common.mustSucceed(validate)); + } + for (const [privateKeyEncoding, validate] of [ + [undefined, (_, privateKey) => { + assert.strictEqual(privateKey.type, 'private'); + assert.strictEqual(privateKey.asymmetricKeyType, asymmetricKeyType); + assert.deepStrictEqual(privateKey.asymmetricKeyDetails, {}); + }], + [{ format: 'pem', type: 'pkcs8' }, (_, privateKey) => assert.strictEqual(typeof privateKey, 'string')], + [{ format: 'der', type: 'pkcs8' }, (_, privateKey) => assert.strictEqual(Buffer.isBuffer(privateKey), true)], + ]) { + generateKeyPair(asymmetricKeyType, { privateKeyEncoding }, common.mustSucceed(validate)); + } + } +} diff --git a/test/parallel/test-debug-process.js b/test/parallel/test-debug-process.js index 0d10a15e2eefa0..4eb5ad7b4d5db8 100644 --- a/test/parallel/test-debug-process.js +++ b/test/parallel/test-debug-process.js @@ -16,6 +16,6 @@ cp.on('exit', common.mustCall(function() { try { process._debugProcess(cp.pid); } catch (error) { - assert.strictEqual(error.message, 'The system cannot find the file specified.'); + assert.strictEqual(error.errno, 2); } })); diff --git a/test/parallel/test-dgram-blocklist.js b/test/parallel/test-dgram-blocklist.js index 8af6522e7bd2d2..0fdc50f38bd304 100644 --- a/test/parallel/test-dgram-blocklist.js +++ b/test/parallel/test-dgram-blocklist.js @@ -41,9 +41,13 @@ const net = require('net'); receiveSocket.bind(0, common.localhostIPv4, common.mustCall(() => { const addressInfo = receiveSocket.address(); const client = dgram.createSocket('udp4'); - client.send('hello', addressInfo.port, addressInfo.address, common.mustCall((err) => { - assert.ok(!err); - client.close(); - })); + client.send( + 'hello', + addressInfo.port, + addressInfo.address, + common.mustSucceed(() => { + client.close(); + }) + ); })); } diff --git a/test/parallel/test-diagnostics-channel-http2-client-stream-created.js b/test/parallel/test-diagnostics-channel-http2-client-stream-created.js index 75806db591438e..53e3b4b11e613b 100644 --- a/test/parallel/test-diagnostics-channel-http2-client-stream-created.js +++ b/test/parallel/test-diagnostics-channel-http2-client-stream-created.js @@ -18,6 +18,9 @@ const { Duplex } = require('stream'); const clientHttp2StreamCreationCount = 2; +let countdown; +let port; + dc.subscribe('http2.client.stream.created', common.mustCall(({ stream, headers }) => { // Since ClientHttp2Stream is not exported from any module, this just checks // if the stream is an instance of Duplex and the constructor name is @@ -25,6 +28,28 @@ dc.subscribe('http2.client.stream.created', common.mustCall(({ stream, headers } assert.ok(stream instanceof Duplex); assert.strictEqual(stream.constructor.name, 'ClientHttp2Stream'); assert.ok(headers && !Array.isArray(headers) && typeof headers === 'object'); + if (countdown.remaining === clientHttp2StreamCreationCount) { + // The request stream headers. + assert.deepStrictEqual(headers, { + '__proto__': null, + ':method': 'GET', + ':authority': `localhost:${port}`, + ':scheme': 'http', + ':path': '/', + 'requestHeader': 'requestValue', + }); + } else { + // The push stream headers. + assert.deepStrictEqual(headers, { + '__proto__': null, + ':method': 'GET', + ':authority': `localhost:${port}`, + ':scheme': 'http', + ':path': '/', + [http2.sensitiveHeaders]: [], + 'pushheader': 'pushValue', + }); + } }, clientHttp2StreamCreationCount)); const server = http2.createServer(); @@ -32,22 +57,22 @@ server.on('stream', common.mustCall((stream) => { stream.respond(); stream.end(); - stream.pushStream({}, common.mustSucceed((pushStream) => { + stream.pushStream({ 'pushHeader': 'pushValue' }, common.mustSucceed((pushStream) => { pushStream.respond(); pushStream.end(); }, 1)); }, 1)); server.listen(0, common.mustCall(() => { - const port = server.address().port; + port = server.address().port; const client = http2.connect(`http://localhost:${port}`); - const countdown = new Countdown(clientHttp2StreamCreationCount, () => { + countdown = new Countdown(clientHttp2StreamCreationCount, () => { client.close(); server.close(); }); - const stream = client.request({}); + const stream = client.request(['requestHeader', 'requestValue']); stream.on('response', common.mustCall(() => { countdown.dec(); })); diff --git a/test/parallel/test-diagnostics-channel-http2-client-stream-start.js b/test/parallel/test-diagnostics-channel-http2-client-stream-start.js index e8edf3a0a49f4a..36b9a5db4cc1f4 100644 --- a/test/parallel/test-diagnostics-channel-http2-client-stream-start.js +++ b/test/parallel/test-diagnostics-channel-http2-client-stream-start.js @@ -18,12 +18,38 @@ const { Duplex } = require('stream'); const clientHttp2StreamStartCount = 2; +let countdown; +let port; + dc.subscribe('http2.client.stream.start', common.mustCall(({ stream, headers }) => { // Since ClientHttp2Stream is not exported from any module, this just checks // if the stream is an instance of Duplex. assert.ok(stream instanceof Duplex); assert.strictEqual(stream.constructor.name, 'ClientHttp2Stream'); assert.ok(headers && !Array.isArray(headers) && typeof headers === 'object'); + + if (countdown.remaining === clientHttp2StreamStartCount) { + // The request stream headers. + assert.deepStrictEqual(headers, { + '__proto__': null, + ':method': 'GET', + ':authority': `localhost:${port}`, + ':scheme': 'http', + ':path': '/', + 'requestHeader': 'requestValue', + }); + } else { + // The push stream headers. + assert.deepStrictEqual(headers, { + '__proto__': null, + ':method': 'GET', + ':authority': `localhost:${port}`, + ':scheme': 'http', + ':path': '/', + [http2.sensitiveHeaders]: [], + 'pushheader': 'pushValue', + }); + } }, clientHttp2StreamStartCount)); const server = http2.createServer(); @@ -31,22 +57,22 @@ server.on('stream', common.mustCall((stream) => { stream.respond(); stream.end(); - stream.pushStream({}, common.mustSucceed((pushStream) => { + stream.pushStream({ 'pushHeader': 'pushValue' }, common.mustSucceed((pushStream) => { pushStream.respond(); pushStream.end(); }, 1)); }, 1)); server.listen(0, common.mustCall(() => { - const port = server.address().port; + port = server.address().port; const client = http2.connect(`http://localhost:${port}`); - const countdown = new Countdown(clientHttp2StreamStartCount, () => { + countdown = new Countdown(clientHttp2StreamStartCount, () => { client.close(); server.close(); }); - const stream = client.request({}); + const stream = client.request(['requestHeader', 'requestValue']); stream.on('response', common.mustCall(() => { countdown.dec(); })); diff --git a/test/parallel/test-fs-cp-sync-copy-symlink-not-pointing-to-folder.mjs b/test/parallel/test-fs-cp-sync-copy-symlink-not-pointing-to-folder.mjs index d23a2c8be63e6c..fa5ec6a7dbc821 100644 --- a/test/parallel/test-fs-cp-sync-copy-symlink-not-pointing-to-folder.mjs +++ b/test/parallel/test-fs-cp-sync-copy-symlink-not-pointing-to-folder.mjs @@ -1,5 +1,5 @@ // This tests that cpSync copies link if it does not point to folder in src. -import { mustNotMutateObjectDeep } from '../common/index.mjs'; +import { mustNotMutateObjectDeep, isWindows } from '../common/index.mjs'; import { nextdir } from '../common/fs.js'; import assert from 'node:assert'; import { cpSync, mkdirSync, symlinkSync, readlinkSync } from 'node:fs'; @@ -16,4 +16,11 @@ mkdirSync(join(dest, 'a'), mustNotMutateObjectDeep({ recursive: true })); symlinkSync(dest, join(dest, 'a', 'c')); cpSync(src, dest, mustNotMutateObjectDeep({ recursive: true })); const link = readlinkSync(join(dest, 'a', 'c')); -assert.strictEqual(link, src); + +if (isWindows) { + // On Windows, readlinkSync() may return a path with uppercase drive letter, + // but paths are case-insensitive. + assert.strictEqual(link.toLowerCase(), src.toLowerCase()); +} else { + assert.strictEqual(link, src); +} diff --git a/test/parallel/test-fs-cp-sync-resolve-relative-symlinks-default.mjs b/test/parallel/test-fs-cp-sync-resolve-relative-symlinks-default.mjs index 070fabd438749e..7c7622ed4050b2 100644 --- a/test/parallel/test-fs-cp-sync-resolve-relative-symlinks-default.mjs +++ b/test/parallel/test-fs-cp-sync-resolve-relative-symlinks-default.mjs @@ -1,5 +1,5 @@ // This tests that cpSync resolves relative symlinks to their absolute path by default. -import { mustNotMutateObjectDeep } from '../common/index.mjs'; +import { mustNotMutateObjectDeep, isWindows } from '../common/index.mjs'; import { nextdir } from '../common/fs.js'; import assert from 'node:assert'; import { cpSync, mkdirSync, writeFileSync, symlinkSync, readlinkSync } from 'node:fs'; @@ -18,4 +18,11 @@ mkdirSync(dest, mustNotMutateObjectDeep({ recursive: true })); cpSync(src, dest, mustNotMutateObjectDeep({ recursive: true })); const link = readlinkSync(join(dest, 'bar.js')); -assert.strictEqual(link, join(src, 'foo.js')); + +if (isWindows) { + // On Windows, readlinkSync() may return a path with uppercase drive letter, + // but paths are case-insensitive. + assert.strictEqual(link.toLowerCase(), join(src, 'foo.js').toLowerCase()); +} else { + assert.strictEqual(link, join(src, 'foo.js')); +} diff --git a/test/parallel/test-fs-cp-sync-resolve-relative-symlinks-false.mjs b/test/parallel/test-fs-cp-sync-resolve-relative-symlinks-false.mjs index e5f59d655f33f7..ac1ec01fd0b6ef 100644 --- a/test/parallel/test-fs-cp-sync-resolve-relative-symlinks-false.mjs +++ b/test/parallel/test-fs-cp-sync-resolve-relative-symlinks-false.mjs @@ -1,5 +1,5 @@ // This tests that cpSync resolves relative symlinks when verbatimSymlinks is false. -import { mustNotMutateObjectDeep } from '../common/index.mjs'; +import { mustNotMutateObjectDeep, isWindows } from '../common/index.mjs'; import { nextdir } from '../common/fs.js'; import assert from 'node:assert'; import { cpSync, mkdirSync, writeFileSync, symlinkSync, readlinkSync } from 'node:fs'; @@ -18,4 +18,11 @@ mkdirSync(dest, mustNotMutateObjectDeep({ recursive: true })); cpSync(src, dest, mustNotMutateObjectDeep({ recursive: true, verbatimSymlinks: false })); const link = readlinkSync(join(dest, 'bar.js')); -assert.strictEqual(link, join(src, 'foo.js')); + +if (isWindows) { + // On Windows, readlinkSync() may return a path with uppercase drive letter, + // but paths are case-insensitive. + assert.strictEqual(link.toLowerCase(), join(src, 'foo.js').toLowerCase()); +} else { + assert.strictEqual(link, join(src, 'foo.js')); +} diff --git a/test/parallel/test-fs-watchfile.js b/test/parallel/test-fs-watchfile.js index 6a83f120f70982..bd86835d9ff758 100644 --- a/test/parallel/test-fs-watchfile.js +++ b/test/parallel/test-fs-watchfile.js @@ -94,9 +94,7 @@ if (common.isLinux || common.isMacOS || common.isWindows) { })); const interval = setInterval(() => { - fs.writeFile(path.join(dir, 'foo.txt'), 'foo', common.mustCall((err) => { - if (err) assert.fail(err); - })); + fs.writeFile(path.join(dir, 'foo.txt'), 'foo', common.mustSucceed()); }, 1); } diff --git a/test/parallel/test-http-agent-keep-alive-timeout-buffer.js b/test/parallel/test-http-agent-keep-alive-timeout-buffer.js new file mode 100644 index 00000000000000..d7ec5df9b86c31 --- /dev/null +++ b/test/parallel/test-http-agent-keep-alive-timeout-buffer.js @@ -0,0 +1,44 @@ +'use strict'; + +const common = require('../common'); +const assert = require('assert'); +const http = require('http'); + +// Ensure agentKeepAliveTimeoutBuffer option sets the correct value or falls back to default. +{ + const agent1 = new http.Agent({ agentKeepAliveTimeoutBuffer: 1500, keepAlive: true }); + assert.strictEqual(agent1.agentKeepAliveTimeoutBuffer, 1500); + + const agent2 = new http.Agent({ agentKeepAliveTimeoutBuffer: -100, keepAlive: true }); + assert.strictEqual(agent2.agentKeepAliveTimeoutBuffer, 1000); + + const agent3 = new http.Agent({ agentKeepAliveTimeoutBuffer: Infinity, keepAlive: true }); + assert.strictEqual(agent3.agentKeepAliveTimeoutBuffer, 1000); + + const agent4 = new http.Agent({ keepAlive: true }); + assert.strictEqual(agent4.agentKeepAliveTimeoutBuffer, 1000); +} + +// Integration test with server sending Keep-Alive timeout header. +{ + const SERVER_TIMEOUT = 3; + const BUFFER = 1500; + + const server = http.createServer((req, res) => { + res.setHeader('Keep-Alive', `timeout=${SERVER_TIMEOUT}`); + res.end('ok'); + }); + + server.listen(0, common.mustCall(() => { + const agent = new http.Agent({ agentKeepAliveTimeoutBuffer: BUFFER, keepAlive: true }); + assert.strictEqual(agent.agentKeepAliveTimeoutBuffer, BUFFER); + + http.get({ port: server.address().port, agent }, (res) => { + res.resume(); + res.on('end', () => { + agent.destroy(); + server.close(); + }); + }); + })); +} diff --git a/test/parallel/test-http-header-overflow.js b/test/parallel/test-http-header-overflow.js index 0d0dffed89b089..930b0e6d5bacba 100644 --- a/test/parallel/test-http-header-overflow.js +++ b/test/parallel/test-http-header-overflow.js @@ -4,14 +4,13 @@ const assert = require('assert'); const { createServer, maxHeaderSize } = require('http'); const { createConnection } = require('net'); -const CRLF = '\r\n'; const DUMMY_HEADER_NAME = 'Cookie: '; const DUMMY_HEADER_VALUE = 'a'.repeat( // Plus one is to make it 1 byte too big maxHeaderSize - DUMMY_HEADER_NAME.length + 1 ); const PAYLOAD_GET = 'GET /blah HTTP/1.1'; -const PAYLOAD = PAYLOAD_GET + CRLF + DUMMY_HEADER_NAME + DUMMY_HEADER_VALUE; +const PAYLOAD = PAYLOAD_GET + '\r\n' + DUMMY_HEADER_NAME + DUMMY_HEADER_VALUE; const server = createServer(); diff --git a/test/parallel/test-net-server-drop-connections-in-cluster.js b/test/parallel/test-http-server-drop-connections-in-cluster.js similarity index 100% rename from test/parallel/test-net-server-drop-connections-in-cluster.js rename to test/parallel/test-http-server-drop-connections-in-cluster.js diff --git a/test/parallel/test-http-upgrade-client2.js b/test/parallel/test-http-upgrade-client2.js index 8883faa9e05067..8521a8ab47fb31 100644 --- a/test/parallel/test-http-upgrade-client2.js +++ b/test/parallel/test-http-upgrade-client2.js @@ -23,13 +23,11 @@ const common = require('../common'); const http = require('http'); -const CRLF = '\r\n'; - const server = http.createServer(); server.on('upgrade', function(req, socket) { - socket.write(`HTTP/1.1 101 Ok${CRLF}` + - `Connection: Upgrade${CRLF}` + - `Upgrade: Test${CRLF}${CRLF}` + + socket.write(`HTTP/1.1 101 Ok\r\n` + + `Connection: Upgrade\r\n` + + `Upgrade: Test\r\n\r\n` + 'head'); socket.on('end', function() { socket.end(); diff --git a/test/parallel/test-http2-raw-headers-defaults.js b/test/parallel/test-http2-raw-headers-defaults.js new file mode 100644 index 00000000000000..fa742605a02a9a --- /dev/null +++ b/test/parallel/test-http2-raw-headers-defaults.js @@ -0,0 +1,76 @@ +'use strict'; + +const common = require('../common'); +if (!common.hasCrypto) + common.skip('missing crypto'); +const assert = require('assert'); +const http2 = require('http2'); + +{ + const server = http2.createServer(); + server.on('stream', common.mustCall((stream, _headers, _flags, rawHeaders) => { + assert.deepStrictEqual(rawHeaders, [ + ':method', 'GET', + ':authority', `localhost:${server.address().port}`, + ':scheme', 'http', + ':path', '/', + 'a', 'b', + 'x-foo', 'bar', // Lowercased as required for HTTP/2 + 'a', 'c', // Duplicate header order preserved + ]); + stream.respond([ + 'x', '1', + 'x-FOO', 'bar', + 'x', '2', + ]); + + assert.partialDeepStrictEqual(stream.sentHeaders, { + '__proto__': null, + ':status': 200, + 'x': [ '1', '2' ], + 'x-FOO': 'bar', + }); + + assert.strictEqual(typeof stream.sentHeaders.date, 'string'); + + stream.end(); + })); + + + server.listen(0, common.mustCall(() => { + const port = server.address().port; + const client = http2.connect(`http://localhost:${port}`); + + const req = client.request([ + 'a', 'b', + 'x-FOO', 'bar', + 'a', 'c', + ]).end(); + + assert.deepStrictEqual(req.sentHeaders, { + '__proto__': null, + ':path': '/', + ':scheme': 'http', + ':authority': `localhost:${server.address().port}`, + ':method': 'GET', + 'a': [ 'b', 'c' ], + 'x-FOO': 'bar', + }); + + req.on('response', common.mustCall((_headers, _flags, rawHeaders) => { + assert.strictEqual(rawHeaders.length, 10); + assert.deepStrictEqual(rawHeaders.slice(0, 8), [ + ':status', '200', + 'x', '1', + 'x-foo', 'bar', // Lowercased as required for HTTP/2 + 'x', '2', // Duplicate header order preserved + ]); + + assert.strictEqual(rawHeaders[8], 'date'); + assert.strictEqual(typeof rawHeaders[9], 'string'); + + client.close(); + server.close(); + })); + })); +} diff --git a/test/parallel/test-http2-raw-headers.js b/test/parallel/test-http2-raw-headers.js index 8a84542a130fae..a77fe2db515962 100644 --- a/test/parallel/test-http2-raw-headers.js +++ b/test/parallel/test-http2-raw-headers.js @@ -8,19 +8,33 @@ const http2 = require('http2'); { const server = http2.createServer(); - server.on('stream', common.mustCall((stream, headers, flags, rawHeaders) => { + server.on('stream', common.mustCall((stream, _headers, _flags, rawHeaders) => { assert.deepStrictEqual(rawHeaders, [ ':path', '/foobar', ':scheme', 'http', - ':authority', `localhost:${server.address().port}`, - ':method', 'GET', + ':authority', `test.invalid:${server.address().port}`, + ':method', 'POST', 'a', 'b', - 'x-foo', 'bar', - 'a', 'c', + 'x-foo', 'bar', // Lowercased as required for HTTP/2 + 'a', 'c', // Duplicate header order preserved + ]); + + stream.respond([ + ':status', '404', + 'x', '1', + 'x-FOO', 'bar', + 'x', '2', + 'DATE', '0000', ]); - stream.respond({ - ':status': 200 + + assert.deepStrictEqual(stream.sentHeaders, { + '__proto__': null, + ':status': '404', + 'x': [ '1', '2' ], + 'x-FOO': 'bar', + 'DATE': '0000', }); + stream.end(); })); @@ -32,8 +46,8 @@ const http2 = require('http2'); const req = client.request([ ':path', '/foobar', ':scheme', 'http', - ':authority', `localhost:${server.address().port}`, - ':method', 'GET', + ':authority', `test.invalid:${server.address().port}`, + ':method', 'POST', 'a', 'b', 'x-FOO', 'bar', 'a', 'c', @@ -43,14 +57,20 @@ const http2 = require('http2'); '__proto__': null, ':path': '/foobar', ':scheme': 'http', - ':authority': `localhost:${server.address().port}`, - ':method': 'GET', + ':authority': `test.invalid:${server.address().port}`, + ':method': 'POST', 'a': [ 'b', 'c' ], 'x-FOO': 'bar', }); - req.on('response', common.mustCall((headers) => { - assert.strictEqual(headers[':status'], 200); + req.on('response', common.mustCall((_headers, _flags, rawHeaders) => { + assert.deepStrictEqual(rawHeaders, [ + ':status', '404', + 'x', '1', + 'x-foo', 'bar', // Lowercased as required for HTTP/2 + 'x', '2', // Duplicate header order preserved + 'date', '0000', // Server doesn't automatically set its own value + ]); client.close(); server.close(); })); diff --git a/test/parallel/test-https-foafssl.js b/test/parallel/test-https-foafssl.js index df375e7d22201e..ec009c6a768185 100644 --- a/test/parallel/test-https-foafssl.js +++ b/test/parallel/test-https-foafssl.js @@ -47,7 +47,6 @@ const webIdUrl = 'URI:http://example.com/#me'; const modulus = fixtures.readKey('rsa_cert_foafssl_b.modulus', 'ascii').replace(/\n/g, ''); const exponent = fixtures.readKey('rsa_cert_foafssl_b.exponent', 'ascii').replace(/\n/g, ''); -const CRLF = '\r\n'; const body = 'hello world\n'; let cert; @@ -76,7 +75,7 @@ server.listen(0, function() { client.stdout.on('data', function(data) { console.log('response received'); const message = data.toString(); - const contents = message.split(CRLF + CRLF).pop(); + const contents = message.split('\r\n\r\n').pop(); assert.strictEqual(body, contents); server.close((e) => { assert.ifError(e); diff --git a/test/parallel/test-inspector-debug-async-hook.js b/test/parallel/test-inspector-debug-async-hook.js new file mode 100644 index 00000000000000..e8f13b40c7e43a --- /dev/null +++ b/test/parallel/test-inspector-debug-async-hook.js @@ -0,0 +1,31 @@ +'use strict'; +const common = require('../common'); +common.skipIfInspectorDisabled(); +const test = require('node:test'); +const { NodeInstance } = require('../common/inspector-helper'); + +const script = ` +import { createHook } from "async_hooks" +import fs from "fs" + +const hook = createHook({ + after() { + } +}); +hook.enable(true); +console.log('Async hook enabled'); +`; + +test('inspector async hooks should not crash in debug build', async () => { + const instance = new NodeInstance([ + '--inspect-brk=0', + ], script); + const session = await instance.connectInspectorSession(); + await session.send({ method: 'NodeRuntime.enable' }); + await session.waitForNotification('NodeRuntime.waitingForDebugger'); + await session.send({ method: 'Runtime.enable' }); + await session.send({ method: 'Debugger.enable' }); + await session.send({ id: 6, method: 'Debugger.setAsyncCallStackDepth', params: { maxDepth: 32 } }); + await session.send({ method: 'Runtime.runIfWaitingForDebugger' }); + await session.waitForDisconnect(); +}); diff --git a/test/parallel/test-inspector-emit-protocol-event.js b/test/parallel/test-inspector-emit-protocol-event.js index 5a90b258cd6e1a..3125287c3a53d5 100644 --- a/test/parallel/test-inspector-emit-protocol-event.js +++ b/test/parallel/test-inspector-emit-protocol-event.js @@ -27,6 +27,7 @@ const EXPECTED_EVENTS = { url: 'https://nodejs.org/en', method: 'GET', headers: {}, + hasPostData: false, }, timestamp: 1000, wallTime: 1000, @@ -87,6 +88,34 @@ const EXPECTED_EVENTS = { errorText: 'Failed to load resource' } }, + { + name: 'webSocketCreated', + params: { + requestId: 'websocket-id-1', + url: 'ws://example.com:8080', + } + + }, + { + name: 'webSocketHandshakeResponseReceived', + params: { + requestId: 'websocket-id-1', + response: { + status: 101, + statusText: 'Switching Protocols', + headers: {}, + }, + timestamp: 1000, + } + }, + { + name: 'webSocketClosed', + params: { + requestId: 'websocket-id-1', + timestamp: 1000, + + } + }, ] }; @@ -123,7 +152,7 @@ const runAsyncTest = async () => { continue; } session.on(`${domain}.${event.name}`, common.mustCall(({ params }) => { - if (event.name === 'requestWillBeSent') { + if (event.name === 'requestWillBeSent' || event.name === 'webSocketCreated') { // Initiator is automatically captured and contains caller info. // No need to validate it. delete params.initiator; diff --git a/test/parallel/test-inspector-network-websocket.js b/test/parallel/test-inspector-network-websocket.js new file mode 100644 index 00000000000000..6f2613a4c27de2 --- /dev/null +++ b/test/parallel/test-inspector-network-websocket.js @@ -0,0 +1,63 @@ +// Flags: --inspect=0 --experimental-network-inspection +'use strict'; +const common = require('../common'); +if (!common.hasCrypto) + common.skip('missing crypto'); +common.skipIfInspectorDisabled(); + +const assert = require('node:assert'); +const { once } = require('node:events'); +const WebSocketServer = require('../common/websocket-server'); +const inspector = require('node:inspector/promises'); +const dc = require('diagnostics_channel'); + +const session = new inspector.Session(); +session.connect(); + +dc.channel('undici:websocket:socket_error').subscribe((message) => { + console.error('WebSocket error:', message); +}); + +function findFrameInInitiator(scriptName, initiator) { + const frame = initiator.stack.callFrames.find((it) => { + return it.url === scriptName; + }); + return frame; +} + +async function test() { + await session.post('Network.enable'); + const server = new WebSocketServer({ + responseError: true, + }); + await server.start(); + const url = `ws://127.0.0.1:${server.port}/`; + let requestId; + once(session, 'Network.webSocketCreated').then(common.mustCall(([message]) => { + assert.strictEqual(message.method, 'Network.webSocketCreated'); + assert.strictEqual(message.params.url, url); + assert.ok(message.params.requestId); + assert.strictEqual(typeof message.params.initiator, 'object'); + assert.strictEqual(message.params.initiator.type, 'script'); + assert.ok(findFrameInInitiator('node:internal/deps/undici/undici', message.params.initiator)); + requestId = message.params.requestId; + })); + + once(session, 'Network.webSocketHandshakeResponseReceived').then(common.mustCall(([message]) => { + assert.strictEqual(message.params.requestId, requestId); + assert.strictEqual(message.params.response.status, 101); + assert.strictEqual(message.params.response.statusText, 'Switching Protocols'); + assert.strictEqual(typeof message.params.timestamp, 'number'); + socket.close(); + })); + + const socket = new WebSocket(url); + once(session, 'Network.webSocketClosed').then(common.mustCall(([message]) => { + assert.strictEqual(message.method, 'Network.webSocketClosed'); + assert.strictEqual(message.params.requestId, requestId); + session.disconnect(); + server.server.close(); + })); +} + +test().then(common.mustCall()); diff --git a/test/parallel/test-max-old-space-size-percentage.js b/test/parallel/test-max-old-space-size-percentage.js index 511a10109f9d2b..e55c426d7ee1a8 100644 --- a/test/parallel/test-max-old-space-size-percentage.js +++ b/test/parallel/test-max-old-space-size-percentage.js @@ -119,14 +119,19 @@ assert( // Validate heap sizes against system memory const totalMemoryMB = Math.floor(os.totalmem() / 1024 / 1024); -const margin = 10; // 5% margin +const uint64Max = 2 ** 64 - 1; +const constrainedMemory = process.constrainedMemory(); +const constrainedMemoryMB = Math.floor(constrainedMemory / 1024 / 1024); +const effectiveMemoryMB = + constrainedMemory > 0 && constrainedMemory !== uint64Max ? constrainedMemoryMB : totalMemoryMB; +const margin = 10; // 10% margin testPercentages.forEach((percentage) => { - const upperLimit = totalMemoryMB * ((percentage + margin) / 100); + const upperLimit = effectiveMemoryMB * ((percentage + margin) / 100); assert( heapSizes[percentage] <= upperLimit, `Heap size for ${percentage}% (${heapSizes[percentage]} MB) should not exceed upper limit (${upperLimit} MB)` ); - const lowerLimit = totalMemoryMB * ((percentage - margin) / 100); + const lowerLimit = effectiveMemoryMB * ((percentage - margin) / 100); assert( heapSizes[percentage] >= lowerLimit, `Heap size for ${percentage}% (${heapSizes[percentage]} MB) should not be less than lower limit (${lowerLimit} MB)` diff --git a/test/parallel/test-parse-test-envs.js b/test/parallel/test-parse-test-envs.js new file mode 100644 index 00000000000000..501db4b758de00 --- /dev/null +++ b/test/parallel/test-parse-test-envs.js @@ -0,0 +1,26 @@ +'use strict'; + +// Env: A_SET_ENV_VAR=A_SET_ENV_VAR_VALUE B_SET_ENV_VAR=B_SET_ENV_VAR_VALUE +// Flags: --test-isolation=none --expose-internals + +require('../common'); +const assert = require('node:assert'); +const { describe, it } = require('node:test'); + + +// This test verifies that a test file that requires 'common' can set environment variables +// via comments in the test file, similar to how we set flags via comments. +// Ref: https://github.com/nodejs/node/issues/58179 +describe('env var via comment', () => { + it('should set env var A_SET_ENV_VAR', () => { + assert.strictEqual(process.env.A_SET_ENV_VAR, 'A_SET_ENV_VAR_VALUE'); + }); + it('should set env var B_SET_ENV_VAR', () => { + assert.strictEqual(process.env.B_SET_ENV_VAR, 'B_SET_ENV_VAR_VALUE'); + }); + + it('should interop with flags', () => { + const flag = require('internal/options').getOptionValue('--test-isolation'); + assert.strictEqual(flag, 'none'); + }); +}); diff --git a/test/parallel/test-parse-test-only-envs.js b/test/parallel/test-parse-test-only-envs.js new file mode 100644 index 00000000000000..0eafec719325db --- /dev/null +++ b/test/parallel/test-parse-test-only-envs.js @@ -0,0 +1,20 @@ +'use strict'; + +// Env: A_SET_ENV_VAR=A_SET_ENV_VAR_VALUE B_SET_ENV_VAR=B_SET_ENV_VAR_VALUE + +require('../common'); +const assert = require('node:assert'); +const { describe, it } = require('node:test'); + + +// This test verifies that a test file that requires 'common' can set environment variables +// via comments in the test file, similar to how we set flags via comments. +// Ref: https://github.com/nodejs/node/issues/58179 +describe('env var via comment', () => { + it('should set env var A_SET_ENV_VAR', () => { + assert.strictEqual(process.env.A_SET_ENV_VAR, 'A_SET_ENV_VAR_VALUE'); + }); + it('should set env var B_SET_ENV_VAR', () => { + assert.strictEqual(process.env.B_SET_ENV_VAR, 'B_SET_ENV_VAR_VALUE'); + }); +}); diff --git a/test/parallel/test-repl-tab-complete-import.js b/test/parallel/test-repl-tab-complete-import.js index f4ef408c89174c..3ce48ac45b1f1e 100644 --- a/test/parallel/test-repl-tab-complete-import.js +++ b/test/parallel/test-repl-tab-complete-import.js @@ -32,8 +32,7 @@ const testMe = repl.start({ testMe._domain.on('error', assert.ifError); // Tab complete provides built in libs for import() -testMe.complete('import(\'', common.mustCall((error, data) => { - assert.strictEqual(error, null); +testMe.complete('import(\'', common.mustSucceed((data) => { publicUnprefixedModules.forEach((lib) => { assert( data[0].includes(lib) && data[0].includes(`node:${lib}`), @@ -43,15 +42,14 @@ testMe.complete('import(\'', common.mustCall((error, data) => { const newModule = 'foobar'; assert(!builtinModules.includes(newModule)); repl.builtinModules.push(newModule); - testMe.complete('import(\'', common.mustCall((_, [modules]) => { + testMe.complete('import(\'', common.mustSucceed(([modules]) => { assert.strictEqual(data[0].length + 1, modules.length); assert(modules.includes(newModule) && !modules.includes(`node:${newModule}`)); })); })); -testMe.complete("import\t( 'n", common.mustCall((error, data) => { - assert.strictEqual(error, null); +testMe.complete("import\t( 'n", common.mustSucceed((data) => { assert.strictEqual(data.length, 2); assert.strictEqual(data[1], 'n'); const completions = data[0]; @@ -77,16 +75,14 @@ testMe.complete("import\t( 'n", common.mustCall((error, data) => { // Import calls should handle all types of quotation marks. for (const quotationMark of ["'", '"', '`']) { putIn.run(['.clear']); - testMe.complete('import(`@nodejs', common.mustCall((err, data) => { - assert.strictEqual(err, null); + testMe.complete('import(`@nodejs', common.mustSucceed((data) => { assert.deepStrictEqual(data, [expected, '@nodejs']); })); putIn.run(['.clear']); // Completions should not be greedy in case the quotation ends. const input = `import(${quotationMark}@nodejsscope${quotationMark}`; - testMe.complete(input, common.mustCall((err, data) => { - assert.strictEqual(err, null); + testMe.complete(input, common.mustSucceed((data) => { assert.deepStrictEqual(data, [[], undefined]); })); } @@ -96,8 +92,7 @@ testMe.complete("import\t( 'n", common.mustCall((error, data) => { putIn.run(['.clear']); // Completions should find modules and handle whitespace after the opening // bracket. - testMe.complete('import \t("no_ind', common.mustCall((err, data) => { - assert.strictEqual(err, null); + testMe.complete('import \t("no_ind', common.mustSucceed((data) => { assert.deepStrictEqual(data, [['no_index', 'no_index/'], 'no_ind']); })); } @@ -110,8 +105,7 @@ testMe.complete("import\t( 'n", common.mustCall((error, data) => { process.chdir(__dirname); ['import(\'.', 'import(".'].forEach((input) => { - testMe.complete(input, common.mustCall((err, data) => { - assert.strictEqual(err, null); + testMe.complete(input, common.mustSucceed((data) => { assert.strictEqual(data.length, 2); assert.strictEqual(data[1], '.'); assert.strictEqual(data[0].length, 2); @@ -121,16 +115,14 @@ testMe.complete("import\t( 'n", common.mustCall((error, data) => { }); ['import(\'..', 'import("..'].forEach((input) => { - testMe.complete(input, common.mustCall((err, data) => { - assert.strictEqual(err, null); + testMe.complete(input, common.mustSucceed((data) => { assert.deepStrictEqual(data, [['../'], '..']); })); }); ['./', './test-'].forEach((path) => { [`import('${path}`, `import("${path}`].forEach((input) => { - testMe.complete(input, common.mustCall((err, data) => { - assert.strictEqual(err, null); + testMe.complete(input, common.mustSucceed((data) => { assert.strictEqual(data.length, 2); assert.strictEqual(data[1], path); assert.ok(data[0].includes('./test-repl-tab-complete.js')); @@ -140,8 +132,7 @@ testMe.complete("import\t( 'n", common.mustCall((error, data) => { ['../parallel/', '../parallel/test-'].forEach((path) => { [`import('${path}`, `import("${path}`].forEach((input) => { - testMe.complete(input, common.mustCall((err, data) => { - assert.strictEqual(err, null); + testMe.complete(input, common.mustSucceed((data) => { assert.strictEqual(data.length, 2); assert.strictEqual(data[1], path); assert.ok(data[0].includes('../parallel/test-repl-tab-complete.js')); diff --git a/test/parallel/test-runner-flag-propagation.js b/test/parallel/test-runner-flag-propagation.js new file mode 100644 index 00000000000000..d5190251ef8db7 --- /dev/null +++ b/test/parallel/test-runner-flag-propagation.js @@ -0,0 +1,128 @@ +'use strict'; + +require('../common'); +const fixtures = require('../common/fixtures.js'); +const tmpdir = require('../common/tmpdir'); +const assert = require('node:assert'); +const fs = require('node:fs'); +const { spawnSync } = require('node:child_process'); +const { describe, it } = require('node:test'); +const path = require('node:path'); + +const fixtureDir = fixtures.path('test-runner', 'flag-propagation'); +const runner = path.join(fixtureDir, 'runner.mjs'); + +describe('test runner flag propagation', () => { + describe('via command line', () => { + const flagPropagationTests = [ + ['--experimental-config-file', 'node.config.json', ''], + ['--experimental-default-config-file', '', false], + ['--env-file', '.env', '.env'], + ['--env-file-if-exists', '.env', '.env'], + ['--test-concurrency', '2', '2'], + ['--test-timeout', '5000', '5000'], + ['--test-coverage-branches', '100', '100'], + ['--test-coverage-functions', '100', '100'], + ['--test-coverage-lines', '100', '100'], + ['--experimental-test-coverage', '', false], + ['--test-coverage-exclude', 'test/**', 'test/**'], + ['--test-coverage-include', 'src/**', 'src/**'], + ['--test-update-snapshots', '', true], + ['--import', './index.js', './index.js'], + ['--require', './index.js', './index.js'], + ]; + + for (const [flagName, testValue, expectedValue] of flagPropagationTests) { + const testDescription = `should propagate ${flagName} to child tests as expected`; + + it(testDescription, () => { + const args = [ + '--test-reporter=tap', + '--no-warnings', + '--expose-internals', + // We need to pass the flag that will be propagated to the child test + testValue ? `${flagName}=${testValue}` : flagName, + // Use the runner fixture + runner, + // Pass parameters to the fixture + `--flag=${flagName}`, + `--expected=${expectedValue}`, + `--description="${testDescription}"`, + ].filter(Boolean); + + const child = spawnSync( + process.execPath, + args, + { + cwd: fixtureDir, + }, + ); + + assert.strictEqual(child.status, 0, `Flag propagation test failed for ${flagName}.`); + const stdout = child.stdout.toString(); + assert.match(stdout, /tests 1/, `Test should execute for ${flagName}`); + assert.match(stdout, /pass 1/, `Test should pass for ${flagName} propagation check`); + }); + } + }); + + describe('via config file', () => { + const configFilePropagationTests = [ + ['--test-concurrency', 2, 2, 'testRunner'], + ['--test-timeout', 5000, 5000, 'testRunner'], + ['--test-coverage-branches', 100, 100, 'testRunner'], + ['--test-coverage-functions', 100, 100, 'testRunner'], + ['--test-coverage-lines', 100, 100, 'testRunner'], + ['--experimental-test-coverage', true, false, 'testRunner'], + ['--test-coverage-exclude', 'test/**', 'test/**', 'testRunner'], + ['--test-coverage-include', 'src/**', 'src/**', 'testRunner'], + ['--test-update-snapshots', true, true, 'testRunner'], + ['--test-concurrency', 3, 3, 'testRunner'], + ['--test-timeout', 2500, 2500, 'testRunner'], + ['--test-coverage-branches', 90, 90, 'testRunner'], + ['--test-coverage-functions', 85, 85, 'testRunner'], + ]; + + for (const [flagName, configValue, expectedValue, namespace] of configFilePropagationTests) { + const testDescription = `should propagate ${flagName} from config file (${namespace}) to child tests`; + + it(testDescription, () => { + tmpdir.refresh(); + + // Create a temporary config file + const configFile = path.join(tmpdir.path, 'test-config.json'); + const configContent = { + [namespace]: { + [flagName.replace('--', '')]: configValue + } + }; + + fs.writeFileSync(configFile, JSON.stringify(configContent, null, 2)); + + const args = [ + '--test-reporter=tap', + '--no-warnings', + '--expose-internals', + `--experimental-config-file=${configFile}`, + runner, + `--flag=${flagName}`, + `--expected=${expectedValue}`, + `--description="${testDescription}"`, + ]; + + const child = spawnSync( + process.execPath, + args, + { + cwd: fixtureDir, + }, + ); + + assert.strictEqual(child.status, 0, `Config file propagation test failed for ${flagName}.`); + const stdout = child.stdout.toString(); + assert.match(stdout, /tests 1/, `Test should execute for config file ${flagName}`); + assert.match(stdout, /pass 1/, `Test should pass for config file ${flagName} propagation check`); + }); + } + }); +}); diff --git a/test/parallel/test-runner-output.mjs b/test/parallel/test-runner-output.mjs index ccfca45e502fd5..7169c28e293dbe 100644 --- a/test/parallel/test-runner-output.mjs +++ b/test/parallel/test-runner-output.mjs @@ -6,13 +6,13 @@ import { describe, it } from 'node:test'; import { hostname } from 'node:os'; import { chdir, cwd } from 'node:process'; import { fileURLToPath } from 'node:url'; -import internalTTy from 'internal/tty'; const skipForceColors = process.config.variables.icu_gyp_path !== 'tools/icu/icu-generic.gyp' || process.config.variables.node_shared_openssl; -const canColorize = internalTTy.getColorDepth() > 2; +// We're using dynamic import here to not break `NODE_REGENERATE_SNAPSHOTS`. +const canColorize = (await import('internal/tty')).default.getColorDepth() > 2; const skipCoverageColors = !canColorize; function replaceTestDuration(str) { diff --git a/test/parallel/test-runner-reporters.js b/test/parallel/test-runner-reporters.js index b557cef1b9bef8..50a47578a1da7e 100644 --- a/test/parallel/test-runner-reporters.js +++ b/test/parallel/test-runner-reporters.js @@ -191,4 +191,17 @@ describe('node:test reporters', { concurrency: true }, () => { assert.match(fileConent, /ℹ skipped 0/); assert.match(fileConent, /ℹ todo 0/); }); + + it('should correctly report pass/fail for junit reporter using reporters.js', async () => { + const file = tmpdir.resolve(`${tmpFiles++}.xml`); + const child = spawnSync(process.execPath, + ['--test', '--test-reporter', 'junit', '--test-reporter-destination', file, testFile]); + assert.strictEqual(child.stderr.toString(), ''); + assert.strictEqual(child.stdout.toString(), ''); + const fileContents = fs.readFileSync(file, 'utf8'); + assert.match(fileContents, //); + assert.match(fileContents, /\s*/); + assert.match(fileContents, //); + assert.match(fileContents, //); + }); }); diff --git a/test/parallel/test-runner-test-fullname.js b/test/parallel/test-runner-test-fullname.js index 40e8abccd36b24..e3aad0822bf258 100644 --- a/test/parallel/test-runner-test-fullname.js +++ b/test/parallel/test-runner-test-fullname.js @@ -1,7 +1,11 @@ 'use strict'; require('../common'); const { strictEqual } = require('node:assert'); -const { suite, test } = require('node:test'); +const { before, suite, test } = require('node:test'); + +before((t) => { + strictEqual(t.fullName, ''); +}); suite('suite', (t) => { strictEqual(t.fullName, 'suite'); diff --git a/test/parallel/test-runner-test-rerun-failures.js b/test/parallel/test-runner-test-rerun-failures.js new file mode 100644 index 00000000000000..482af5172de4ec --- /dev/null +++ b/test/parallel/test-runner-test-rerun-failures.js @@ -0,0 +1,117 @@ +'use strict'; +const common = require('../common'); + +const fixtures = require('../common/fixtures'); +const assert = require('node:assert'); +const { rm, readFile } = require('node:fs/promises'); +const { setTimeout } = require('node:timers/promises'); +const { test, beforeEach, afterEach, run } = require('node:test'); + +const fixture = fixtures.path('test-runner', 'rerun.js'); +const stateFile = fixtures.path('test-runner', 'rerun-state.json'); + +beforeEach(() => rm(stateFile, { force: true })); +afterEach(() => rm(stateFile, { force: true })); + +const expectedStateFile = [ + { + 'test/fixtures/test-runner/rerun.js:17:3': { passed_on_attempt: 0, name: 'ambiguous (expectedAttempts=0)' }, + 'test/fixtures/test-runner/rerun.js:9:1': { passed_on_attempt: 0, name: 'ok' }, + }, + { + 'test/fixtures/test-runner/rerun.js:17:3': { passed_on_attempt: 0, name: 'ambiguous (expectedAttempts=0)' }, + 'test/fixtures/test-runner/rerun.js:17:3:(1)': { passed_on_attempt: 1, name: 'ambiguous (expectedAttempts=1)' }, + 'test/fixtures/test-runner/rerun.js:9:1': { passed_on_attempt: 0, name: 'ok' }, + }, + { + 'test/fixtures/test-runner/rerun.js:17:3': { passed_on_attempt: 0, name: 'ambiguous (expectedAttempts=0)' }, + 'test/fixtures/test-runner/rerun.js:17:3:(1)': { passed_on_attempt: 1, name: 'ambiguous (expectedAttempts=1)' }, + 'test/fixtures/test-runner/rerun.js:9:1': { passed_on_attempt: 0, name: 'ok' }, + 'test/fixtures/test-runner/rerun.js:3:1': { passed_on_attempt: 2, name: 'should fail on first two attempts' }, + }, +]; + +const getStateFile = async () => JSON.parse((await readFile(stateFile, 'utf8')).replaceAll('\\\\', '/')); + +test('test should pass on third rerun', async () => { + const args = ['--test-rerun-failures', stateFile, fixture]; + + let { code, stdout, signal } = await common.spawnPromisified(process.execPath, args); + assert.strictEqual(code, 1); + assert.strictEqual(signal, null); + assert.match(stdout, /pass 2/); + assert.match(stdout, /fail 2/); + assert.deepStrictEqual(await getStateFile(), expectedStateFile.slice(0, 1)); + + ({ code, stdout, signal } = await common.spawnPromisified(process.execPath, args)); + assert.strictEqual(code, 1); + assert.strictEqual(signal, null); + assert.match(stdout, /pass 3/); + assert.match(stdout, /fail 1/); + assert.deepStrictEqual(await getStateFile(), expectedStateFile.slice(0, 2)); + + + ({ code, stdout, signal } = await common.spawnPromisified(process.execPath, args)); + assert.strictEqual(code, 0); + assert.strictEqual(signal, null); + assert.match(stdout, /pass 4/); + assert.match(stdout, /fail 0/); + assert.deepStrictEqual(await getStateFile(), expectedStateFile); +}); + +test('test should pass on third rerun with `--test`', async () => { + const args = ['--test', '--test-rerun-failures', stateFile, fixture]; + + let { code, stdout, signal } = await common.spawnPromisified(process.execPath, args); + assert.strictEqual(code, 1); + assert.strictEqual(signal, null); + assert.match(stdout, /pass 2/); + assert.match(stdout, /fail 2/); + assert.deepStrictEqual(await getStateFile(), expectedStateFile.slice(0, 1)); + + ({ code, stdout, signal } = await common.spawnPromisified(process.execPath, args)); + assert.strictEqual(code, 1); + assert.strictEqual(signal, null); + assert.match(stdout, /pass 3/); + assert.match(stdout, /fail 1/); + assert.deepStrictEqual(await getStateFile(), expectedStateFile.slice(0, 2)); + + + ({ code, stdout, signal } = await common.spawnPromisified(process.execPath, args)); + assert.strictEqual(code, 0); + assert.strictEqual(signal, null); + assert.match(stdout, /pass 4/); + assert.match(stdout, /fail 0/); + assert.deepStrictEqual(await getStateFile(), expectedStateFile); +}); + +test('using `run` api', async () => { + let stream = run({ files: [fixture], rerunFailuresFilePath: stateFile }); + stream.on('test:pass', common.mustCall(2)); + stream.on('test:fail', common.mustCall(2)); + + // eslint-disable-next-line no-unused-vars + for await (const _ of stream); + await setTimeout(common.platformTimeout(10)); // Wait for the stream to finish processing + assert.deepStrictEqual(await getStateFile(), expectedStateFile.slice(0, 1)); + + + stream = run({ files: [fixture], rerunFailuresFilePath: stateFile }); + stream.on('test:pass', common.mustCall(3)); + stream.on('test:fail', common.mustCall(1)); + + // eslint-disable-next-line no-unused-vars + for await (const _ of stream); + await setTimeout(common.platformTimeout(10)); // Wait for the stream to finish processing + assert.deepStrictEqual(await getStateFile(), expectedStateFile.slice(0, 2)); + + + stream = run({ files: [fixture], rerunFailuresFilePath: stateFile }); + stream.on('test:pass', common.mustCall(4)); + stream.on('test:fail', common.mustNotCall()); + + // eslint-disable-next-line no-unused-vars + for await (const _ of stream); + await setTimeout(common.platformTimeout(10)); // Wait for the stream to finish processing + assert.deepStrictEqual(await getStateFile(), expectedStateFile); +}); diff --git a/test/parallel/test-setproctitle.js b/test/parallel/test-setproctitle.js index b08302e0a35ac0..368bc85800a9a9 100644 --- a/test/parallel/test-setproctitle.js +++ b/test/parallel/test-setproctitle.js @@ -4,7 +4,7 @@ const common = require('../common'); const { isMainThread } = require('worker_threads'); // FIXME add sunos support -if (common.isSunOS || common.isIBMi) { +if (common.isSunOS || common.isIBMi || common.isWindows) { common.skip(`Unsupported platform [${process.platform}]`); } @@ -25,15 +25,10 @@ assert.notStrictEqual(process.title, title); process.title = title; assert.strictEqual(process.title, title); -// Test setting the title but do not try to run `ps` on Windows. -if (common.isWindows) { - common.skip('Windows does not have "ps" utility'); -} - try { execSync('command -v ps'); } catch (err) { - if (err.status === 1) { + if (err.status === 1 || err.status === 127) { common.skip('The "ps" utility is not available'); } throw err; @@ -53,5 +48,5 @@ exec(cmd, common.mustSucceed((stdout, stderr) => { title += ` (${path.basename(process.execPath)})`; // Omitting trailing whitespace and \n - assert.strictEqual(stdout.replace(/\s+$/, '').endsWith(title), true); + assert.ok(stdout.trimEnd().endsWith(title)); })); diff --git a/test/parallel/test-sqlite-database-sync.js b/test/parallel/test-sqlite-database-sync.js index 5b34dec4cc220f..d778f839098737 100644 --- a/test/parallel/test-sqlite-database-sync.js +++ b/test/parallel/test-sqlite-database-sync.js @@ -281,6 +281,15 @@ suite('DatabaseSync() constructor', () => { { changes: 1, lastInsertRowid: 1 }, ); }); + + test('has sqlite-type symbol property', (t) => { + const dbPath = nextDb(); + const db = new DatabaseSync(dbPath); + t.after(() => { db.close(); }); + + const sqliteTypeSymbol = Symbol.for('sqlite-type'); + t.assert.strictEqual(db[sqliteTypeSymbol], 'node:sqlite'); + }); }); suite('DatabaseSync.prototype.open()', () => { diff --git a/test/parallel/test-sqlite-statement-sync.js b/test/parallel/test-sqlite-statement-sync.js index 858a1486601763..04494a02c692a8 100644 --- a/test/parallel/test-sqlite-statement-sync.js +++ b/test/parallel/test-sqlite-statement-sync.js @@ -240,6 +240,36 @@ suite('StatementSync.prototype.run()', () => { stmt.run({ k: 3, v: 30 }), { changes: 1, lastInsertRowid: 3 } ); }); + + test('SQLite defaults unbound ?NNN parameters', (t) => { + const db = new DatabaseSync(nextDb()); + t.after(() => { db.close(); }); + const setup = db.exec( + 'CREATE TABLE data(key INTEGER PRIMARY KEY, val INTEGER NOT NULL) STRICT;' + ); + t.assert.strictEqual(setup, undefined); + const stmt = db.prepare('INSERT INTO data (key, val) VALUES (?1, ?3)'); + + t.assert.throws(() => { + stmt.run(1); + }, { + code: 'ERR_SQLITE_ERROR', + message: 'NOT NULL constraint failed: data.val', + errcode: 1299, + errstr: 'constraint failed', + }); + }); + + test('binds ?NNN params by position', (t) => { + const db = new DatabaseSync(nextDb()); + t.after(() => { db.close(); }); + const setup = db.exec( + 'CREATE TABLE data(key INTEGER PRIMARY KEY, val INTEGER NOT NULL) STRICT;' + ); + t.assert.strictEqual(setup, undefined); + const stmt = db.prepare('INSERT INTO data (key, val) VALUES (?1, ?2)'); + t.assert.deepStrictEqual(stmt.run(1, 2), { changes: 1, lastInsertRowid: 1 }); + }); }); suite('StatementSync.prototype.sourceSQL', () => { diff --git a/test/parallel/test-util-inspect.js b/test/parallel/test-util-inspect.js index b06f6814e4985a..5aafb4378c18e1 100644 --- a/test/parallel/test-util-inspect.js +++ b/test/parallel/test-util-inspect.js @@ -2837,7 +2837,7 @@ assert.strictEqual( // Use a fake stack to verify the expected colored outcome. const stack = [ 'Error: CWD is grayed out, even cwd that are percent encoded!', - ' at A. (/test/node_modules/foo/node_modules/bar/baz.js:2:7)', + ' at A. (/test/node_modules/foo/node_modules/@namespace/bar/baz.js:2:7)', ' at Module._compile (node:internal/modules/cjs/loader:827:30)', ' at Fancy (node:vm:697:32)', // This file is not an actual Node.js core file. @@ -2862,7 +2862,7 @@ assert.strictEqual( } const escapedCWD = util.inspect(process.cwd()).slice(1, -1); util.inspect(err, { colors: true }).split('\n').forEach((line, i) => { - let expected = stack[i].replace(/node_modules\/([^/]+)/gi, (_, m) => { + let expected = stack[i].replace(/node_modules\/(@[^/]+\/[^/]+|[^/]+)/gi, (_, m) => { return `node_modules/\u001b[4m${m}\u001b[24m`; }).replaceAll(new RegExp(`(\\(?${escapedCWD}(\\\\|/))`, 'gi'), (_, m) => { return `\x1B[90m${m}\x1B[39m`; diff --git a/test/parallel/test-vm-module-link-shared-deps.js b/test/parallel/test-vm-module-link-shared-deps.js new file mode 100644 index 00000000000000..4d4bae253a426d --- /dev/null +++ b/test/parallel/test-vm-module-link-shared-deps.js @@ -0,0 +1,82 @@ +// Flags: --experimental-vm-modules + +'use strict'; + +const common = require('../common'); +const vm = require('vm'); +const assert = require('assert'); + +// This test verifies that a module can be returned multiple +// times in the linker function in `module.link(linker)`. +// `module.link(linker)` should handle the race condition of +// `module.status` when the linker function is asynchronous. + +// Regression of https://github.com/nodejs/node/issues/59480 + +const sources = { + './index.js': ` + import foo from "./foo.js"; + import shared from "./shared.js"; + export default { + foo, + shared + }; + `, + './foo.js': ` + import shared from "./shared.js"; + export default { + name: "foo" + }; + `, + './shared.js': ` + export default { + name: "shared", + }; + `, +}; + +const moduleCache = new Map(); + +function getModuleInstance(identifier) { + let module = moduleCache.get(identifier); + + if (!module) { + module = new vm.SourceTextModule(sources[identifier], { + identifier, + }); + moduleCache.set(identifier, module); + } + + return module; +} + +async function esmImport(identifier) { + const module = getModuleInstance(identifier); + const requests = []; + + await module.link(async (specifier, referrer) => { + requests.push([specifier, referrer.identifier]); + // Use `Promise.resolve` to defer a tick to create a race condition on + // `module.status` when a module is being imported several times. + return Promise.resolve(getModuleInstance(specifier)); + }); + + await module.evaluate(); + return [module.namespace, requests]; +} + +async function test() { + const { 0: mod, 1: requests } = await esmImport('./index.js'); + assert.strictEqual(mod.default.foo.name, 'foo'); + assert.strictEqual(mod.default.shared.name, 'shared'); + + // Assert that there is no duplicated requests. + assert.deepStrictEqual(requests, [ + // [specifier, referrer] + ['./foo.js', './index.js'], + ['./shared.js', './index.js'], + ['./shared.js', './foo.js'], + ]); +} + +test().then(common.mustCall()); diff --git a/test/parallel/test-webcrypto-constructors.js b/test/parallel/test-webcrypto-constructors.js index 4a60c9325e24a1..782265edc29486 100644 --- a/test/parallel/test-webcrypto-constructors.js +++ b/test/parallel/test-webcrypto-constructors.js @@ -137,6 +137,20 @@ const notSubtle = Reflect.construct(function() {}, [], SubtleCrypto); }).then(common.mustCall()); } +// Test SubtleCrypto.supports +{ + assert.throws(() => SubtleCrypto.supports.call(undefined), { + name: 'TypeError', code: 'ERR_INVALID_THIS', + }); +} + +// Test SubtleCrypto.prototype.getPublicKey +{ + assert.rejects(() => notSubtle.getPublicKey(), { + name: 'TypeError', code: 'ERR_INVALID_THIS', + }).then(common.mustCall()); +} + { subtle.importKey( 'raw', diff --git a/test/parallel/test-webcrypto-derivebits-cfrg.js b/test/parallel/test-webcrypto-derivebits-cfrg.js index d45e59fd5087b9..757c8127253662 100644 --- a/test/parallel/test-webcrypto-derivebits-cfrg.js +++ b/test/parallel/test-webcrypto-derivebits-cfrg.js @@ -18,19 +18,26 @@ const kTests = [ '64ea51fae5b3307cfe9706', result: '2768409dfab99ec23b8c89b93ff5880295f76176088f89e43dfebe7ea1950008' }, - { - name: 'X448', - size: 56, - pkcs8: '3046020100300506032b656f043a043858c7d29a3eb519b29d00cfb191bb64fc6' + +]; + +if (!process.features.openssl_is_boringssl) { + kTests.push( + { + name: 'X448', + size: 56, + pkcs8: '3046020100300506032b656f043a043858c7d29a3eb519b29d00cfb191bb64fc6' + 'd8a42d8f17176272b89f2272d1819295c6525c0829671b052ef0727530f188e31' + 'd0cc53bf26929e', - spki: '3042300506032b656f033900b604a1d1a5cd1d9426d561ef630a9eb16cbe69d5b9' + + spki: '3042300506032b656f033900b604a1d1a5cd1d9426d561ef630a9eb16cbe69d5b9' + 'ca615edc53633efb52ea31e6e6a0a1dbacc6e76cbce6482d7e4ba3d55d9e802765' + 'ce6f', - result: 'f0f6c5f17f94f4291eab7178866d37ec8906dd6c514143dc85be7cf28deff39b' + + result: 'f0f6c5f17f94f4291eab7178866d37ec8906dd6c514143dc85be7cf28deff39b' + '726e0f6dcf810eb594dca97b4882bd44c43ea7dc67f49a4e', - }, -]; + } + ); +} else { + common.printSkipMessage('Skipping unsupported X448 test case'); +} async function prepareKeys() { const keys = {}; @@ -153,9 +160,9 @@ async function prepareKeys() { // Missing public property await assert.rejects( subtle.deriveBits( - { name: 'X448' }, - keys.X448.privateKey, - 8 * keys.X448.size), + { name: 'X25519' }, + keys.X25519.privateKey, + 8 * keys.X25519.size), { code: 'ERR_MISSING_OPTION' }); } @@ -164,15 +171,15 @@ async function prepareKeys() { await assert.rejects( subtle.deriveBits( { - name: 'X448', + name: 'X25519', public: { message: 'Not a CryptoKey' } }, - keys.X448.privateKey, - 8 * keys.X448.size), + keys.X25519.privateKey, + 8 * keys.X25519.size), { code: 'ERR_INVALID_ARG_TYPE' }); } - { + if (keys.X25519 && keys.X448) { // Mismatched types await assert.rejects( subtle.deriveBits( @@ -182,15 +189,15 @@ async function prepareKeys() { }, keys.X448.privateKey, 8 * keys.X448.size), - { message: 'algorithm.public must be an X448 key' }); + { message: 'key algorithm mismatch' }); } { // Base key is not a private key await assert.rejects(subtle.deriveBits({ - name: 'X448', - public: keys.X448.publicKey - }, keys.X448.publicKey, null), { + name: 'X25519', + public: keys.X25519.publicKey + }, keys.X25519.publicKey, null), { name: 'InvalidAccessError' }); } @@ -198,9 +205,9 @@ async function prepareKeys() { { // Base key is not a private key await assert.rejects(subtle.deriveBits({ - name: 'X448', - public: keys.X448.privateKey - }, keys.X448.publicKey, null), { + name: 'X25519', + public: keys.X25519.privateKey + }, keys.X25519.publicKey, null), { name: 'InvalidAccessError' }); } @@ -215,9 +222,9 @@ async function prepareKeys() { false, ['encrypt']); await assert.rejects(subtle.deriveBits({ - name: 'X448', + name: 'X25519', public: key - }, keys.X448.publicKey, null), { + }, keys.X25519.publicKey, null), { name: 'InvalidAccessError' }); } diff --git a/test/parallel/test-webcrypto-derivebits-ecdh.js b/test/parallel/test-webcrypto-derivebits-ecdh.js index 7a7fa09d2b3754..6bc7a9ab846941 100644 --- a/test/parallel/test-webcrypto-derivebits-ecdh.js +++ b/test/parallel/test-webcrypto-derivebits-ecdh.js @@ -218,7 +218,7 @@ async function prepareKeys() { name: 'ECDH', public: publicKey }, keys['P-521'].privateKey, null), { - message: 'algorithm.public must be an ECDH key' + message: 'key algorithm mismatch' }); } diff --git a/test/parallel/test-webcrypto-derivebits-hkdf.js b/test/parallel/test-webcrypto-derivebits-hkdf.js index 0e3b2992e1c137..0629f85b0fb538 100644 --- a/test/parallel/test-webcrypto-derivebits-hkdf.js +++ b/test/parallel/test-webcrypto-derivebits-hkdf.js @@ -6,6 +6,7 @@ if (!common.hasCrypto) common.skip('missing crypto'); const assert = require('assert'); +const { hasOpenSSL } = require('../common/crypto'); const { subtle } = globalThis.crypto; function getDeriveKeyInfo(name, length, hash, ...usages) { @@ -19,14 +20,33 @@ const kDerivedKeyTypes = [ ['AES-CTR', 256, undefined, 'encrypt', 'decrypt'], ['AES-GCM', 128, undefined, 'encrypt', 'decrypt'], ['AES-GCM', 256, undefined, 'encrypt', 'decrypt'], - ['AES-KW', 128, undefined, 'wrapKey', 'unwrapKey'], - ['AES-KW', 256, undefined, 'wrapKey', 'unwrapKey'], ['HMAC', 256, 'SHA-1', 'sign', 'verify'], ['HMAC', 256, 'SHA-256', 'sign', 'verify'], ['HMAC', 256, 'SHA-384', 'sign', 'verify'], ['HMAC', 256, 'SHA-512', 'sign', 'verify'], ]; +if (!process.features.openssl_is_boringssl) { + kDerivedKeyTypes.push( + ['AES-KW', 128, undefined, 'wrapKey', 'unwrapKey'], + ['AES-KW', 256, undefined, 'wrapKey', 'unwrapKey'], + ['HMAC', 256, 'SHA3-256', 'sign', 'verify'], + ['HMAC', 256, 'SHA3-384', 'sign', 'verify'], + ['HMAC', 256, 'SHA3-512', 'sign', 'verify'], + ); +} else { + common.printSkipMessage('Skipping unsupported test cases'); +} + +if (hasOpenSSL(3)) { + kDerivedKeyTypes.push( + ['AES-OCB', 128, undefined, 'encrypt', 'decrypt'], + ['AES-OCB', 256, undefined, 'encrypt', 'decrypt'], + ); +} else { + common.printSkipMessage('Skipping unsupported test cases'); +} + const kDerivedKeys = { short: '5040737377307264', long: '55736572732073686f756c64207069636b206c6f6e6720706173737068726' + @@ -70,7 +90,19 @@ const kDerivations = { 'b127e92631c1c051482d6690941772b4', empty: '9e4b719033742101e90f1ad61e2ff3b4' + '256863667296d74389f1f02af2c4e6a6' - } + }, + 'SHA3-256': { + normal: '386b0693d7a58c4ddf01b49bfbbd2fa87c6f911991543995170ba20ed28df599', + empty: 'd029bc828b6c6c8bb16ce3d25f5058f19c7d2517745e11c5d65c6d242e82e47f', + }, + 'SHA3-384': { + normal: '8c3b72e659bad40bcd14bdc1f7c3836059d24253795ab046a272973fd0456508', + empty: '3211ff4c676f761494c1ca2683d2d4662fe1d770ae5c58ebf6af6acb181c7d71', + }, + 'SHA3-512': { + normal: '5588c5c70cb3dd2f95323da2e9d5f299ca99c301d920a499330c449d21c645cd', + empty: '2c944b916c2751a71a1b5e57fcb487939c624335683995770b9f7cc7cbbb21f0', + }, }, empty: { 'SHA-384': { @@ -96,7 +128,19 @@ const kDerivations = { '0acea6f165476eb83460b9353ed41dfe', empty: 'c8e12774135305c9147f2cc4766e5ead' + '25d8f457b9a1953d52677361ced558fb' - } + }, + 'SHA3-256': { + normal: '9befc557f5baf4075b5fb38c014b41b92ab7534150baf64201069e8807d0e83d', + empty: '54d1fa1aa7cad99dab0622b772170e775c103756183bac36a228fd817a98a3f6', + }, + 'SHA3-384': { + normal: '46b54c015e368677edf7ac16963bccd9d2ba8246eef0e8beb04d8d188774b91b', + empty: '46eb0b2649bb0f605d70e4818ffc8176ee1be9782396e69fb4d0fd7cfe902b55', + }, + 'SHA3-512': { + normal: 'aa4375c82b5d7a3cac88a0423250b3882f140c253e98e8e7a0f6055b0908e4c2', + empty: '6613003f98602ddb53ac35f5aa256c9f5279d50ee65bb08fdf2ecf65cc5df27f', + }, } }, long: { @@ -124,7 +168,19 @@ const kDerivations = { '50b3dd9a29f30606e2cad199bec14d13', empty: 'e579d1f9e7f08e6f990ffcfcce1ed201' + 'c5e37e62cdf606f0ba4aca80427fbc44' - } + }, + 'SHA3-256': { + normal: '24f38fd1905554b7cbf8395cc3976292d11ce24a0b3131da0fd4b109832d27e3', + empty: '33d0a5151c0f52e4bb7fb67cf7a17063127624dc3e685903f49ebb07872084d1', + }, + 'SHA3-384': { + normal: '15777581a1ea81ad0baac8a97d954df4142f7260d9e8351aa7f6ef6de2d04632', + empty: 'ada4da4e28dc971633a8760b265b3019db57baf17e7bf7e13cf78b1a676f6d44', + }, + 'SHA3-512': { + normal: '621e4602b07fcba55ed6b976a8bef513b0f7c4ad0c546e0f852993051d887408', + empty: 'f1292af65b05c86cf7146b739bc65785c707450316f3207ee54a3f596a7d0f7b', + }, }, empty: { 'SHA-384': { @@ -150,7 +206,19 @@ const kDerivations = { '851cc5baadb42cad024b6290fe213436', empty: 'b4f7e7557674d501cbfbc0148ad800c0' + '750189fe295a2aca5e1bf4122c85edf9' - } + }, + 'SHA3-256': { + normal: 'fe32459f7339dd2e8df6c6fc874ed9e81e3b7aad669edad9b71196f53ed95b12', + empty: '04519be1eb94079c91306cc5b21946b3de6a78ad35ec83d4f4a37bafbda678d7', + }, + 'SHA3-384': { + normal: 'a474e8289cb4a0511e90b87eaf9ec29cadd74d4c1f2ee1fb8cb5f7d08f91a379', + empty: '726c8c4b39083a7d5755604d3a67e9aa6139db00c08028ac9e69f7fb1525bf1d', + }, + 'SHA3-512': { + normal: 'c7a7f5004d1d595c6896498c169642ac24b946e13296ff53e12b534962a88675', + empty: '7b543480b5696932551abb3100d72e05c18f57fbb63aa44fe020bef1eec3555c', + }, } }, }; @@ -294,7 +362,7 @@ async function testDeriveBitsBadHash( subtle.deriveBits( { ...algorithm, - hash: hash.substring(0, 3) + hash.substring(4) + hash: hash.replace('-', '') }, baseKeys[size], 256), { message: /Unrecognized algorithm name/, name: 'NotSupportedError', @@ -306,7 +374,6 @@ async function testDeriveBitsBadHash( hash: 'PBKDF2' }, baseKeys[size], 256), { - message: /Unrecognized algorithm name/, name: 'NotSupportedError', }), ]); @@ -407,7 +474,7 @@ async function testDeriveKey( true, usages); - const bits = await subtle.exportKey('raw', key); + const bits = await subtle.exportKey(key.algorithm.name === 'AES-OCB' ? 'raw-secret' : 'raw', key); assert.strictEqual( Buffer.from(bits).toString('hex'), @@ -431,16 +498,13 @@ async function testDeriveKeyBadHash( subtle.deriveKey( { ...algorithm, - hash: hash.substring(0, 3) + hash.substring(4) + hash: hash.replace('-', '') }, baseKeys[size], keyType, true, usages), - { - message: /Unrecognized algorithm name/, - name: 'NotSupportedError', - }), + { name: 'NotSupportedError' }), assert.rejects( subtle.deriveKey( { @@ -451,10 +515,7 @@ async function testDeriveKeyBadHash( keyType, true, usages), - { - message: /Unrecognized algorithm name/, - name: 'NotSupportedError', - }), + { name: 'NotSupportedError' }), ]); } diff --git a/test/parallel/test-webcrypto-derivebits.js b/test/parallel/test-webcrypto-derivebits.js index 0db467b852283a..50892be7400e4a 100644 --- a/test/parallel/test-webcrypto-derivebits.js +++ b/test/parallel/test-webcrypto-derivebits.js @@ -123,8 +123,10 @@ const { subtle } = globalThis.crypto; assert.deepStrictEqual(secret1, secret2); } + test('X25519').then(common.mustCall()); if (!process.features.openssl_is_boringssl) { - test('X25519').then(common.mustCall()); test('X448').then(common.mustCall()); + } else { + common.printSkipMessage('Skipping unsupported X448 test case'); } } diff --git a/test/parallel/test-webcrypto-derivekey-cfrg.js b/test/parallel/test-webcrypto-derivekey-cfrg.js index 7d8467b4a37cde..c5a5b1f3518fef 100644 --- a/test/parallel/test-webcrypto-derivekey-cfrg.js +++ b/test/parallel/test-webcrypto-derivekey-cfrg.js @@ -18,18 +18,25 @@ const kTests = [ '64ea51fae5b3307cfe9706', result: '2768409dfab99ec23b8c89b93ff5880295f76176088f89e43dfebe7ea1950008' }, - { - name: 'X448', - size: 56, - pkcs8: '3046020100300506032b656f043a043858c7d29a3eb519b29d00cfb191bb64fc6' + +]; + +if (!process.features.openssl_is_boringssl) { + kTests.push( + { + name: 'X448', + size: 56, + pkcs8: '3046020100300506032b656f043a043858c7d29a3eb519b29d00cfb191bb64fc6' + 'd8a42d8f17176272b89f2272d1819295c6525c0829671b052ef0727530f188e31' + 'd0cc53bf26929e', - spki: '3042300506032b656f033900b604a1d1a5cd1d9426d561ef630a9eb16cbe69d5b9' + + spki: '3042300506032b656f033900b604a1d1a5cd1d9426d561ef630a9eb16cbe69d5b9' + 'ca615edc53633efb52ea31e6e6a0a1dbacc6e76cbce6482d7e4ba3d55d9e802765' + 'ce6f', - result: 'f0f6c5f17f94f4291eab7178866d37ec8906dd6c514143dc85be7cf28deff39b' - }, -]; + result: 'f0f6c5f17f94f4291eab7178866d37ec8906dd6c514143dc85be7cf28deff39b' + }, + ); +} else { + common.printSkipMessage('Skipping unsupported X448 test case'); +} async function prepareKeys() { const keys = {}; @@ -107,8 +114,8 @@ async function prepareKeys() { // Missing public property await assert.rejects( subtle.deriveKey( - { name: 'X448' }, - keys.X448.privateKey, + { name: 'X25519' }, + keys.X25519.privateKey, ...otherArgs), { code: 'ERR_MISSING_OPTION' }); } @@ -118,15 +125,15 @@ async function prepareKeys() { await assert.rejects( subtle.deriveKey( { - name: 'X448', + name: 'X25519', public: { message: 'Not a CryptoKey' } }, - keys.X448.privateKey, + keys.X25519.privateKey, ...otherArgs), { code: 'ERR_INVALID_ARG_TYPE' }); } - { + if (keys.X25519 && keys.X448) { // Mismatched named curves await assert.rejects( subtle.deriveKey( @@ -136,7 +143,7 @@ async function prepareKeys() { }, keys.X448.privateKey, ...otherArgs), - { message: 'algorithm.public must be an X448 key' }); + { message: 'key algorithm mismatch' }); } { @@ -144,10 +151,10 @@ async function prepareKeys() { await assert.rejects( subtle.deriveKey( { - name: 'X448', - public: keys.X448.publicKey + name: 'X25519', + public: keys.X25519.publicKey }, - keys.X448.publicKey, + keys.X25519.publicKey, ...otherArgs), { name: 'InvalidAccessError' }); } @@ -157,10 +164,10 @@ async function prepareKeys() { await assert.rejects( subtle.deriveKey( { - name: 'X448', - public: keys.X448.privateKey + name: 'X25519', + public: keys.X25519.privateKey }, - keys.X448.privateKey, + keys.X25519.privateKey, ...otherArgs), { name: 'InvalidAccessError' }); } @@ -177,10 +184,10 @@ async function prepareKeys() { await assert.rejects( subtle.deriveKey( { - name: 'X448', + name: 'X25519', public: key }, - keys.X448.publicKey, + keys.X25519.publicKey, ...otherArgs), { name: 'InvalidAccessError' }); } diff --git a/test/parallel/test-webcrypto-derivekey-ecdh.js b/test/parallel/test-webcrypto-derivekey-ecdh.js index 3b4f86cc02d5ba..61a284cb8f8dea 100644 --- a/test/parallel/test-webcrypto-derivekey-ecdh.js +++ b/test/parallel/test-webcrypto-derivekey-ecdh.js @@ -174,7 +174,7 @@ async function prepareKeys() { }, keys['P-521'].privateKey, ...otherArgs), - { message: 'algorithm.public must be an ECDH key' }); + { message: 'key algorithm mismatch' }); } { diff --git a/test/parallel/test-webcrypto-derivekey.js b/test/parallel/test-webcrypto-derivekey.js index 90f76839474e16..07d427e841c6b1 100644 --- a/test/parallel/test-webcrypto-derivekey.js +++ b/test/parallel/test-webcrypto-derivekey.js @@ -73,12 +73,29 @@ const { KeyObject } = require('crypto'); } const kTests = [ + ['hello', 'there', 'my friend', 'SHA-1', + '365ca5d3f42d050c74302e420c83975327950f1913a151eecd00526bf52614a0'], ['hello', 'there', 'my friend', 'SHA-256', '14d93b0ccd99d4f2cbd9fbfe9c830b5b8a43e3e45e32941ef21bdeb0fa87b6b6'], ['hello', 'there', 'my friend', 'SHA-384', 'e36cf2cf943d8f3a88adb80f478745c336ac811b1a86d03a7d10eb0b6b52295c'], + ['hello', 'there', 'my friend', 'SHA-512', + '1e42d43fcacba361716f65853bd5f3c479f679612f0180eab3c51ed6c9d2b47d'], ]; + if (!process.features.openssl_is_boringssl) { + kTests.push( + ['hello', 'there', 'my friend', 'SHA3-256', + '2a49a3b6fb219117af9e251c6c65f16600cbca13bd0be6e70d96b0b9fa4cf3fd'], + ['hello', 'there', 'my friend', 'SHA3-384', + '0437bb59b95f2db2c7684c0b439028cb0fdd6f0f5d03b9f489066a87ae147221'], + ['hello', 'there', 'my friend', 'SHA3-512', + '3bbc469d38214371921e52c6f147e96cb7eb370421a81f53dea8b4851dfb8bce'], + ); + } else { + common.printSkipMessage('Skipping unsupported SHA-3 test cases'); + } + const tests = Promise.all(kTests.map((args) => test(...args))); tests.then(common.mustCall()); @@ -109,10 +126,20 @@ const { KeyObject } = require('crypto'); } const kTests = [ - ['hello', 'there', 10, 'SHA-256', - 'f72d1cf4853fffbd16a42751765d11f8dc7939498ee7b7ce7678b4cb16fad880'], + ['hello', 'there', 5, 'SHA-1', + 'f8f65a5fd92c9b74916083a7e9b0001c46bc89e2a14c48014cf1e0e1dbabf635'], + ['hello', 'there', 5, 'SHA-256', + '2e575eae24267db32106c7dba01615e5417557e8c5cf33ba15a311cb0c2907ee'], ['hello', 'there', 5, 'SHA-384', '201509b012c9cd2fbe7ea938f0c509b36ecb140f38bf9130e96923f55f46756d'], + ['hello', 'there', 5, 'SHA-512', + '2e8d981741f98193e0af9c79870af0e985089341221edad9a130d297eae1984b'], + ['hello', 'there', 5, 'SHA3-256', + '0aed29b61b3ca3978aea34a9793276574ea997b69e8d03727438199f90571649'], + ['hello', 'there', 5, 'SHA3-384', + '7aa4a274aa19b4623c5d3091c4b06355de85ff6f25e53a83e3126cbb86ae68df'], + ['hello', 'there', 5, 'SHA3-512', + '4d909c47a81c625f866d1f9406248e6bc3c7ea89225fbccf1f08820254c9ef56'], ]; const tests = Promise.all(kTests.map((args) => test(...args))); @@ -128,26 +155,41 @@ const { KeyObject } = require('crypto'); [{ name: 'HMAC', hash: 'SHA-1' }, 'sign', 512], [{ name: 'HMAC', hash: 'SHA-256' }, 'sign', 512], // Not long enough secret generated by ECDH - // [{ name: 'HMAC', hash: 'SHA-384' }, 'sign', 1024], - // [{ name: 'HMAC', hash: 'SHA-512' }, 'sign', 1024], + [{ name: 'HMAC', hash: 'SHA-384' }, 'sign', 1024], + [{ name: 'HMAC', hash: 'SHA-512' }, 'sign', 1024], + [{ name: 'HMAC', hash: 'SHA3-256', length: 256 }, 'sign', 256], + [{ name: 'HMAC', hash: 'SHA3-384', length: 384 }, 'sign', 384], + [{ name: 'HMAC', hash: 'SHA3-512', length: 512 }, 'sign', 512], + // This interaction is not defined for now. + // https://github.com/WICG/webcrypto-modern-algos/issues/23 + // [{ name: 'HMAC', hash: 'SHA3-256' }, 'sign', 256], + // [{ name: 'HMAC', hash: 'SHA3-384' }, 'sign', 384], + // [{ name: 'HMAC', hash: 'SHA3-512' }, 'sign', 512], ]; (async () => { const keyPair = await subtle.generateKey({ name: 'ECDH', namedCurve: 'P-521' }, false, ['deriveKey']); for (const [derivedKeyAlgorithm, usage, expected] of vectors) { - const derived = await subtle.deriveKey( + const [result] = await Promise.allSettled([subtle.deriveKey( { name: 'ECDH', public: keyPair.publicKey }, keyPair.privateKey, derivedKeyAlgorithm, false, - [usage]); + [usage])]); - if (derived.algorithm.name === 'HMAC') { - assert.strictEqual(derived.algorithm.length, expected); + if (expected > 528) { + assert.strictEqual(result.status, 'rejected'); + assert.match(result.reason.message, /derived bit length is too small/); } else { - // KDFs cannot be exportable and do not indicate their length - const secretKey = KeyObject.from(derived); - assert.strictEqual(secretKey.symmetricKeySize, expected / 8); + assert.strictEqual(result.status, 'fulfilled'); + const derived = result.value; + if (derived.algorithm.name === 'HMAC') { + assert.strictEqual(derived.algorithm.length, expected); + } else { + // KDFs cannot be exportable and do not indicate their length + const secretKey = KeyObject.from(derived); + assert.strictEqual(secretKey.symmetricKeySize, expected / 8); + } } } })().then(common.mustCall()); @@ -159,6 +201,14 @@ const { KeyObject } = require('crypto'); [{ name: 'HMAC', hash: 'SHA-256' }, 'sign', 512], [{ name: 'HMAC', hash: 'SHA-384' }, 'sign', 1024], [{ name: 'HMAC', hash: 'SHA-512' }, 'sign', 1024], + [{ name: 'HMAC', hash: 'SHA3-256', length: 256 }, 'sign', 256], + [{ name: 'HMAC', hash: 'SHA3-384', length: 384 }, 'sign', 384], + [{ name: 'HMAC', hash: 'SHA3-512', length: 512 }, 'sign', 512], + // This interaction is not defined for now. + // https://github.com/WICG/webcrypto-modern-algos/issues/23 + // [{ name: 'HMAC', hash: 'SHA3-256' }, 'sign', 256], + // [{ name: 'HMAC', hash: 'SHA3-384' }, 'sign', 384], + // [{ name: 'HMAC', hash: 'SHA3-512' }, 'sign', 512], ]; (async () => { @@ -206,8 +256,10 @@ const { KeyObject } = require('crypto'); assert.deepStrictEqual(raw1, raw2); } + test('X25519').then(common.mustCall()); if (!process.features.openssl_is_boringssl) { - test('X25519').then(common.mustCall()); test('X448').then(common.mustCall()); + } else { + common.printSkipMessage('Skipping unsupported X448 test case'); } } diff --git a/test/parallel/test-webcrypto-digest.js b/test/parallel/test-webcrypto-digest.js index bfd01ecc12b63e..04507d77b59142 100644 --- a/test/parallel/test-webcrypto-digest.js +++ b/test/parallel/test-webcrypto-digest.js @@ -8,15 +8,27 @@ if (!common.hasCrypto) const assert = require('assert'); const { Buffer } = require('buffer'); const { subtle } = globalThis.crypto; -const { createHash } = require('crypto'); +const { createHash, getHashes } = require('crypto'); const kTests = [ - ['SHA-1', 'sha1', 160], - ['SHA-256', 'sha256', 256], - ['SHA-384', 'sha384', 384], - ['SHA-512', 'sha512', 512], + ['SHA-1', ['sha1'], 160], + ['SHA-256', ['sha256'], 256], + ['SHA-384', ['sha384'], 384], + ['SHA-512', ['sha512'], 512], ]; +if (!process.features.openssl_is_boringssl) { + kTests.push( + [{ name: 'cSHAKE128', length: 256 }, ['shake128', { outputLength: 256 >> 3 }], 256], + [{ name: 'cSHAKE256', length: 512 }, ['shake256', { outputLength: 512 >> 3 }], 512], + ['SHA3-256', ['sha3-256'], 256], + ['SHA3-384', ['sha3-384'], 384], + ['SHA3-512', ['sha3-512'], 512], + ); +} else { + common.printSkipMessage('Skipping unsupported test cases'); +} + // Empty hash just works, not checking result subtle.digest('SHA-512', Buffer.alloc(0)) .then(common.mustCall()); @@ -38,12 +50,10 @@ const kData = (new TextEncoder()).encode('hello'); await Promise.all(kTests.map(async (test) => { // Get the digest using the legacy crypto API const checkValue = - createHash(test[1]).update(kData).digest().toString('hex'); + createHash.apply(createHash, test[1]).update(kData).digest().toString('hex'); // Get the digest using the SubtleCrypto API const values = Promise.all([ - subtle.digest({ name: test[0] }, kData), - subtle.digest({ name: test[0], length: test[2] }, kData), subtle.digest(test[0], kData), subtle.digest(test[0], kData.buffer), subtle.digest(test[0], new DataView(kData.buffer)), @@ -137,17 +147,87 @@ const kDigestedData = { long: '4b02caf650276030ea5617e597c5d53fd9daa68b78bfe' + '60b22aab8d36a4c2a3affdb71234f49276737c575ddf7' + '4d14054cbd6fdb98fd0ddcbcb46f91ad76b6ee' + }, + 'cshake128': { + empty: '7f9c2ba4e88f827d616045507605853ed73b8093f6e' + + 'fbc88eb1a6eacfa66ef26', + short: 'dea62d73e6b59cf725d0320d660089a4475cbbd3b85' + + '39e36691f150d47556794', + medium: 'b1acd53a03e76a221e52ea578e042f686a68c3d1c9' + + '832ab18285cf4f304ca32d', + long: '3a5bf5676955e5dec87d430e526925558971ca14c370' + + 'ee5d7cf572b94c7c63d7' + }, + 'cshake256': { + empty: '46b9dd2b0ba88d13233b3feb743eeb243fcd52ea62b' + + '81b82b50c27646ed5762fd75dc4ddd8c0f200cb0501' + + '9d67b592f6fc821c49479ab48640292eacb3b7c4be', + short: '1738113f5abb3ee5320ee18aa266c3617a7475dbd8e' + + 'd9a985994fddd6112ad999ec8e2ebdfeafb96e76f6b' + + 'b3a3adba43da60f00cd12496df5af3e28ae6d3de42', + medium: '4146c13d86d9bc186b0b309ab6a124ee0c74ba26b8' + + 'c60dcc7b3ed505969aa8d19028c6317999a085b1e6' + + 'b6a785ce4ff632aeb27493227e44232fb7b3952141' + + '7b', + long: '0c42bfd1e282622fd8144aa29b072fd09fc2bae70885' + + 'd5290933492f9d17411926a613dd0611668c2ac999e8' + + 'c011aabaa9004323425fbad75b0f58ee6e777a94' + }, + 'sha3-256': { + empty: 'a7ffc6f8bf1ed76651c14756a061d662f580ff4de43b49fa82d80a4b80f8434a', + short: '3059af7aa33b517084e8ad7bbc4fb208a44c28ef32b4698d103dd540e4f91aa1', + medium: '1fa7cd1da74cd8046417508c8314e74a9a4a9d38f9f18e6cb215b8c891a0a80e', + long: 'b2cfc61e0386cdaef5e10a2be189891f5ef52a7624bfcd8edc893acc64fec600' + }, + 'sha3-384': { + empty: '0c63a75b845e4f7d01107d852e4c2485c51a50aaaa9' + + '4fc61995e71bbee983a2ac3713831264adb47fb6bd1' + + 'e058d5f004', + short: '54b8f0e4cf4974de740098f66b3024479b01631315a' + + '6773606c33eadc32556a6e778e08f0225ae79265aec' + + '666cb2390b', + medium: '437b7d8b68b250b5c1739ea4cc86db2033879dfb18' + + 'de292c9c50d9c193a4c79a08a6cae3f4e483c2795e' + + 'a5d1ef7e69d2', + long: '3b39c4c97ad87613305d0ccc987181713e2d5e84b1f9' + + '760011bcce0c297499005bdce8a3d2409b5ad0164f32' + + 'bb8778d0' + }, + 'sha3-512': { + empty: 'a69f73cca23a9ac5c8b567dc185a756e97c982164fe' + + '25859e0d1dcc1475c80a615b2123af1f5f94c11e3e9' + + '402c3ac558f500199d95b6d3e301758586281dcd26', + short: '2dd2e07a62e6ad0498ba84f313c4d4024cb46001f78' + + 'f75db336b0d4d8bd2a9ec152c4ad20878735d82ba08' + + '72ecf59608ef3ced2b2a8669427e7da31e362333d8', + medium: 'e640a21909536640369e9b0a48931c5cb2efcbc91f' + + 'ecf247306bc96a0e4ca33307cb8e1b9af367946dd01' + + 'c243f3907508d04f1692a3161df1f898de8ee25febe', + long: 'bd262cecf565c338032de5ba0138f0aacfe7dde83d27' + + '2d0d37d952829ed25de1a1342d98659ef7d2fa4aca7c' + + 'e2b1aa0784d8fc1dcbf81bcec7a7431a3da36bf7' } }; -async function testDigest(size, name) { +async function testDigest(size, alg) { const digest = await subtle.digest( - name, + alg, Buffer.from(kSourceData[size], 'hex')); assert.strictEqual( Buffer.from(digest).toString('hex'), - kDigestedData[name.toLowerCase()][size]); + kDigestedData[(alg.name || alg).toLowerCase()][size]); +} + +function applyXOF(name) { + if (name.match(/cshake128/i)) { + return { name, length: 256 }; + } + if (name.match(/cshake256/i)) { + return { name, length: 512 }; + } + return name; + } (async function() { @@ -158,9 +238,9 @@ async function testDigest(size, name) { const downCase = alg.toLowerCase(); const mixedCase = upCase.slice(0, 1) + downCase.slice(1); - variations.push(testDigest(size, upCase)); - variations.push(testDigest(size, downCase)); - variations.push(testDigest(size, mixedCase)); + variations.push(testDigest(size, applyXOF(upCase))); + variations.push(testDigest(size, applyXOF(downCase))); + variations.push(testDigest(size, applyXOF(mixedCase))); }); }); @@ -172,3 +252,18 @@ async function testDigest(size, name) { name: 'NotSupportedError', }); })().then(common.mustCall()); + +// CShake edge cases +if (getHashes().includes('shake128')) { + (async () => { + assert.deepStrictEqual( + new Uint8Array(await subtle.digest({ name: 'cSHAKE128', length: 0 }, Buffer.alloc(1))), + new Uint8Array(0), + ); + + await assert.rejects(subtle.digest({ name: 'cSHAKE128', length: 7 }, Buffer.alloc(1)), { + name: 'NotSupportedError', + message: 'Unsupported CShakeParams length', + }); + })().then(common.mustCall()); +} diff --git a/test/parallel/test-webcrypto-encap-decap-ml-kem.js b/test/parallel/test-webcrypto-encap-decap-ml-kem.js new file mode 100644 index 00000000000000..450ba2cefb0a4f --- /dev/null +++ b/test/parallel/test-webcrypto-encap-decap-ml-kem.js @@ -0,0 +1,264 @@ +'use strict'; + +const common = require('../common'); + +if (!common.hasCrypto) + common.skip('missing crypto'); + +const { hasOpenSSL } = require('../common/crypto'); + +if (!hasOpenSSL(3, 5)) + common.skip('requires OpenSSL >= 3.5'); + +const assert = require('assert'); +const crypto = require('crypto'); +const { KeyObject } = crypto; +const { subtle } = globalThis.crypto; + +const vectors = require('../fixtures/crypto/ml-kem')(); + +async function testEncapsulateKey({ name, publicKeyPem, privateKeyPem, results }) { + const [ + publicKey, + noEncapsulatePublicKey, + privateKey, + ] = await Promise.all([ + crypto.createPublicKey(publicKeyPem) + .toCryptoKey(name, false, ['encapsulateKey']), + crypto.createPublicKey(publicKeyPem) + .toCryptoKey(name, false, ['encapsulateBits']), + crypto.createPrivateKey(privateKeyPem) + .toCryptoKey(name, false, ['decapsulateKey']), + ]); + + // Test successful encapsulation + const encapsulated = await subtle.encapsulateKey( + { name }, + publicKey, + 'HKDF', + false, + ['deriveBits'] + ); + + assert(encapsulated.sharedKey instanceof CryptoKey); + assert(encapsulated.ciphertext instanceof ArrayBuffer); + assert.strictEqual(encapsulated.sharedKey.type, 'secret'); + assert.strictEqual(encapsulated.sharedKey.algorithm.name, 'HKDF'); + assert.strictEqual(encapsulated.sharedKey.extractable, false); + assert.deepStrictEqual(encapsulated.sharedKey.usages, ['deriveBits']); + + // Verify ciphertext length matches expected for algorithm + assert.strictEqual(encapsulated.ciphertext.byteLength, results.ciphertext.byteLength); + + // Test with different shared key algorithm + const encapsulated2 = await subtle.encapsulateKey( + { name }, + publicKey, + { name: 'HMAC', hash: 'SHA-256' }, + false, + ['sign', 'verify'] + ); + + assert(encapsulated2.sharedKey instanceof CryptoKey); + assert.strictEqual(encapsulated2.sharedKey.algorithm.name, 'HMAC'); + assert.strictEqual(encapsulated2.sharedKey.extractable, false); + + // Test failure when using wrong key type + await assert.rejects( + subtle.encapsulateKey({ name }, privateKey, 'HKDF', false, ['deriveBits']), { + name: 'InvalidAccessError', + }); + + // Test failure when using key without proper usage + await assert.rejects( + subtle.encapsulateKey({ name }, noEncapsulatePublicKey, 'HKDF', false, ['deriveBits']), { + name: 'InvalidAccessError', + }); +} + +async function testEncapsulateBits({ name, publicKeyPem, privateKeyPem, results }) { + const [ + publicKey, + noEncapsulatePublicKey, + privateKey, + ] = await Promise.all([ + crypto.createPublicKey(publicKeyPem) + .toCryptoKey(name, false, ['encapsulateBits']), + crypto.createPublicKey(publicKeyPem) + .toCryptoKey(name, false, ['encapsulateKey']), + crypto.createPrivateKey(privateKeyPem) + .toCryptoKey(name, false, ['decapsulateBits']), + ]); + + // Test successful encapsulation + const encapsulated = await subtle.encapsulateBits({ name }, publicKey); + + assert(encapsulated.sharedKey instanceof ArrayBuffer); + assert(encapsulated.ciphertext instanceof ArrayBuffer); + assert.strictEqual(encapsulated.sharedKey.byteLength, 32); // ML-KEM shared secret is 32 bytes + + // Verify ciphertext length matches expected for algorithm + assert.strictEqual(encapsulated.ciphertext.byteLength, results.ciphertext.byteLength); + + // Test failure when using wrong key type + await assert.rejects( + subtle.encapsulateBits({ name }, privateKey), { + name: 'InvalidAccessError', + }); + + // Test failure when using key without proper usage + await assert.rejects( + subtle.encapsulateBits({ name }, noEncapsulatePublicKey), { + name: 'InvalidAccessError', + }); +} + +async function testDecapsulateKey({ name, publicKeyPem, privateKeyPem, results }) { + const [ + publicKey, + privateKey, + noDecapsulatePrivateKey, + ] = await Promise.all([ + crypto.createPublicKey(publicKeyPem) + .toCryptoKey(name, false, ['encapsulateKey']), + crypto.createPrivateKey(privateKeyPem) + .toCryptoKey(name, false, ['decapsulateKey']), + crypto.createPrivateKey(privateKeyPem) + .toCryptoKey(name, false, ['decapsulateBits']), + ]); + + // Test successful round-trip: encapsulate then decapsulate + const encapsulated = await subtle.encapsulateKey( + { name }, + publicKey, + 'HKDF', + false, + ['deriveBits'] + ); + + const decapsulatedKey = await subtle.decapsulateKey( + { name }, + privateKey, + encapsulated.ciphertext, + 'HKDF', + false, + ['deriveBits'] + ); + + assert(decapsulatedKey instanceof CryptoKey); + assert.strictEqual(decapsulatedKey.type, 'secret'); + assert.strictEqual(decapsulatedKey.algorithm.name, 'HKDF'); + assert.strictEqual(decapsulatedKey.extractable, false); + assert.deepStrictEqual(decapsulatedKey.usages, ['deriveBits']); + + // Verify the keys are the same by using KeyObject.from() and comparing + const originalKeyData = KeyObject.from(encapsulated.sharedKey).export(); + const decapsulatedKeyData = KeyObject.from(decapsulatedKey).export(); + assert(originalKeyData.equals(decapsulatedKeyData)); + + // Test with test vector ciphertext and expected shared key + const vectorDecapsulatedKey = await subtle.decapsulateKey( + { name }, + privateKey, + results.ciphertext, + 'HKDF', + false, + ['deriveBits'] + ); + + const vectorKeyData = KeyObject.from(vectorDecapsulatedKey).export(); + assert(vectorKeyData.equals(results.sharedKey)); + + // Test failure when using wrong key type + await assert.rejects( + subtle.decapsulateKey({ name }, publicKey, encapsulated.ciphertext, + 'HKDF', false, ['deriveKey']), { + name: 'InvalidAccessError' + }); + + // Test failure when using key without proper usage + await assert.rejects( + subtle.decapsulateKey({ name }, noDecapsulatePrivateKey, encapsulated.ciphertext, + 'HKDF', false, ['deriveKey']), { + name: 'InvalidAccessError' + }); + + // Test failure with wrong ciphertext length + const wrongLengthCiphertext = new Uint8Array(encapsulated.ciphertext.byteLength - 1); + await assert.rejects( + subtle.decapsulateKey({ name }, privateKey, wrongLengthCiphertext, + 'HKDF', false, ['deriveKey']), { + name: 'OperationError', + }); +} + +async function testDecapsulateBits({ name, publicKeyPem, privateKeyPem, results }) { + const [ + publicKey, + privateKey, + noDecapsulatePrivateKey, + ] = await Promise.all([ + crypto.createPublicKey(publicKeyPem) + .toCryptoKey(name, false, ['encapsulateBits']), + crypto.createPrivateKey(privateKeyPem) + .toCryptoKey(name, false, ['decapsulateBits']), + crypto.createPrivateKey(privateKeyPem) + .toCryptoKey(name, false, ['decapsulateKey']), + ]); + + // Test successful round-trip: encapsulate then decapsulate + const encapsulated = await subtle.encapsulateBits({ name }, publicKey); + + const decapsulatedBits = await subtle.decapsulateBits( + { name }, + privateKey, + encapsulated.ciphertext + ); + + assert(decapsulatedBits instanceof ArrayBuffer); + assert.strictEqual(decapsulatedBits.byteLength, 32); // ML-KEM shared secret is 32 bytes + + // Verify the shared secrets are the same + assert(Buffer.from(encapsulated.sharedKey).equals(Buffer.from(decapsulatedBits))); + + // Test with test vector ciphertext and expected shared key + const vectorDecapsulatedBits = await subtle.decapsulateBits( + { name }, + privateKey, + results.ciphertext + ); + + assert(Buffer.from(vectorDecapsulatedBits).equals(results.sharedKey)); + + // Test failure when using wrong key type + await assert.rejects( + subtle.decapsulateBits({ name }, publicKey, encapsulated.ciphertext), { + name: 'InvalidAccessError' + }); + + // Test failure when using key without proper usage + await assert.rejects( + subtle.decapsulateBits({ name }, noDecapsulatePrivateKey, encapsulated.ciphertext), { + name: 'InvalidAccessError' + }); + + // Test failure with wrong ciphertext length + const wrongLengthCiphertext = new Uint8Array(encapsulated.ciphertext.byteLength - 1); + await assert.rejects( + subtle.decapsulateBits({ name }, privateKey, wrongLengthCiphertext), { + name: 'OperationError', + }); +} + +(async function() { + const variations = []; + + vectors.forEach((vector) => { + variations.push(testEncapsulateKey(vector)); + variations.push(testEncapsulateBits(vector)); + variations.push(testDecapsulateKey(vector)); + variations.push(testDecapsulateBits(vector)); + }); + + await Promise.all(variations); +})().then(common.mustCall()); diff --git a/test/parallel/test-webcrypto-encrypt-decrypt-aes.js b/test/parallel/test-webcrypto-encrypt-decrypt-aes.js index 298f6d60698139..e03be277f089c7 100644 --- a/test/parallel/test-webcrypto-encrypt-decrypt-aes.js +++ b/test/parallel/test-webcrypto-encrypt-decrypt-aes.js @@ -5,6 +5,8 @@ const common = require('../common'); if (!common.hasCrypto) common.skip('missing crypto'); +const { hasOpenSSL } = require('../common/crypto'); + const assert = require('assert'); const { subtle } = globalThis.crypto; @@ -12,8 +14,9 @@ async function testEncrypt({ keyBuffer, algorithm, plaintext, result }) { // Using a copy of plaintext to prevent tampering of the original plaintext = Buffer.from(plaintext); + const keyFormat = algorithm.name === 'AES-OCB' ? 'raw-secret' : 'raw'; const key = await subtle.importKey( - 'raw', + keyFormat, keyBuffer, { name: algorithm.name }, false, @@ -37,8 +40,9 @@ async function testEncrypt({ keyBuffer, algorithm, plaintext, result }) { } async function testEncryptNoEncrypt({ keyBuffer, algorithm, plaintext }) { + const keyFormat = algorithm.name === 'AES-OCB' ? 'raw-secret' : 'raw'; const key = await subtle.importKey( - 'raw', + keyFormat, keyBuffer, { name: algorithm.name }, false, @@ -50,8 +54,9 @@ async function testEncryptNoEncrypt({ keyBuffer, algorithm, plaintext }) { } async function testEncryptNoDecrypt({ keyBuffer, algorithm, plaintext }) { + const keyFormat = algorithm.name === 'AES-OCB' ? 'raw-secret' : 'raw'; const key = await subtle.importKey( - 'raw', + keyFormat, keyBuffer, { name: algorithm.name }, false, @@ -66,8 +71,9 @@ async function testEncryptNoDecrypt({ keyBuffer, algorithm, plaintext }) { async function testEncryptWrongAlg({ keyBuffer, algorithm, plaintext }, alg) { assert.notStrictEqual(algorithm.name, alg); + const keyFormat = alg === 'AES-OCB' ? 'raw-secret' : 'raw'; const key = await subtle.importKey( - 'raw', + keyFormat, keyBuffer, { name: alg }, false, @@ -79,8 +85,9 @@ async function testEncryptWrongAlg({ keyBuffer, algorithm, plaintext }, alg) { } async function testDecrypt({ keyBuffer, algorithm, result }) { + const keyFormat = algorithm.name === 'AES-OCB' ? 'raw-secret' : 'raw'; const key = await subtle.importKey( - 'raw', + keyFormat, keyBuffer, { name: algorithm.name }, false, @@ -202,6 +209,43 @@ async function testDecrypt({ keyBuffer, algorithm, result }) { })().then(common.mustCall()); } +// Test aes-ocb vectors +if (hasOpenSSL(3)) { + const { + passing, + failing, + decryptionFailing + } = require('../fixtures/crypto/aes_ocb')(); + + (async function() { + const variations = []; + + passing.forEach((vector) => { + variations.push(testEncrypt(vector)); + variations.push(testEncryptNoEncrypt(vector)); + variations.push(testEncryptNoDecrypt(vector)); + variations.push(testEncryptWrongAlg(vector, 'AES-GCM')); + }); + + failing.forEach((vector) => { + variations.push(assert.rejects(testEncrypt(vector), { + message: /is not a valid AES-OCB tag length/ + })); + variations.push(assert.rejects(testDecrypt(vector), { + message: /is not a valid AES-OCB tag length/ + })); + }); + + decryptionFailing.forEach((vector) => { + variations.push(assert.rejects(testDecrypt(vector), { + name: 'OperationError' + })); + }); + + await Promise.all(variations); + })().then(common.mustCall()); +} + { (async function() { const secretKey = await subtle.generateKey( diff --git a/test/parallel/test-webcrypto-encrypt-decrypt-chacha20-poly1305.js b/test/parallel/test-webcrypto-encrypt-decrypt-chacha20-poly1305.js new file mode 100644 index 00000000000000..aea9528f2463db --- /dev/null +++ b/test/parallel/test-webcrypto-encrypt-decrypt-chacha20-poly1305.js @@ -0,0 +1,255 @@ +'use strict'; + +const common = require('../common'); + +if (!common.hasCrypto) + common.skip('missing crypto'); + +if (process.features.openssl_is_boringssl) + common.skip('Skipping unsupported ChaCha20-Poly1305 test case'); + +const assert = require('assert'); +const { subtle } = globalThis.crypto; + +async function testEncrypt({ keyBuffer, algorithm, plaintext, result }) { + // Using a copy of plaintext to prevent tampering of the original + plaintext = Buffer.from(plaintext); + + const key = await subtle.importKey( + 'raw-secret', + keyBuffer, + { name: algorithm.name }, + false, + ['encrypt', 'decrypt']); + + const output = await subtle.encrypt(algorithm, key, plaintext); + plaintext[0] = 255 - plaintext[0]; + + assert.strictEqual( + Buffer.from(output).toString('hex'), + Buffer.from(result).toString('hex')); + + // Converting the returned ArrayBuffer into a Buffer right away, + // so that the next line works + const check = Buffer.from(await subtle.decrypt(algorithm, key, output)); + check[0] = 255 - check[0]; + + assert.strictEqual( + Buffer.from(check).toString('hex'), + Buffer.from(plaintext).toString('hex')); +} + +async function testEncryptNoEncrypt({ keyBuffer, algorithm, plaintext }) { + const key = await subtle.importKey( + 'raw-secret', + keyBuffer, + { name: algorithm.name }, + false, + ['decrypt']); + + return assert.rejects(subtle.encrypt(algorithm, key, plaintext), { + message: /The requested operation is not valid for the provided key/ + }); +} + +async function testEncryptNoDecrypt({ keyBuffer, algorithm, plaintext }) { + const key = await subtle.importKey( + 'raw-secret', + keyBuffer, + { name: algorithm.name }, + false, + ['encrypt']); + + const output = await subtle.encrypt(algorithm, key, plaintext); + + return assert.rejects(subtle.decrypt(algorithm, key, output), { + message: /The requested operation is not valid for the provided key/ + }); +} + +async function testEncryptWrongAlg({ keyBuffer, algorithm, plaintext }, alg) { + assert.notStrictEqual(algorithm.name, alg); + const key = await subtle.importKey( + 'raw-secret', + keyBuffer, + { name: alg }, + false, + ['encrypt']); + + return assert.rejects(subtle.encrypt(algorithm, key, plaintext), { + message: /The requested operation is not valid for the provided key/ + }); +} + +async function testDecrypt({ keyBuffer, algorithm, result }) { + const key = await subtle.importKey( + 'raw-secret', + keyBuffer, + { name: algorithm.name }, + false, + ['encrypt', 'decrypt']); + + await subtle.decrypt(algorithm, key, result); +} + +{ + const { + passing, + failing, + decryptionFailing + } = require('../fixtures/crypto/chacha20_poly1305')(); + + (async function() { + const variations = []; + + passing.forEach((vector) => { + variations.push(testEncrypt(vector)); + variations.push(testEncryptNoEncrypt(vector)); + variations.push(testEncryptNoDecrypt(vector)); + variations.push(testEncryptWrongAlg(vector, 'AES-GCM')); + }); + + failing.forEach((vector) => { + variations.push(assert.rejects(testEncrypt(vector), { + message: /is not a valid ChaCha20-Poly1305 tag length/ + })); + variations.push(assert.rejects(testDecrypt(vector), { + message: /is not a valid ChaCha20-Poly1305 tag length/ + })); + }); + + decryptionFailing.forEach((vector) => { + variations.push(assert.rejects(testDecrypt(vector), { + name: 'OperationError' + })); + }); + + await Promise.all(variations); + })().then(common.mustCall()); +} + +{ + (async function() { + const secretKey = await subtle.generateKey( + { + name: 'ChaCha20-Poly1305', + }, + false, + ['encrypt', 'decrypt'], + ); + + const iv = globalThis.crypto.getRandomValues(new Uint8Array(12)); + const aad = globalThis.crypto.getRandomValues(new Uint8Array(32)); + + const encrypted = await subtle.encrypt( + { + name: 'ChaCha20-Poly1305', + iv, + additionalData: aad, + }, + secretKey, + globalThis.crypto.getRandomValues(new Uint8Array(32)) + ); + + await subtle.decrypt( + { + name: 'ChaCha20-Poly1305', + iv, + additionalData: aad, + }, + secretKey, + new Uint8Array(encrypted), + ); + })().then(common.mustCall()); +} + +{ + + async function testRejectsImportKey(format, keyData, algorithm, extractable, usages, expectedError) { + await assert.rejects( + subtle.importKey(format, keyData, algorithm, extractable, usages), + expectedError + ); + } + + async function testRejectsGenerateKey(algorithm, extractable, usages, expectedError) { + await assert.rejects( + subtle.generateKey(algorithm, extractable, usages), + expectedError + ); + } + + (async function() { + const baseJwk = { kty: 'oct', k: 'AQIDBAUGBwgJCgsMDQ4PEBESExQVFhcYGRobHB0eHyA' }; + const alg = { name: 'ChaCha20-Poly1305' }; + const keyData32 = globalThis.crypto.getRandomValues(new Uint8Array(32)); + + // Test decrypt with data too small + const secretKey = await subtle.generateKey(alg, false, ['encrypt', 'decrypt']); + const iv = globalThis.crypto.getRandomValues(new Uint8Array(12)); + await assert.rejects( + subtle.decrypt({ name: 'ChaCha20-Poly1305', iv }, secretKey, new Uint8Array(8)), + { name: 'OperationError', message: /The provided data is too small/ } + ); + + // Test invalid tagLength values + await assert.rejects( + subtle.encrypt({ name: 'ChaCha20-Poly1305', iv, tagLength: 64 }, secretKey, keyData32), + { name: 'OperationError', message: /is not a valid ChaCha20-Poly1305 tag length/ } + ); + await assert.rejects( + subtle.encrypt({ name: 'ChaCha20-Poly1305', iv, tagLength: 96 }, secretKey, keyData32), + { name: 'OperationError', message: /is not a valid ChaCha20-Poly1305 tag length/ } + ); + + // JWK error conditions + const jwkTests = [ + [{ k: baseJwk.k }, /Invalid keyData/], + [{ ...baseJwk, kty: 'RSA' }, /Invalid JWK "kty" Parameter/], + [{ ...baseJwk, use: 'sig' }, /Invalid JWK "use" Parameter/], + [{ ...baseJwk, ext: false }, /JWK "ext" Parameter and extractable mismatch/, true], + [{ ...baseJwk, alg: 'A256GCM' }, /JWK "alg" does not match the requested algorithm/], + [{ ...baseJwk, key_ops: ['sign'] }, /Key operations and usage mismatch|Unsupported key usage/], + [{ ...baseJwk, key_ops: ['encrypt'] }, /Key operations and usage mismatch/, false, ['decrypt']], + ]; + + for (const [jwk, errorPattern, extractable = false, usages = ['encrypt']] of jwkTests) { + await testRejectsImportKey('jwk', jwk, alg, extractable, usages, + { name: 'DataError', message: errorPattern }); + } + + // Valid JWK imports + const validKeys = await Promise.all([ + subtle.importKey('jwk', { ...baseJwk, alg: 'C20P' }, alg, false, ['encrypt']), + subtle.importKey('jwk', { ...baseJwk, use: 'enc' }, alg, false, ['encrypt']), + ]); + validKeys.forEach((key) => assert.strictEqual(key.algorithm.name, 'ChaCha20-Poly1305')); + + // Invalid key usages + const usageTests = [ + [['sign'], 'generateKey'], + [['verify'], 'importKey'], + ]; + + for (const [usages, method] of usageTests) { + const fn = method === 'generateKey' ? + () => testRejectsGenerateKey(alg, false, usages, { name: 'SyntaxError', message: /Unsupported key usage/ }) : + () => testRejectsImportKey('raw-secret', keyData32, alg, false, usages, { name: 'SyntaxError', message: /Unsupported key usage/ }); + await fn(); + } + + // Valid wrapKey/unwrapKey usage + const wrapKey = await subtle.importKey('raw-secret', keyData32, alg, false, ['wrapKey', 'unwrapKey']); + assert.strictEqual(wrapKey.algorithm.name, 'ChaCha20-Poly1305'); + + // Invalid key lengths + for (const size of [16, 64]) { + await testRejectsImportKey('raw-secret', new Uint8Array(size), alg, false, ['encrypt'], + { name: 'DataError', message: /Invalid key length/ }); + } + + // Invalid JWK keyData + await testRejectsImportKey('jwk', { ...baseJwk, k: 'invalid-base64-!@#$%^&*()' }, alg, false, ['encrypt'], + { name: 'DataError' }); + })().then(common.mustCall()); +} diff --git a/test/parallel/test-webcrypto-encrypt-decrypt.js b/test/parallel/test-webcrypto-encrypt-decrypt.js index 5d4ecc02c74a31..a00c7d214bad99 100644 --- a/test/parallel/test-webcrypto-encrypt-decrypt.js +++ b/test/parallel/test-webcrypto-encrypt-decrypt.js @@ -6,12 +6,13 @@ if (!common.hasCrypto) common.skip('missing crypto'); const assert = require('assert'); +const { hasOpenSSL } = require('../common/crypto'); const { subtle } = globalThis.crypto; // This is only a partial test. The WebCrypto Web Platform Tests // will provide much greater coverage. -// Test Encrypt/Decrypt RSA-OAEP +// Test Encrypt/Decrypt RSA-OAEP w/ SHA-2 { const buf = globalThis.crypto.getRandomValues(new Uint8Array(50)); @@ -56,6 +57,51 @@ const { subtle } = globalThis.crypto; test().then(common.mustCall()); } +// Test Encrypt/Decrypt RSA-OAEP w/ SHA-3 +if (!process.features.openssl_is_boringssl) { + const buf = globalThis.crypto.getRandomValues(new Uint8Array(50)); + + async function test() { + const ec = new TextEncoder(); + const { publicKey, privateKey } = await subtle.generateKey({ + name: 'RSA-OAEP', + modulusLength: 2048, + publicExponent: new Uint8Array([1, 0, 1]), + hash: 'SHA3-384', + }, true, ['encrypt', 'decrypt']); + + const ciphertext = await subtle.encrypt({ + name: 'RSA-OAEP', + label: ec.encode('a label') + }, publicKey, buf); + + const plaintext = await subtle.decrypt({ + name: 'RSA-OAEP', + label: ec.encode('a label') + }, privateKey, ciphertext); + + assert.strictEqual( + Buffer.from(plaintext).toString('hex'), + Buffer.from(buf).toString('hex')); + + await assert.rejects(() => subtle.encrypt({ + name: 'RSA-OAEP', + }, privateKey, buf), { + name: 'InvalidAccessError', + message: 'The requested operation is not valid for the provided key' + }); + + await assert.rejects(() => subtle.decrypt({ + name: 'RSA-OAEP', + }, publicKey, ciphertext), { + name: 'InvalidAccessError', + message: 'The requested operation is not valid for the provided key' + }); + } + + test().then(common.mustCall()); +} + // Test Encrypt/Decrypt AES-CTR { const buf = globalThis.crypto.getRandomValues(new Uint8Array(50)); @@ -136,3 +182,32 @@ const { subtle } = globalThis.crypto; test().then(common.mustCall()); } + +// Test Encrypt/Decrypt AES-OCB +if (hasOpenSSL(3)) { + const buf = globalThis.crypto.getRandomValues(new Uint8Array(50)); + const iv = globalThis.crypto.getRandomValues(new Uint8Array(12)); + + async function test() { + const key = await subtle.generateKey({ + name: 'AES-OCB', + length: 256 + }, true, ['encrypt', 'decrypt']); + + const ciphertext = await subtle.encrypt( + { name: 'AES-OCB', iv }, key, buf, + ); + + const plaintext = await subtle.decrypt( + { name: 'AES-OCB', iv }, key, ciphertext, + ); + + assert.strictEqual( + Buffer.from(plaintext).toString('hex'), + Buffer.from(buf).toString('hex')); + } + + test().then(common.mustCall()); +} else { + common.printSkipMessage('Skipping unsupported AES-OCB test cases'); +} diff --git a/test/parallel/test-webcrypto-export-import-cfrg.js b/test/parallel/test-webcrypto-export-import-cfrg.js index ea1dd8176ae413..ae203e1005de0a 100644 --- a/test/parallel/test-webcrypto-export-import-cfrg.js +++ b/test/parallel/test-webcrypto-export-import-cfrg.js @@ -87,23 +87,30 @@ const testVectors = [ privateUsages: ['sign'], publicUsages: ['verify'] }, - { - name: 'Ed448', - privateUsages: ['sign'], - publicUsages: ['verify'] - }, { name: 'X25519', privateUsages: ['deriveKey', 'deriveBits'], publicUsages: [] }, - { - name: 'X448', - privateUsages: ['deriveKey', 'deriveBits'], - publicUsages: [] - }, ]; +if (!process.features.openssl_is_boringssl) { + testVectors.push( + { + name: 'Ed448', + privateUsages: ['sign'], + publicUsages: ['verify'] + }, + { + name: 'X448', + privateUsages: ['deriveKey', 'deriveBits'], + publicUsages: [] + }, + ); +} else { + common.printSkipMessage('Skipping unsupported Curve448 test cases'); +} + async function testImportSpki({ name, publicUsages }, extractable) { const key = await subtle.importKey( 'spki', diff --git a/test/parallel/test-webcrypto-export-import-ec.js b/test/parallel/test-webcrypto-export-import-ec.js index 57f1b2831e33bc..46a7e9153f2668 100644 --- a/test/parallel/test-webcrypto-export-import-ec.js +++ b/test/parallel/test-webcrypto-export-import-ec.js @@ -423,14 +423,14 @@ async function testImportRaw({ name, publicUsages }, namedCurve) { subtle.importKey( 'spki', rsaPublic.export({ format: 'der', type: 'spki' }), - { name, hash: 'SHA-256', namedCurve: 'P-256' }, + { name, namedCurve: 'P-256' }, true, publicUsages), { message: /Invalid key type/ }, ).then(common.mustCall()); assert.rejects( subtle.importKey( 'pkcs8', rsaPrivate.export({ format: 'der', type: 'pkcs8' }), - { name, hash: 'SHA-256', namedCurve: 'P-256' }, + { name, namedCurve: 'P-256' }, true, privateUsages), { message: /Invalid key type/ }, ).then(common.mustCall()); } @@ -491,7 +491,7 @@ async function testImportRaw({ name, publicUsages }, namedCurve) { subtle.importKey( 'pkcs8', pkcs8, - { name, hash: 'SHA-256', namedCurve }, + { name, namedCurve }, true, privateUsages), { name: 'DataError', message: /Invalid keyData/ }, ).then(common.mustCall()); } diff --git a/test/parallel/test-webcrypto-export-import-ml-dsa.js b/test/parallel/test-webcrypto-export-import-ml-dsa.js new file mode 100644 index 00000000000000..38d619eb8c00ec --- /dev/null +++ b/test/parallel/test-webcrypto-export-import-ml-dsa.js @@ -0,0 +1,511 @@ +'use strict'; + +const common = require('../common'); + +if (!common.hasCrypto) + common.skip('missing crypto'); + +const { hasOpenSSL } = require('../common/crypto'); + +if (!hasOpenSSL(3, 5)) + common.skip('requires OpenSSL >= 3.5'); + +const assert = require('assert'); +const { subtle } = globalThis.crypto; + +const fixtures = require('../common/fixtures'); + +function getKeyFileName(type, suffix) { + return `${type.replaceAll('-', '_')}_${suffix}.pem`; +} + +function toDer(pem) { + const der = pem.replace(/(?:-----(?:BEGIN|END) (?:PRIVATE|PUBLIC) KEY-----|\s)/g, ''); + return Buffer.alloc(Buffer.byteLength(der, 'base64'), der, 'base64'); +} + +/* eslint-disable @stylistic/js/max-len */ +const keyData = { + 'ML-DSA-44': { + pkcs8_seed_only: toDer(fixtures.readKey(getKeyFileName('ml-dsa-44', 'private_seed_only'), 'ascii')), + pkcs8: toDer(fixtures.readKey(getKeyFileName('ml-dsa-44', 'private'), 'ascii')), + pkcs8_priv_only: toDer(fixtures.readKey(getKeyFileName('ml-dsa-44', 'private_priv_only'), 'ascii')), + spki: toDer(fixtures.readKey(getKeyFileName('ml-dsa-44', 'public'), 'ascii')), + jwk: { + kty: 'AKP', + alg: 'ML-DSA-44', + pub: 'fYmD1Rx_jkoW9KG7Bs_5zyYEiWEZs15tYBxNdKq9NircZnvZBwwwaGbj0UsxJNc4Dyfp2IFAZZPO3rFCSUdpXHPrGRHwIVMzwiwfu2V7V02xoheW4mrkPThA3JRJSmNdsx6YGu37MaeJkIk6AlUexo46JfGrkRXZp_IyZxiL_L2dPrfwx-32j7WFI5sBadp7cDWfNkJjdQwW4puTe5Rw7h16GHb-DMOAKpfeMHujh7IYHuLCU6lVi90j1m8Ru0dxdmeQ1eY1vDnO7fNQKfzOLhpUNnj7BBZ24GTqFc-SN5HDCSCsSGKScTYYBwiSVTdSGG1GNqIiN2FgE4z1Jj6JFVB_OIUnl4sKbb3m8kB0BwtUPbkC0FVokGRUEGt6ba1Pc_IMpB5Gs3g9PFREI_C9o1yVW3NS2PzH_Vk4Tpf0N1K1kzIK_3IqekLfyqXmVDNsOovsS7Sw9TdmdWUNGRmhXFKRkex5VjpMIx7OwBGsYJCc4FhauWdrVtbkvHGggSpsla73ZcA4Vzh7aq47LMv0KS2YLp-DMn7SEohPHGg74118eLLn88yptxwtwt1dBFj8BKUfPrytuN1EIRQy34hwbkBLN9wDqhgn3Z3fvksRvmgN_4ZQ8YjeD-H3OFh5WJ_Rd66wHSl-YFat-_JF4UPcdlkNUbxPvDi5VL909Pe3VlwEZhT5otdtXQX4U3dUfqWKEh2kN0Q2lo8wbf3OMmBOFTfyX0eYa_5088ZnJvvliefn-TCDyc6WlcZrNqwBOF8N8-IN3b_8RPq-RuV8-mK-M83Hi4ElQB7Z44eZMmfUwFrozEG4Wq2K6MwQ_edG4dWeUVMCloTpGDFOtlLQlDoAN4m_sS2Lbwm_3ra29noUcK8_j10yy-hENE2Yluh1pIL-GoWZj3uYO-rEKVbszaagdE0DJ_uQcHUdNnBHKn64-cQ6xihXzxaeHx9OxkWWMKbzLtKpuYDK_X7EVvm8YTjl_oTsr2SWT2usjNJko32DhRV-OXLKKHo5FJpCy2bGFLXGG26CglUvgZQ2dyXiWeGVNKffOv1cQ5R_RlU2MpLiZ1bigy9hh4lu_XAHLfjQfhf71jeMuF4nEBWV-YOAjDTaDB2hcGqv_XcGXcmLWHqOWgc5Mb6lkb2zYs_oyOskmyFx6C0P7UrV8kCiN4zbuTqZNdNjlWL_QJUmU3vk6CpNa0XN1M3sLjZpOEsaqgRVPLcIDH-juVhyWiymuxe-8yNCOFSKxhscew08EQ9DEckP_iIA8qU2gcreHtvAS5VA5Emz1K2ypYe6oS3ogP-CX4nOAEfvjsb1HHJoclgiwjL1BtCLFgOE-0vn1M-nVOE6WbHGHoNKMJMHP2a3HQC7DmDfSOw5P6Cj5X7QVqhCY6tAGZWEPu3hUssp7K5UJePEdBn_LrErt4ucyXW6y1PAA2Fn8EuHaRyf2ggibDGnzq8E15m_R4LMvZAuGR0bN9jBTlm_x4ZQMqFwKkIdllkN1QTErazOyNsgU6fhA_20h5EIYT6-LqXr_Otj3Kp8MkJB9c3XNGoo5sbHTQCt0VNOHoxCFP_swiAJLtm743eOsI1M6naWLIqPagSCioosAvJYowypJQGvM-N3hBu8KUr0f911KRN7WqTAXTOHZ_vvTqcWKet0dFdh1EHuP3TrU8hSMciaphGvuK93T3gaWuJ6lcCkQndWvEo9S6FQB7eLU_ALKOQ3ROybUUkXgfyTkWDPxbHdeJCgMRv6Ig1PShPyxYb4ig', + priv: '273AhMPiZWLlSQCY41yi1fMj6xavGH0btB23zMhI1uY', + }, + }, + 'ML-DSA-65': { + pkcs8_seed_only: toDer(fixtures.readKey(getKeyFileName('ml-dsa-65', 'private_seed_only'), 'ascii')), + pkcs8: toDer(fixtures.readKey(getKeyFileName('ml-dsa-65', 'private'), 'ascii')), + pkcs8_priv_only: toDer(fixtures.readKey(getKeyFileName('ml-dsa-65', 'private_priv_only'), 'ascii')), + spki: toDer(fixtures.readKey(getKeyFileName('ml-dsa-65', 'public'), 'ascii')), + jwk: { + kty: 'AKP', + alg: 'ML-DSA-65', + pub: 'hxPP5LvG83t2fJyfA1TUssJK_ydrzryrCHGZuKFxmnl5Y3sxHRCPW_JpHEoiIgR6kgELnwibZnueax1zFerTOTA7o0NwXHFiaEB-8AmqJI93DkvtbUOSTCixa3admQBKW_PtgMCVtaEVuuvCuOEFhOyuZkyfvnpBwUKOkz3t-O1wpgrSmf-rdPXOEv8YcsSn-xfLYPSLzPCnt7gnIX_fwtkgnXjref-QqjFKlKZE2e7MkmHeViJ4iGy78r3UzVhBHsmFGC0ZNc8-iT3muH5Sn0SXmNq-F2EoerWLIAsPxL2KE6UrqPAwTbHn1B5sAGWvhsVVLlFPI1s1JLVLBNRJ5vhif525xNIpMAMuAZrteD827pve3zQo9_GHjWgykj9VzM9PEcVmVqxZ5u41kUXsM4PWZF29Oh2sYsmJ2LdiJ9RcA91vRLG2DqEYm-V5JwIz8uxL17DUsEC7zYthvtqGASq05CbfPTBev33rQUv4H0Etz99U89WooTk0FisHDz1uEUilU_VY1tN5byIDitXNf0jnz3SIHDUUZARn7ll0YwO0jtksT68sQW3Liy6Exhlp1td0so2qZUrbVZasjyCOVuibwbwvrdpP3QRsoG5UqkAqk8Rm2iCpdQSg87pswOscgA8AC8TczGHNfXc9PqzAmbsEPKvmZuE60HLGzqpRqFULf3nyYUQUqbdmKJsKQ29LXeDVbyy3-fkTUDuYqNC2tBY7PkzHJSA9Z4hDC_BHEFxcelibScSNyf7y4lDVWnuJMXpQ0WRh3UkUPa007IerhixwxvBvFXQR-ytYinixvjirlcEF1wQI1DzE8KjOXYYuFPS4Yl8HeZHQ-64Q0RuxlKIRP3YvjZWh4IDVvEVs5ZLzZPbE3Twe0N5a7iCu0BzZWTeHNbcMoViFyJTpec2w3vVHeI4PJB-5HeI8xuh-9y8ytTau8QtMe4thoROoajizDQLrkw3e6ryJJ3R84i0oni4vmZWyLDilwcLqPOkQJCIDMjq7exdmVX5t3DtAW4F6Coz0z3sf7tGlSMVxA7izCoVbG0y2_l1P2h7fWBuEPT7PWlMdPqu9Pj5jqXY6jJ0nkaR_pp7dDhO1HKae5edcBYunHZqVQQjRZ_DvKzbPrDk5t6Xq9fdSkiAeP3B4qn5uU-nx7OaX7DRoVEnbbiEDynIRPSEY-Ts3alPJtBv8zuzaGNyX05Z9MyZ0w-VlC-WxOBdVEsIAp_4uJ3kQ3UsfE9DLJH8WPuDI4t4i2VnNNyFlI0XSUocc_0rWgqp2I1UzSzkVbklwkuFywPI645u4G2XAlfdd_wpjFGC-IUPXgpeSfspPwW15sBP-ITS-gwtvfzQVLpRS0euzN97xo_GMhNPZ4bW-YyZt8z_R8bsQ8ktfoP-5RUV-yzYDt0tA01QJsZdBLf5J_H7qP8l4c8V4hPe_CFL032obbxmAnVPAP69u2SaMBlL8azjk4wGVFQpQp1JqMJCao2W8ZImCVegkPZxhGbx0nkgVfyFx4ihMeDNM288JbGC4CGON8C02Q84rQzhwzZE83Y9rSe1Bb0fUMHMu6ihD5jLdeltuBL4ZdJlKgL24KZK5o5pq4_l9SyzGAjB1KAQnClNOB1SxV89CtILu-65wb17s0z3qw2-NF0B6UVlGQFebjbSyLQv2ARaETh_8cBiPugVMgBIV3K1KBwNyWejyI1ZDCssvIZHJCF2SRW3HmJerTiB23eGHFYKSLdxW7LEzoHIc2xZEc3pwR43gavjeoL0pNc-HNFV_c19wiH7Tnw3IHld_FfTqAIPnqKMNIY7D_D0DmFNTOdnzcipqKxUB0Avc-wr8Fz0gjeRpLH2iDSCJWtvWjoeYvHTktGsblDAM5j9xznwEvZfQvj8fTUnFxl3clkD6e9V1jrDQDkXfOtl-bDIv9PtMwamfJFu-z2ubF-gKytUewPNo10uhwr2TDNdUayCZDR2T3HoRLN8goIw2bFoPJ98LoPcSukEvKABjH0DiHNeqFELNZPx_uCx5N-YFkUZxHWA1QUoGhqQ3REtcT3c-SZf_TDFOPvws6bmwt4lcWpLmubOAJLFt6J8m8HCkVUshRdFzvHQm_0JEvA3JtyXZzvsPUv5njdk0nxTZktvsnqX054RQk5x8U-lBY-bK3uMOoFnHju45LMoHCUgGJi22eUm7nLGZEh84ZAbNlPLXpfavXvPJh21OW5EOAeuQ-yWNHY2xbmAiHNnb-J2VpZc1Vy82sxn8umFtKduuQuIQMOsf4qHqj5MzDY_1NjrM4Wm7XAiLC4MpQ22w9PWNQXSZWvo2fj8WUnfEibpgyRkoD2P25GRQqsRJ3-Ykl5bm_2Vfe6i3oXHOwQwZwKGXfAqXyo4iU1UI7e-qC4sj5U64oB_A_NSBaJJrZoQ2fVeGTnFxA4QMMoWCT0VlwBXK0B3jht8Xal3WcI-i9ctQB1-GrmwwgG2ttePHt1IKy69bSZE3FLkFicaHg6VxypG6ef8rVsmMrfpTATOnF5_iEaLNY9428HHGW0iz4vXwaE-MkYy7NK2KMPFiCB0ec9OjIROwayK4LREv4qknWHnVQRSm25Rr9DcVFXKj16Au7X1hv7TuVH7h25U', + priv: '1X9VEr_iXMRwBvnSytEmHrtA-DpD6FWAUqMrDNlJVBg', + }, + }, + 'ML-DSA-87': { + pkcs8_seed_only: toDer(fixtures.readKey(getKeyFileName('ml-dsa-87', 'private_seed_only'), 'ascii')), + pkcs8: toDer(fixtures.readKey(getKeyFileName('ml-dsa-87', 'private'), 'ascii')), + pkcs8_priv_only: toDer(fixtures.readKey(getKeyFileName('ml-dsa-87', 'private_priv_only'), 'ascii')), + spki: toDer(fixtures.readKey(getKeyFileName('ml-dsa-87', 'public'), 'ascii')), + jwk: { + kty: 'AKP', + alg: 'ML-DSA-87', + pub: 'DZXqaBATRN0GRtigxzkLxp7C9fFYxI7Gl-tdfXqJHbzVCTTvRfRwZcu3YmpsUYXBdX2pVsQ51QlqxMslKBRmfNCanBLcfd57qoEIb0K6GIKZGHxlsr9aXNjEGcKMo0ICon0LYTvTWrl72Oz-2yEA_abPK3_dBUFGAYQ6kOQhAHcT1CMmTTck23PnEd5WUpYfZOA9giFX9dNVrrdFWczj_vDOty81ObNKsxfVWT1nG7c60UCJxb2c2tMrBx3rp7Hfc_aOg54W5KHocJi0Eai0ok4buySTe0UCSCUTkeoCcdiABgOFBiXRYzpm3Lz4uot6hSgFpuh67fE9Zpgtn64vfI-O1mgcPrPPpd3yA92Jrq-dvXuM55w1RmA_hha3U5Sh2vm0tD1U57q945UppFReIv_8NAKBkxQ_vHil7ySm-m7IAM-sTUY86_IqMZqisxoz7Ff7ZR2vIiUm3-L0ow4B8uPsCv2ZlUoVXvMF6XQiOHsgqgP1rfH8DmfmPFudwiXrAW6wEmi10skPmkN92aC3TPG6nmaNryQ7f8J82yVmGxW9U7zMbg21qNkRGBi_1YwEt6D8V2pUWv5U1a4p4-Ma0f4uQG4g0odM-WGomlh7pZWZf3sffiPXk9wBrGisxtCJuaB5vtkheWxpEfWqnhc3QdOWfrsRg6P1h7M95SNVW0U8A38wwrqPOpzEnckCVdCrZz2b2KVln6a4twfINg1-3lEZR4rkEmTaTYlLFlXzbRFWBBPGATxeRxhQ_9N5VhHi7STWPFD5HIJyVqz436bbVvM6Py_oldT_xt_tWlPc0w4Pesy2CgaCPlJCnx6cjEg_sRBUcRkBoHqa7aZj4JFFm9bzEaiJ2MKfkHVT4xdbEimMHsD0HkIQpg5-zoB2Jsqgc6Qi3L57hZi-Q1V0G2lmdZ5WZkQ2m5hxle4hHtAmghgynK2p0qzDWHScxHcdd2sInYqQgMYbnvs04YYKWpIfndCUBs9q_EONN5tn8gfSwHEKlQ-KpplEL5kc-99h2uaZsxRlJOF6_z8EZ-aKaKY8jvoAV0g4kZJH5UKy_MkBiva6r-zXUmo88qJjXQatOOSdfvJUTiZiSfcpBQqF9SSDD9WWsgInaOCKO_fAFf9fuacXDMEj0esUx1YrVEe_77S5uObg-UrK405U1JhKJJvd7o8xQKxenv5BJdsbbyYQDbSSe9BrCqeHEgmfRHTXdSvl_3QOP0Ej-dT8YJJHZ1lrujU7Zg5f5Kg99tU5GdLMbHX2kt4F2a0NX09HikEemvUg4NLPhjOihfVkChr-zdF69nfsnTiaQrMgpIcl9jttN99_8Gju-LU8OWbb92m9RLxAUFP115v22f77YPoILm92IjMZMkxEhGneoclWhnudkyR7YoTBjCnT5b7AC9_05uls637FmVf7Ck8-MF4gLil3dstXi4g24bitYhxxqWwiqF4vsDouSGUnuKCMwx3TLsII_xk77TjpQP4vpLdYM3tn94AVlTMMhnI-OZkVJk-_mIbywCwRHlQb5nzVCc0BWlM1kb9PJys2IfciS8LWEoxeq9moDX5w72yJKoLN3CWpD3VdJAiW79zUaySw-IeW0XaHnlze5fYnOozG8lIeyQ9sMZasMiFovGnR3b7jyMtA38U33v16fouWuBILOu0m_QOpRDI9i3rjRM6hdC48zCtNSzc1_1VPYkWDSFK1oVAjdd8-2rjyqdPeUwnqD26VA9_d3R7x8ThrazdbRC8U1hr9jpqNHuZ4LGYu3Ui8wB-lSt9QMaHz517MY_zBEoNGyvbQWtlM7mvLu12KoMM7nvGrPJnvD-HmxTqsVQolD8_lIV5ao72yiKDpArVr6RuV4PpI0j_Wy4-yDCuwBW0gjnB9GvCwOTeByYXJT6Ul7dgHck4BbF3IyFgvmY--ceWr5mBrbAC9LJP_4Wf5O6ul3hFrhiG6zSV4zzBYLnEwfW6LNLEZjZKgmBYiC5s1xlxYWDdcQ5FGmLQ9uEDkr4VItXQWvIIdeBQPyujxmd965Mig9-Sa7SCyV_3wH8fQnGlvU-jJMGL0zvzB2gcu7hMLMagUBj1AKXj-UxpbX1i95f2TOiZDwMeCCszgvCjQ21XKg07TBXrrOiFcgcADgdo-HJr7O1T3ozOIulq1PDM7QZH6i3wDD0j3b0NCdqKCWqhfLXz2-FszyUHmA_GCzOLVzrLT2DcGWIcQbkvF0yZPgTyqKArKa8qytOerdH6oCJ0bRl96855sMVjuUdyVLX7XW_rVTwsOwV0gVAx8SrzovtDFeHRNl7BQKMsyQ1BjWu25jqKJ598vAi3LCZv0kMdiC24qPdgZU4e2aUkco11EnD6nJgdqsVFxufCl4BD_D9g5Wy42fJt4ZgNPAcbUf341KERyReeBEQj-qlPB3IUTIXcJw68GScebhxb0W_tGKMBC6-ip4QfNW7UTxUxVxmCV7h0yRnBBlkuUR1eYQwWRmEPjKd3dLHvgHtr266NQmE1tnKtJlsdPKb0ztrI9vogsENsgGNFQ2tHoeX8vqxcagGznlPVPfc3DlqjBSeTFQaPWvmCQHVKgxkbvffKzFQyFvXEqt6bGGtkwBoRJ_IIwtSeWQ2nFPBe3rlyKrtSnQFIMibJbbYvPVE03Cld9R61r-GGDSQz4aXekLzePEVwnxpe4mWJGco7ctQyE73PekL1uo2g0bRK-KgaE878OiLRBo5T7c633xEf2hMy9532M2GVdTZuoE0LL-wpAh9GmNdvJZc7g2sINvwZi778v2WHcYEKqXvdmrX-Shyh3QkzgIGZrDzM4UlxxUWaXfZ0Z6PNguk7Jafqf3xuUe9Z8zfAJl5c_VA3k8dn7IRg99hRsh-TGBCzqzgjJq4p4XMWP2QuxFTGSgHRe2GSCFzd5-lPjrj76ZyT3MPUxQe_bV2VE-Oys3MT-VkCCM8jFCANXdrfltG6jSiUZ2uJUWNqdNnxPglmmyrgff_m-5CyIRWYXQsIdZGspqdjzb4F6RbBKfL2PQlM6zUfo9JNmE8YQq815Nxkex8vDOrImnew312fZA6rRjr9_uE4lEbw3U7PFlCKBUPvPnsdgedjKYhiS0xU6iS1NDKOvYhcrkCkiU67EmFD4U0-OCv9Kpbb5bIxTJuv405NxJBElAMVI-ya0ns7D4-xUPn05E7PhtGZT0eHwItjT6omThTsTHwB_bQqYfNrjrObO1l1go2hQ-cUadZYsG5l47CB5RlFhANtaC8tiq4KJi48TmEEApB_0VwOI4EmI7SR0oaqx3HRXZfeGevCx2yC9aCYM4HqcyqP2g_1HwsOYzwq4XDEbK5Yl1dtYABxPoo7t8FBq2sSmfrBJWFv_nvreb_DPwbfoSeCy9knqvOktSQRrPMmo-nNGpandBvjmrjSk3EdeziAP7XNre5I-bn_2voDxkzGFtUM-wzlL379ASRGej8FkNWaOyqGP6Anq5PSJ', + priv: 'LZSOlEPbU9S5_mSsMULffTyxZu6qKEOQ1nfEi2NCscg', + } + }, +}; +/* eslint-enable @stylistic/js/max-len */ + +const testVectors = [ + { + name: 'ML-DSA-44', + privateUsages: ['sign'], + publicUsages: ['verify'] + }, + { + name: 'ML-DSA-65', + privateUsages: ['sign'], + publicUsages: ['verify'] + }, + { + name: 'ML-DSA-87', + privateUsages: ['sign'], + publicUsages: ['verify'] + }, +]; + +async function testImportSpki({ name, publicUsages }, extractable) { + const key = await subtle.importKey( + 'spki', + keyData[name].spki, + { name }, + extractable, + publicUsages); + assert.strictEqual(key.type, 'public'); + assert.strictEqual(key.extractable, extractable); + assert.deepStrictEqual(key.usages, publicUsages); + assert.deepStrictEqual(key.algorithm.name, name); + assert.strictEqual(key.algorithm, key.algorithm); + assert.strictEqual(key.usages, key.usages); + + if (extractable) { + // Test the roundtrip + const spki = await subtle.exportKey('spki', key); + assert.strictEqual( + Buffer.from(spki).toString('hex'), + keyData[name].spki.toString('hex')); + } else { + await assert.rejects( + subtle.exportKey('spki', key), { + message: /key is not extractable/ + }); + } + + // Bad usage + await assert.rejects( + subtle.importKey( + 'spki', + keyData[name].spki, + { name }, + extractable, + ['wrapKey']), + { message: /Unsupported key usage/ }); +} + +async function testImportPkcs8({ name, privateUsages }, extractable) { + const key = await subtle.importKey( + 'pkcs8', + keyData[name].pkcs8, + { name }, + extractable, + privateUsages); + assert.strictEqual(key.type, 'private'); + assert.strictEqual(key.extractable, extractable); + assert.deepStrictEqual(key.usages, privateUsages); + assert.deepStrictEqual(key.algorithm.name, name); + assert.strictEqual(key.algorithm, key.algorithm); + assert.strictEqual(key.usages, key.usages); + + if (extractable) { + // Test the roundtrip + const pkcs8 = await subtle.exportKey('pkcs8', key); + assert.strictEqual( + Buffer.from(pkcs8).toString('hex'), + keyData[name].pkcs8_seed_only.toString('hex')); + } else { + await assert.rejects( + subtle.exportKey('pkcs8', key), { + message: /key is not extractable/ + }); + } + + await assert.rejects( + subtle.importKey( + 'pkcs8', + keyData[name].pkcs8, + { name }, + extractable, + [/* empty usages */]), + { name: 'SyntaxError', message: 'Usages cannot be empty when importing a private key.' }); +} + +async function testImportPkcs8SeedOnly({ name, privateUsages }, extractable) { + const key = await subtle.importKey( + 'pkcs8', + keyData[name].pkcs8_seed_only, + { name }, + extractable, + privateUsages); + assert.strictEqual(key.type, 'private'); + assert.strictEqual(key.extractable, extractable); + assert.deepStrictEqual(key.usages, privateUsages); + assert.deepStrictEqual(key.algorithm.name, name); + assert.strictEqual(key.algorithm, key.algorithm); + assert.strictEqual(key.usages, key.usages); + + if (extractable) { + // Test the roundtrip + const pkcs8 = await subtle.exportKey('pkcs8', key); + assert.strictEqual( + Buffer.from(pkcs8).toString('hex'), + keyData[name].pkcs8_seed_only.toString('hex')); + } else { + await assert.rejects( + subtle.exportKey('pkcs8', key), { + message: /key is not extractable/ + }); + } + + await assert.rejects( + subtle.importKey( + 'pkcs8', + keyData[name].pkcs8_seed_only, + { name }, + extractable, + [/* empty usages */]), + { name: 'SyntaxError', message: 'Usages cannot be empty when importing a private key.' }); +} + +async function testImportPkcs8PrivOnly({ name, privateUsages }, extractable) { + const key = await subtle.importKey( + 'pkcs8', + keyData[name].pkcs8_priv_only, + { name }, + extractable, + privateUsages); + assert.strictEqual(key.type, 'private'); + assert.strictEqual(key.extractable, extractable); + assert.deepStrictEqual(key.usages, privateUsages); + assert.deepStrictEqual(key.algorithm.name, name); + assert.strictEqual(key.algorithm, key.algorithm); + assert.strictEqual(key.usages, key.usages); + + if (extractable) { + await assert.rejects(subtle.exportKey('pkcs8', key), (err) => { + assert.strictEqual(err.name, 'OperationError'); + assert.strictEqual(err.cause.code, 'ERR_CRYPTO_OPERATION_FAILED'); + assert.strictEqual(err.cause.message, 'Failed to get raw seed'); + return true; + }); + } else { + await assert.rejects( + subtle.exportKey('pkcs8', key), { + message: /key is not extractable/ + }); + } + + await assert.rejects( + subtle.importKey( + 'pkcs8', + keyData[name].pkcs8_seed_only, + { name }, + extractable, + [/* empty usages */]), + { name: 'SyntaxError', message: 'Usages cannot be empty when importing a private key.' }); +} + +async function testImportJwk({ name, publicUsages, privateUsages }, extractable) { + + const jwk = keyData[name].jwk; + + const tests = [ + subtle.importKey( + 'jwk', + { + kty: jwk.kty, + alg: jwk.alg, + pub: jwk.pub, + }, + { name }, + extractable, publicUsages), + subtle.importKey( + 'jwk', + jwk, + { name }, + extractable, + privateUsages), + ]; + + const [ + publicKey, + privateKey, + ] = await Promise.all(tests); + + assert.strictEqual(publicKey.type, 'public'); + assert.strictEqual(privateKey.type, 'private'); + assert.strictEqual(publicKey.extractable, extractable); + assert.strictEqual(privateKey.extractable, extractable); + assert.deepStrictEqual(publicKey.usages, publicUsages); + assert.deepStrictEqual(privateKey.usages, privateUsages); + assert.strictEqual(publicKey.algorithm.name, name); + assert.strictEqual(privateKey.algorithm.name, name); + assert.strictEqual(privateKey.algorithm, privateKey.algorithm); + assert.strictEqual(privateKey.usages, privateKey.usages); + assert.strictEqual(publicKey.algorithm, publicKey.algorithm); + assert.strictEqual(publicKey.usages, publicKey.usages); + + if (extractable) { + // Test the round trip + const [ + pubJwk, + pvtJwk, + ] = await Promise.all([ + subtle.exportKey('jwk', publicKey), + subtle.exportKey('jwk', privateKey), + ]); + + assert.deepStrictEqual(pubJwk.key_ops, publicUsages); + assert.strictEqual(pubJwk.ext, true); + assert.strictEqual(pubJwk.kty, 'AKP'); + assert.strictEqual(pubJwk.pub, jwk.pub); + + assert.deepStrictEqual(pvtJwk.key_ops, privateUsages); + assert.strictEqual(pvtJwk.ext, true); + assert.strictEqual(pvtJwk.kty, 'AKP'); + assert.strictEqual(pvtJwk.pub, jwk.pub); + assert.strictEqual(pvtJwk.priv, jwk.priv); + + assert.strictEqual(pubJwk.alg, jwk.alg); + assert.strictEqual(pvtJwk.alg, jwk.alg); + } else { + await assert.rejects( + subtle.exportKey('jwk', publicKey), { + message: /key is not extractable/ + }); + await assert.rejects( + subtle.exportKey('jwk', privateKey), { + message: /key is not extractable/ + }); + } + + await assert.rejects( + subtle.importKey( + 'jwk', + { ...jwk, use: 'enc' }, + { name }, + extractable, + privateUsages), + { message: 'Invalid JWK "use" Parameter' }); + + await assert.rejects( + subtle.importKey( + 'jwk', + { ...jwk, pub: undefined }, + { name }, + extractable, + privateUsages), + { message: 'Invalid JWK' }); + + await assert.rejects( + subtle.importKey( + 'jwk', + { ...jwk, priv: 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' }, // Public vs private mismatch + { name }, + extractable, + privateUsages), + { message: 'Invalid keyData' }); + + await assert.rejects( + subtle.importKey( + 'jwk', + { ...jwk, kty: 'OKP' }, + { name }, + extractable, + privateUsages), + { message: 'Invalid JWK "kty" Parameter' }); + + await assert.rejects( + subtle.importKey( + 'jwk', + { ...jwk }, + { name }, + extractable, + publicUsages), // Invalid for a private key + { message: /Unsupported key usage/ }); + + await assert.rejects( + subtle.importKey( + 'jwk', + { ...jwk, ext: false }, + { name }, + true, + privateUsages), + { message: 'JWK "ext" Parameter and extractable mismatch' }); + + await assert.rejects( + subtle.importKey( + 'jwk', + { ...jwk, priv: undefined }, + { name }, + extractable, + privateUsages), // Invalid for a public key + { message: /Unsupported key usage/ }); + + for (const alg of [undefined, name === 'ML-DSA-44' ? 'ML-DSA-87' : 'ML-DSA-44']) { + await assert.rejects( + subtle.importKey( + 'jwk', + { kty: jwk.kty, pub: jwk.pub, alg }, + { name }, + extractable, + publicUsages), + { message: 'JWK "alg" Parameter and algorithm name mismatch' }); + + await assert.rejects( + subtle.importKey( + 'jwk', + { ...jwk, alg }, + { name }, + extractable, + privateUsages), + { message: 'JWK "alg" Parameter and algorithm name mismatch' }); + } + + await assert.rejects( + subtle.importKey( + 'jwk', + { ...jwk }, + { name }, + extractable, + [/* empty usages */]), + { name: 'SyntaxError', message: 'Usages cannot be empty when importing a private key.' }); + + await assert.rejects( + subtle.importKey( + 'jwk', + { kty: jwk.kty, /* missing pub */ alg: jwk.alg }, + { name }, + extractable, + publicUsages), + { name: 'DataError', message: 'Invalid keyData' }); +} + +async function testImportRawPublic({ name, publicUsages }, extractable) { + const jwk = keyData[name].jwk; + const pub = Buffer.from(jwk.pub, 'base64url'); + + const publicKey = await subtle.importKey( + 'raw-public', + pub, + { name }, + extractable, publicUsages); + + assert.strictEqual(publicKey.type, 'public'); + assert.deepStrictEqual(publicKey.usages, publicUsages); + assert.strictEqual(publicKey.algorithm.name, name); + assert.strictEqual(publicKey.algorithm, publicKey.algorithm); + assert.strictEqual(publicKey.usages, publicKey.usages); + assert.strictEqual(publicKey.extractable, extractable); + + if (extractable) { + const value = await subtle.exportKey('raw-public', publicKey); + assert.deepStrictEqual(Buffer.from(value), pub); + + await assert.rejects(subtle.exportKey('raw', publicKey), { + name: 'NotSupportedError', + message: `Unable to export ${publicKey.algorithm.name} public key using raw format`, + }); + } + + await assert.rejects( + subtle.importKey( + 'raw-public', + pub.subarray(0, pub.byteLength - 1), + { name }, + extractable, publicUsages), + { message: 'Invalid keyData' }); + + await assert.rejects( + subtle.importKey( + 'raw-public', + pub, + { name: name === 'ML-DSA-44' ? 'ML-DSA-65' : 'ML-DSA-44' }, + extractable, publicUsages), + { message: 'Invalid keyData' }); +} + +async function testImportRawSeed({ name, privateUsages }, extractable) { + const jwk = keyData[name].jwk; + const seed = Buffer.from(jwk.priv, 'base64url'); + + const privateKey = await subtle.importKey( + 'raw-seed', + seed, + { name }, + extractable, privateUsages); + + assert.strictEqual(privateKey.type, 'private'); + assert.deepStrictEqual(privateKey.usages, privateUsages); + assert.strictEqual(privateKey.algorithm.name, name); + assert.strictEqual(privateKey.algorithm, privateKey.algorithm); + assert.strictEqual(privateKey.usages, privateKey.usages); + assert.strictEqual(privateKey.extractable, extractable); + + if (extractable) { + const value = await subtle.exportKey('raw-seed', privateKey); + assert.deepStrictEqual(Buffer.from(value), seed); + } + + await assert.rejects( + subtle.importKey( + 'raw-seed', + seed.subarray(0, 30), + { name }, + extractable, + privateUsages), + { message: 'Invalid keyData' }); +} + +(async function() { + const tests = []; + for (const vector of testVectors) { + for (const extractable of [true, false]) { + tests.push(testImportSpki(vector, extractable)); + tests.push(testImportPkcs8(vector, extractable)); + tests.push(testImportPkcs8SeedOnly(vector, extractable)); + tests.push(testImportPkcs8PrivOnly(vector, extractable)); + tests.push(testImportJwk(vector, extractable)); + tests.push(testImportRawSeed(vector, extractable)); + tests.push(testImportRawPublic(vector, extractable)); + } + } + await Promise.all(tests); +})().then(common.mustCall()); + +(async function() { + const alg = 'ML-DSA-44'; + const pub = Buffer.from(keyData[alg].jwk.pub, 'base64url'); + await assert.rejects(subtle.importKey('raw', pub, alg, false, []), { + name: 'NotSupportedError', + message: 'Unable to import ML-DSA-44 using raw format', + }); +})().then(common.mustCall()); diff --git a/test/parallel/test-webcrypto-export-import-ml-kem.js b/test/parallel/test-webcrypto-export-import-ml-kem.js new file mode 100644 index 00000000000000..c927b5571a69da --- /dev/null +++ b/test/parallel/test-webcrypto-export-import-ml-kem.js @@ -0,0 +1,315 @@ +'use strict'; + +const common = require('../common'); + +if (!common.hasCrypto) + common.skip('missing crypto'); + +const { hasOpenSSL } = require('../common/crypto'); + +if (!hasOpenSSL(3, 5)) + common.skip('requires OpenSSL >= 3.5'); + +const assert = require('assert'); +const { subtle } = globalThis.crypto; + +const fixtures = require('../common/fixtures'); + +function getKeyFileName(type, suffix) { + return `${type.replaceAll('-', '_')}_${suffix}.pem`; +} + +function toDer(pem) { + const der = pem.replace(/(?:-----(?:BEGIN|END) (?:PRIVATE|PUBLIC) KEY-----|\s)/g, ''); + return Buffer.alloc(Buffer.byteLength(der, 'base64'), der, 'base64'); +} + +const keyData = { + 'ML-KEM-512': { + pkcs8_seed_only: toDer(fixtures.readKey(getKeyFileName('ml-kem-512', 'private_seed_only'), 'ascii')), + pkcs8: toDer(fixtures.readKey(getKeyFileName('ml-kem-512', 'private'), 'ascii')), + pkcs8_priv_only: toDer(fixtures.readKey(getKeyFileName('ml-kem-512', 'private_priv_only'), 'ascii')), + spki: toDer(fixtures.readKey(getKeyFileName('ml-kem-512', 'public'), 'ascii')), + pub_len: 800, + }, + 'ML-KEM-768': { + pkcs8_seed_only: toDer(fixtures.readKey(getKeyFileName('ml-kem-768', 'private_seed_only'), 'ascii')), + pkcs8: toDer(fixtures.readKey(getKeyFileName('ml-kem-768', 'private'), 'ascii')), + pkcs8_priv_only: toDer(fixtures.readKey(getKeyFileName('ml-kem-768', 'private_priv_only'), 'ascii')), + spki: toDer(fixtures.readKey(getKeyFileName('ml-kem-768', 'public'), 'ascii')), + pub_len: 1184, + }, + 'ML-KEM-1024': { + pkcs8_seed_only: toDer(fixtures.readKey(getKeyFileName('ml-kem-1024', 'private_seed_only'), 'ascii')), + pkcs8: toDer(fixtures.readKey(getKeyFileName('ml-kem-1024', 'private'), 'ascii')), + pkcs8_priv_only: toDer(fixtures.readKey(getKeyFileName('ml-kem-1024', 'private_priv_only'), 'ascii')), + spki: toDer(fixtures.readKey(getKeyFileName('ml-kem-1024', 'public'), 'ascii')), + pub_len: 1568, + }, +}; + +const testVectors = [ + { + name: 'ML-KEM-512', + privateUsages: ['decapsulateKey', 'decapsulateBits'], + publicUsages: ['encapsulateKey', 'encapsulateBits'] + }, + { + name: 'ML-KEM-768', + privateUsages: ['decapsulateKey', 'decapsulateBits'], + publicUsages: ['encapsulateKey', 'encapsulateBits'] + }, + { + name: 'ML-KEM-1024', + privateUsages: ['decapsulateKey', 'decapsulateBits'], + publicUsages: ['encapsulateKey', 'encapsulateBits'] + }, +]; + +async function testImportSpki({ name, publicUsages }, extractable) { + const key = await subtle.importKey( + 'spki', + keyData[name].spki, + { name }, + extractable, + publicUsages); + assert.strictEqual(key.type, 'public'); + assert.strictEqual(key.extractable, extractable); + assert.deepStrictEqual(key.usages, publicUsages); + assert.deepStrictEqual(key.algorithm.name, name); + assert.strictEqual(key.algorithm, key.algorithm); + assert.strictEqual(key.usages, key.usages); + + if (extractable) { + // Test the roundtrip + const spki = await subtle.exportKey('spki', key); + assert.strictEqual( + Buffer.from(spki).toString('hex'), + keyData[name].spki.toString('hex')); + } else { + await assert.rejects( + subtle.exportKey('spki', key), { + message: /key is not extractable/ + }); + } + + // Bad usage + await assert.rejects( + subtle.importKey( + 'spki', + keyData[name].spki, + { name }, + extractable, + ['wrapKey']), + { message: /Unsupported key usage/ }); +} + +async function testImportPkcs8({ name, privateUsages }, extractable) { + const key = await subtle.importKey( + 'pkcs8', + keyData[name].pkcs8, + { name }, + extractable, + privateUsages); + assert.strictEqual(key.type, 'private'); + assert.strictEqual(key.extractable, extractable); + assert.deepStrictEqual(key.usages, privateUsages); + assert.deepStrictEqual(key.algorithm.name, name); + assert.strictEqual(key.algorithm, key.algorithm); + assert.strictEqual(key.usages, key.usages); + + if (extractable) { + // Test the roundtrip + const pkcs8 = await subtle.exportKey('pkcs8', key); + assert.strictEqual( + Buffer.from(pkcs8).toString('hex'), + keyData[name].pkcs8_seed_only.toString('hex')); + } else { + await assert.rejects( + subtle.exportKey('pkcs8', key), { + message: /key is not extractable/ + }); + } + + await assert.rejects( + subtle.importKey( + 'pkcs8', + keyData[name].pkcs8, + { name }, + extractable, + [/* empty usages */]), + { name: 'SyntaxError', message: 'Usages cannot be empty when importing a private key.' }); +} + +async function testImportPkcs8SeedOnly({ name, privateUsages }, extractable) { + const key = await subtle.importKey( + 'pkcs8', + keyData[name].pkcs8_seed_only, + { name }, + extractable, + privateUsages); + assert.strictEqual(key.type, 'private'); + assert.strictEqual(key.extractable, extractable); + assert.deepStrictEqual(key.usages, privateUsages); + assert.deepStrictEqual(key.algorithm.name, name); + assert.strictEqual(key.algorithm, key.algorithm); + assert.strictEqual(key.usages, key.usages); + + if (extractable) { + // Test the roundtrip + const pkcs8 = await subtle.exportKey('pkcs8', key); + assert.strictEqual( + Buffer.from(pkcs8).toString('hex'), + keyData[name].pkcs8_seed_only.toString('hex')); + } else { + await assert.rejects( + subtle.exportKey('pkcs8', key), { + message: /key is not extractable/ + }); + } + + await assert.rejects( + subtle.importKey( + 'pkcs8', + keyData[name].pkcs8_seed_only, + { name }, + extractable, + [/* empty usages */]), + { name: 'SyntaxError', message: 'Usages cannot be empty when importing a private key.' }); +} + +async function testImportPkcs8PrivOnly({ name, privateUsages }, extractable) { + const key = await subtle.importKey( + 'pkcs8', + keyData[name].pkcs8_priv_only, + { name }, + extractable, + privateUsages); + assert.strictEqual(key.type, 'private'); + assert.strictEqual(key.extractable, extractable); + assert.deepStrictEqual(key.usages, privateUsages); + assert.deepStrictEqual(key.algorithm.name, name); + assert.strictEqual(key.algorithm, key.algorithm); + assert.strictEqual(key.usages, key.usages); + + if (extractable) { + await assert.rejects(subtle.exportKey('pkcs8', key), (err) => { + assert.strictEqual(err.name, 'OperationError'); + assert.strictEqual(err.cause.code, 'ERR_CRYPTO_OPERATION_FAILED'); + assert.strictEqual(err.cause.message, 'Failed to get raw seed'); + return true; + }); + } else { + await assert.rejects( + subtle.exportKey('pkcs8', key), { + message: /key is not extractable/ + }); + } + + await assert.rejects( + subtle.importKey( + 'pkcs8', + keyData[name].pkcs8_seed_only, + { name }, + extractable, + [/* empty usages */]), + { name: 'SyntaxError', message: 'Usages cannot be empty when importing a private key.' }); +} + +async function testImportRawPublic({ name, publicUsages }, extractable) { + const pub = keyData[name].spki.subarray(-keyData[name].pub_len); + + const publicKey = await subtle.importKey( + 'raw-public', + pub, + { name }, + extractable, publicUsages); + + assert.strictEqual(publicKey.type, 'public'); + assert.deepStrictEqual(publicKey.usages, publicUsages); + assert.strictEqual(publicKey.algorithm.name, name); + assert.strictEqual(publicKey.algorithm, publicKey.algorithm); + assert.strictEqual(publicKey.usages, publicKey.usages); + assert.strictEqual(publicKey.extractable, extractable); + + if (extractable) { + const value = await subtle.exportKey('raw-public', publicKey); + assert.deepStrictEqual(Buffer.from(value), pub); + + await assert.rejects(subtle.exportKey('raw', publicKey), { + name: 'NotSupportedError', + message: `Unable to export ${publicKey.algorithm.name} public key using raw format`, + }); + } + + await assert.rejects( + subtle.importKey( + 'raw-public', + pub.subarray(0, pub.byteLength - 1), + { name }, + extractable, publicUsages), + { message: 'Invalid keyData' }); + + await assert.rejects( + subtle.importKey( + 'raw-public', + pub, + { name: name === 'ML-KEM-512' ? 'ML-KEM-768' : 'ML-KEM-512' }, + extractable, publicUsages), + { message: 'Invalid keyData' }); +} + +async function testImportRawSeed({ name, privateUsages }, extractable) { + const seed = keyData[name].pkcs8_seed_only.subarray(-64); + + const privateKey = await subtle.importKey( + 'raw-seed', + seed, + { name }, + extractable, privateUsages); + + assert.strictEqual(privateKey.type, 'private'); + assert.deepStrictEqual(privateKey.usages, privateUsages); + assert.strictEqual(privateKey.algorithm.name, name); + assert.strictEqual(privateKey.algorithm, privateKey.algorithm); + assert.strictEqual(privateKey.usages, privateKey.usages); + assert.strictEqual(privateKey.extractable, extractable); + + if (extractable) { + const value = await subtle.exportKey('raw-seed', privateKey); + assert.deepStrictEqual(Buffer.from(value), seed); + } + + await assert.rejects( + subtle.importKey( + 'raw-seed', + seed.subarray(0, 30), + { name }, + extractable, + privateUsages), + { message: 'Invalid keyData' }); +} + +(async function() { + const tests = []; + for (const vector of testVectors) { + for (const extractable of [true, false]) { + tests.push(testImportSpki(vector, extractable)); + tests.push(testImportPkcs8(vector, extractable)); + tests.push(testImportPkcs8SeedOnly(vector, extractable)); + tests.push(testImportPkcs8PrivOnly(vector, extractable)); + tests.push(testImportRawSeed(vector, extractable)); + tests.push(testImportRawPublic(vector, extractable)); + } + } + await Promise.all(tests); +})().then(common.mustCall()); + +(async function() { + const alg = 'ML-KEM-512'; + const pub = keyData[alg].spki.subarray(-keyData[alg].pub_len); + await assert.rejects(subtle.importKey('raw', pub, alg, false, []), { + name: 'NotSupportedError', + message: 'Unable to import ML-KEM-512 using raw format', + }); +})().then(common.mustCall()); diff --git a/test/parallel/test-webcrypto-export-import-rsa.js b/test/parallel/test-webcrypto-export-import-rsa.js index f1bdaeed4dee8e..d3af8ec6c3adb9 100644 --- a/test/parallel/test-webcrypto-export-import-rsa.js +++ b/test/parallel/test-webcrypto-export-import-rsa.js @@ -19,6 +19,16 @@ const hashes = [ 'SHA-512', ]; +if (!process.features.openssl_is_boringssl) { + hashes.push( + 'SHA3-256', + 'SHA3-384', + 'SHA3-512', + ); +} else { + common.printSkipMessage('Skipping unsupported SHA-3 test cases'); +} + const keyData = { 1024: { spki: Buffer.from( @@ -387,13 +397,13 @@ async function testImportJwk( let alg; switch (name) { case 'RSA-PSS': - alg = `PS${hash === 'SHA-1' ? 1 : hash.substring(4)}`; + alg = hash.startsWith('SHA-') ? `PS${hash === 'SHA-1' ? 1 : hash.substring(4)}` : undefined; break; case 'RSA-OAEP': - alg = `RSA-OAEP${hash === 'SHA-1' ? '' : hash.substring(3)}`; + alg = hash.startsWith('SHA-') ? `RSA-OAEP${hash === 'SHA-1' ? '' : hash.substring(3)}` : undefined; break; case 'RSASSA-PKCS1-v1_5': - alg = `RS${hash === 'SHA-1' ? 1 : hash.substring(4)}`; + alg = hash.startsWith('SHA-') ? `RS${hash === 'SHA-1' ? 1 : hash.substring(4)}` : undefined; break; } @@ -497,7 +507,7 @@ async function testImportJwk( { message: 'Invalid JWK "use" Parameter' }); } - { + if (alg) { await assert.rejects( subtle.importKey( 'jwk', @@ -516,7 +526,7 @@ async function testImportJwk( { message: 'JWK "alg" does not match the requested algorithm' }); } - { + if (!hash.startsWith('SHA3-')) { let invalidAlgHash = name === 'RSA-OAEP' ? name : name === 'RSA-PSS' ? 'PS' : 'RS'; switch (name) { case 'RSA-OAEP': @@ -547,7 +557,7 @@ async function testImportJwk( { message: 'JWK "alg" does not match the requested algorithm' }); } - { + if (!hash.startsWith('SHA3-')) { const invalidAlgType = name === 'RSA-PSS' ? `RS${hash.substring(4)}` : `PS${hash.substring(4)}`; await assert.rejects( subtle.importKey( diff --git a/test/parallel/test-webcrypto-export-import.js b/test/parallel/test-webcrypto-export-import.js index bd0cd056d46741..a93afadcf914cc 100644 --- a/test/parallel/test-webcrypto-export-import.js +++ b/test/parallel/test-webcrypto-export-import.js @@ -1,12 +1,14 @@ 'use strict'; const common = require('../common'); +const fixtures = require('../common/fixtures'); if (!common.hasCrypto) common.skip('missing crypto'); const assert = require('assert'); const { subtle } = globalThis.crypto; +const { createPrivateKey, createPublicKey, createSecretKey } = require('crypto'); { async function test() { @@ -291,3 +293,41 @@ const { subtle } = globalThis.crypto; test().then(common.mustCall()); } + +// SHA-3 hashes and JWK "alg" +if (!process.features.openssl_is_boringssl) { + const rsa = fixtures.readKey('rsa_private_2048.pem'); + const privateKey = createPrivateKey(rsa); + const publicKey = createPublicKey(privateKey); + + async function test(keyObject, algorithm, usages) { + const key = keyObject.toCryptoKey(algorithm, true, usages); + const jwk = await subtle.exportKey('jwk', key); + assert.strictEqual(jwk.alg, undefined); + } + + for (const hash of ['SHA3-256', 'SHA3-384', 'SHA3-512']) { + for (const name of ['RSA-OAEP', 'RSA-PSS', 'RSASSA-PKCS1-v1_5']) { + test(publicKey, { name, hash }, []).then(common.mustCall()); + test(privateKey, { name, hash }, [name === 'RSA-OAEP' ? 'unwrapKey' : 'sign']).then(common.mustCall()); + } + + test(createSecretKey(Buffer.alloc(32)), { name: 'HMAC', hash }, ['sign']); + } + + { + const jwk = createSecretKey(Buffer.alloc(16)).export({ format: 'jwk' }); + // This is rejected for SHA-2 but ignored for SHA-3 + // Otherwise, if the name attribute of hash is defined in another applicable specification: + // Perform any key import steps defined by other applicable specifications, passing format, + // jwk and hash and obtaining hash. + jwk.alg = 'HS3-256'; + + assert.rejects(subtle.importKey('jwk', jwk, { name: 'HMAC', hash: 'SHA-256' }, false, ['sign', 'verify']), { + name: 'DataError', + message: 'JWK "alg" does not match the requested algorithm', + }).then(common.mustCall()); + + subtle.importKey('jwk', jwk, { name: 'HMAC', hash: 'SHA3-256' }, false, ['sign', 'verify']).then(common.mustCall()); + } +} diff --git a/test/parallel/test-webcrypto-get-public-key.mjs b/test/parallel/test-webcrypto-get-public-key.mjs new file mode 100644 index 00000000000000..9764aabd0a887e --- /dev/null +++ b/test/parallel/test-webcrypto-get-public-key.mjs @@ -0,0 +1,56 @@ +import * as common from '../common/index.mjs'; + +if (!common.hasCrypto) common.skip('missing crypto'); + +import * as assert from 'node:assert'; +const { subtle } = globalThis.crypto; + +const RSA_KEY_GEN = { + modulusLength: 2048, + publicExponent: new Uint8Array([1, 0, 1]), + hash: 'SHA-256', +}; + +const publicUsages = { + 'ECDH': [], + 'ECDSA': ['verify'], + 'Ed25519': ['verify'], + 'RSA-OAEP': ['encrypt', 'wrapKey'], + 'RSA-PSS': ['verify'], + 'RSASSA-PKCS1-v1_5': ['verify'], + 'X25519': [], +}; + +for await (const { privateKey } of [ + subtle.generateKey({ name: 'ECDH', namedCurve: 'P-256' }, false, ['deriveBits']), + subtle.generateKey({ name: 'ECDSA', namedCurve: 'P-256' }, false, ['sign']), + subtle.generateKey('Ed25519', false, ['sign']), + subtle.generateKey({ name: 'RSA-OAEP', ...RSA_KEY_GEN }, false, ['decrypt', 'unwrapKey']), + subtle.generateKey({ name: 'RSA-PSS', ...RSA_KEY_GEN }, false, ['sign']), + subtle.generateKey({ name: 'RSASSA-PKCS1-v1_5', ...RSA_KEY_GEN }, false, ['sign']), + subtle.generateKey('X25519', false, ['deriveBits']), +]) { + const { name } = privateKey.algorithm; + const usages = publicUsages[name]; + const publicKey = await subtle.getPublicKey(privateKey, usages); + assert.deepStrictEqual(publicKey.algorithm, privateKey.algorithm); + assert.strictEqual(publicKey.type, 'public'); + assert.strictEqual(publicKey.extractable, true); + + await assert.rejects(() => subtle.getPublicKey(privateKey, ['deriveBits']), { + name: 'SyntaxError', + message: /Unsupported key usage/ + }); + + await assert.rejects(() => subtle.getPublicKey(publicKey, publicKey.usages), { + name: 'InvalidAccessError', + message: 'key must be a private key' + }); +} + +const secretKey = await subtle.generateKey( + { name: 'AES-CBC', length: 128 }, true, ['encrypt', 'decrypt']); +await assert.rejects(() => subtle.getPublicKey(secretKey, ['encrypt', 'decrypt']), { + name: 'NotSupportedError', + message: 'key must be a private key' +}); diff --git a/test/parallel/test-webcrypto-internal-slots.mjs b/test/parallel/test-webcrypto-internal-slots.mjs new file mode 100644 index 00000000000000..9b824167ba4553 --- /dev/null +++ b/test/parallel/test-webcrypto-internal-slots.mjs @@ -0,0 +1,21 @@ +import * as common from '../common/index.mjs'; + +if (!common.hasCrypto) + common.skip('missing crypto'); + +import * as assert from 'node:assert'; +import * as util from 'node:util'; + +const { subtle } = globalThis.crypto; + +const kp = await subtle.generateKey('Ed25519', true, ['sign', 'verify']); +assert.notStrictEqual(kp.publicKey.algorithm, kp.privateKey.algorithm); +assert.notStrictEqual(kp.publicKey.usages, kp.privateKey.usages); +kp.publicKey.algorithm.name = 'ed25519'; +assert.strictEqual(kp.publicKey.algorithm.name, 'ed25519'); +kp.publicKey.usages.push('foo'); +assert.ok(kp.publicKey.usages.includes('foo')); +assert.ok(util.inspect(kp.publicKey).includes("algorithm: { name: 'Ed25519' }")); +assert.ok(util.inspect(kp.publicKey).includes("usages: [ 'verify' ]")); + +await subtle.sign('Ed25519', kp.privateKey, Buffer.alloc(32)); diff --git a/test/parallel/test-webcrypto-keygen.js b/test/parallel/test-webcrypto-keygen.js index a60463bdb5f139..604caa4b9f6654 100644 --- a/test/parallel/test-webcrypto-keygen.js +++ b/test/parallel/test-webcrypto-keygen.js @@ -6,6 +6,8 @@ const common = require('../common'); if (!common.hasCrypto) common.skip('missing crypto'); +const { hasOpenSSL } = require('../common/crypto'); + const assert = require('assert'); const { types: { isCryptoKey } } = require('util'); const { @@ -57,14 +59,6 @@ const vectors = { 'unwrapKey', ], }, - 'AES-KW': { - algorithm: { length: 256 }, - result: 'CryptoKey', - usages: [ - 'wrapKey', - 'unwrapKey', - ], - }, 'HMAC': { algorithm: { length: 256, hash: 'SHA-256' }, result: 'CryptoKey', @@ -134,13 +128,6 @@ const vectors = { 'verify', ], }, - 'Ed448': { - result: 'CryptoKeyPair', - usages: [ - 'sign', - 'verify', - ], - }, 'X25519': { result: 'CryptoKeyPair', usages: [ @@ -148,14 +135,68 @@ const vectors = { 'deriveBits', ], }, - 'X448': { +}; + +if (!process.features.openssl_is_boringssl) { + vectors.Ed448 = { + result: 'CryptoKeyPair', + usages: [ + 'sign', + 'verify', + ], + }; + vectors.X448 = { result: 'CryptoKeyPair', usages: [ 'deriveKey', 'deriveBits', ], - }, -}; + }; + vectors['AES-KW'] = { + algorithm: { length: 256 }, + result: 'CryptoKey', + usages: [ + 'wrapKey', + 'unwrapKey', + ], + }; + vectors['ChaCha20-Poly1305'] = { + result: 'CryptoKey', + usages: [ + 'encrypt', + 'decrypt', + 'wrapKey', + 'unwrapKey', + ], + }; +} else { + common.printSkipMessage('Skipping unsupported test cases'); +} + +if (hasOpenSSL(3)) { + vectors['AES-OCB'] = { + algorithm: { length: 256 }, + result: 'CryptoKey', + usages: [ + 'encrypt', + 'decrypt', + 'wrapKey', + 'unwrapKey', + ], + }; +} + +if (hasOpenSSL(3, 5)) { + for (const name of ['ML-DSA-44', 'ML-DSA-65', 'ML-DSA-87']) { + vectors[name] = { + result: 'CryptoKeyPair', + usages: [ + 'sign', + 'verify', + ], + }; + } +} // Test invalid algorithms { @@ -163,10 +204,7 @@ const vectors = { return assert.rejects( // The extractable and usages values are invalid here also, // but the unrecognized algorithm name should be caught first. - subtle.generateKey(algorithm, 7, []), { - message: /Unrecognized algorithm name/, - name: 'NotSupportedError', - }); + subtle.generateKey(algorithm, 7, []), { name: 'NotSupportedError' }); } const tests = [ @@ -286,14 +324,20 @@ const vectors = { assert.deepStrictEqual(privateKey.usages, privateUsages); assert.strictEqual(publicKey.algorithm.name, name); assert.strictEqual(publicKey.algorithm.modulusLength, modulusLength); - assert.deepStrictEqual(publicKey.algorithm.publicExponent, publicExponent); + assert(publicKey.algorithm.publicExponent instanceof Uint8Array); + assert.notStrictEqual(publicKey.algorithm.publicExponent, publicExponent); + assert(!Buffer.isBuffer(publicKey.algorithm.publicExponent)); + assert.deepStrictEqual(publicKey.algorithm.publicExponent, new Uint8Array(publicExponent)); assert.strictEqual( KeyObject.from(publicKey).asymmetricKeyDetails.publicExponent, bigIntArrayToUnsignedBigInt(publicExponent)); assert.strictEqual(publicKey.algorithm.hash.name, hash); assert.strictEqual(privateKey.algorithm.name, name); assert.strictEqual(privateKey.algorithm.modulusLength, modulusLength); - assert.deepStrictEqual(privateKey.algorithm.publicExponent, publicExponent); + assert(privateKey.algorithm.publicExponent instanceof Uint8Array); + assert.notStrictEqual(privateKey.algorithm.publicExponent, publicExponent); + assert(!Buffer.isBuffer(privateKey.algorithm.publicExponent)); + assert.deepStrictEqual(privateKey.algorithm.publicExponent, new Uint8Array(publicExponent)); assert.strictEqual( KeyObject.from(privateKey).asymmetricKeyDetails.publicExponent, bigIntArrayToUnsignedBigInt(publicExponent)); @@ -359,10 +403,7 @@ const vectors = { modulusLength, publicExponent, hash - }, true, usages), { - message: /Unrecognized algorithm name/, - name: 'NotSupportedError', - }); + }, true, usages), { name: 'NotSupportedError' }); })); await Promise.all(['', {}, 1, false].map((usages) => { @@ -393,28 +434,36 @@ const vectors = { 'RSASSA-PKCS1-v1_5', 1024, Buffer.from([1, 0, 1]), - 'SHA-256', + 'SHA-1', ['sign'], ['verify'], ], [ 'RSA-PSS', - 2048, + 1024, Buffer.from([1, 0, 1]), - 'SHA-512', + 'SHA-256', ['sign'], ['verify'], ], - [ - 'RSA-OAEP', - 1024, - Buffer.from([3]), - 'SHA-384', - ['decrypt', 'unwrapKey'], - ['encrypt', 'wrapKey'], - ], ]; + + if (!process.features.openssl_is_boringssl) { + kTests.push( + [ + 'RSA-OAEP', + 1024, + Buffer.from([3]), + 'SHA3-256', + ['decrypt', 'unwrapKey'], + ['encrypt', 'wrapKey'], + ], + ); + } else { + common.printSkipMessage('Skipping unsupported SHA-3 test case'); + } + const tests = kTests.map((args) => test(...args)); Promise.all(tests).then(common.mustCall()); @@ -547,10 +596,17 @@ const vectors = { [ 'AES-CBC', 256, ['encrypt', 'decrypt']], [ 'AES-GCM', 128, ['encrypt', 'decrypt']], [ 'AES-GCM', 256, ['encrypt', 'decrypt']], - [ 'AES-KW', 128, ['wrapKey', 'unwrapKey']], - [ 'AES-KW', 256, ['wrapKey', 'unwrapKey']], ]; + if (!process.features.openssl_is_boringssl) { + kTests.push( + [ 'AES-KW', 128, ['wrapKey', 'unwrapKey']], + [ 'AES-KW', 256, ['wrapKey', 'unwrapKey']], + ); + } else { + common.printSkipMessage('Skipping unsupported AES-KW test cases'); + } + const tests = Promise.all(kTests.map((args) => test(...args))); tests.then(common.mustCall()); @@ -590,21 +646,35 @@ const vectors = { [1, false, null].forEach(async (hash) => { await assert.rejects( subtle.generateKey({ name: 'HMAC', length, hash }, true, usages), { - message: /Unrecognized algorithm name/, name: 'NotSupportedError', }); }); } const kTests = [ - [ undefined, 'SHA-1', ['sign', 'verify']], - [ undefined, 'SHA-256', ['sign', 'verify']], - [ undefined, 'SHA-384', ['sign', 'verify']], - [ undefined, 'SHA-512', ['sign', 'verify']], - [ 128, 'SHA-256', ['sign', 'verify']], - [ 1024, 'SHA-512', ['sign', 'verify']], + [undefined, 'SHA-1', ['sign', 'verify']], + [undefined, 'SHA-256', ['sign', 'verify']], + [undefined, 'SHA-384', ['sign', 'verify']], + [undefined, 'SHA-512', ['sign', 'verify']], + [128, 'SHA-256', ['sign', 'verify']], + [1024, 'SHA-512', ['sign', 'verify']], ]; + if (!process.features.openssl_is_boringssl) { + kTests.push( + [256, 'SHA3-256', ['sign', 'verify']], + [384, 'SHA3-384', ['sign', 'verify']], + [512, 'SHA3-512', ['sign', 'verify']], + // This interaction is not defined for now. + // https://github.com/WICG/webcrypto-modern-algos/issues/23 + // [undefined, 'SHA3-256', ['sign', 'verify']], + // [undefined, 'SHA3-384', ['sign', 'verify']], + // [undefined, 'SHA3-512', ['sign', 'verify']], + ); + } else { + common.printSkipMessage('Skipping unsupported SHA-3 test cases'); + } + const tests = Promise.all(kTests.map((args) => test(...args))); tests.then(common.mustCall()); @@ -662,24 +732,119 @@ assert.throws(() => new CryptoKey(), { code: 'ERR_ILLEGAL_CONSTRUCTOR' }); ['sign'], ['verify'], ], - [ - 'Ed448', - ['sign'], - ['verify'], - ], [ 'X25519', ['deriveKey', 'deriveBits'], [], ], - [ - 'X448', - ['deriveKey', 'deriveBits'], - [], - ], ]; + if (!process.features.openssl_is_boringssl) { + kTests.push( + [ + 'Ed448', + ['sign'], + ['verify'], + ], + [ + 'X448', + ['deriveKey', 'deriveBits'], + [], + ], + ); + } else { + common.printSkipMessage('Skipping unsupported Curve448 test cases'); + } + const tests = kTests.map((args) => test(...args)); Promise.all(tests).then(common.mustCall()); } + +// Test ML-DSA Key Generation +if (hasOpenSSL(3, 5)) { + async function test( + name, + privateUsages, + publicUsages = privateUsages) { + + let usages = privateUsages; + if (publicUsages !== privateUsages) + usages = usages.concat(publicUsages); + + const { publicKey, privateKey } = await subtle.generateKey({ + name, + }, true, usages); + + assert(publicKey); + assert(privateKey); + assert(isCryptoKey(publicKey)); + assert(isCryptoKey(privateKey)); + + assert.strictEqual(publicKey.type, 'public'); + assert.strictEqual(privateKey.type, 'private'); + assert.strictEqual(publicKey.toString(), '[object CryptoKey]'); + assert.strictEqual(privateKey.toString(), '[object CryptoKey]'); + assert.strictEqual(publicKey.extractable, true); + assert.strictEqual(privateKey.extractable, true); + assert.deepStrictEqual(publicKey.usages, publicUsages); + assert.deepStrictEqual(privateKey.usages, privateUsages); + assert.strictEqual(publicKey.algorithm.name, name); + assert.strictEqual(privateKey.algorithm.name, name); + assert.strictEqual(privateKey.algorithm, privateKey.algorithm); + assert.strictEqual(privateKey.usages, privateKey.usages); + assert.strictEqual(publicKey.algorithm, publicKey.algorithm); + assert.strictEqual(publicKey.usages, publicKey.usages); + } + + const kTests = ['ML-DSA-44', 'ML-DSA-65', 'ML-DSA-87']; + + const tests = kTests.map((name) => test(name, ['sign'], ['verify'])); + + Promise.all(tests).then(common.mustCall()); +} + +// Test ML-KEM Key Generation +if (hasOpenSSL(3, 5)) { + async function test( + name, + privateUsages, + publicUsages = privateUsages) { + + let usages = privateUsages; + if (publicUsages !== privateUsages) + usages = usages.concat(publicUsages); + + const { publicKey, privateKey } = await subtle.generateKey({ + name, + }, true, usages); + + assert(publicKey); + assert(privateKey); + assert(isCryptoKey(publicKey)); + assert(isCryptoKey(privateKey)); + + assert.strictEqual(publicKey.type, 'public'); + assert.strictEqual(privateKey.type, 'private'); + assert.strictEqual(publicKey.toString(), '[object CryptoKey]'); + assert.strictEqual(privateKey.toString(), '[object CryptoKey]'); + assert.strictEqual(publicKey.extractable, true); + assert.strictEqual(privateKey.extractable, true); + assert.deepStrictEqual(publicKey.usages, publicUsages); + assert.deepStrictEqual(privateKey.usages, privateUsages); + assert.strictEqual(publicKey.algorithm.name, name); + assert.strictEqual(privateKey.algorithm.name, name); + assert.strictEqual(privateKey.algorithm, privateKey.algorithm); + assert.strictEqual(privateKey.usages, privateKey.usages); + assert.strictEqual(publicKey.algorithm, publicKey.algorithm); + assert.strictEqual(publicKey.usages, publicKey.usages); + } + + const kTests = ['ML-KEM-512', 'ML-KEM-768', 'ML-KEM-1024']; + + const tests = kTests.map((name) => test(name, + ['decapsulateBits', 'decapsulateKey'], + ['encapsulateBits', 'encapsulateKey'])); + + Promise.all(tests).then(common.mustCall()); +} diff --git a/test/parallel/test-webcrypto-sign-verify-eddsa.js b/test/parallel/test-webcrypto-sign-verify-eddsa.js index 75d73a49ddea36..87f562043ad2ca 100644 --- a/test/parallel/test-webcrypto-sign-verify-eddsa.js +++ b/test/parallel/test-webcrypto-sign-verify-eddsa.js @@ -219,7 +219,7 @@ async function testSign({ name, })().then(common.mustCall()); // Ed448 context -{ +if (!process.features.openssl_is_boringssl) { const vector = vectors.find(({ name }) => name === 'Ed448'); Promise.all([ subtle.importKey( @@ -241,10 +241,12 @@ async function testSign({ name, await subtle.verify({ name: 'Ed448', context: Buffer.alloc(0) }, publicKey, sig, vector.data), true); await assert.rejects(subtle.sign({ name: 'Ed448', context: Buffer.alloc(1) }, privateKey, vector.data), { - message: /Non zero-length context is not supported/ + message: /Non zero-length Ed448Params\.context is not supported/ }); await assert.rejects(subtle.verify({ name: 'Ed448', context: Buffer.alloc(1) }, publicKey, sig, vector.data), { - message: /Non zero-length context is not supported/ + message: /Non zero-length Ed448Params\.context is not supported/ }); }).then(common.mustCall()); +} else { + common.printSkipMessage('Skipping unsupported Ed448 test case'); } diff --git a/test/parallel/test-webcrypto-sign-verify-ml-dsa.js b/test/parallel/test-webcrypto-sign-verify-ml-dsa.js new file mode 100644 index 00000000000000..5bd21f9c2735b4 --- /dev/null +++ b/test/parallel/test-webcrypto-sign-verify-ml-dsa.js @@ -0,0 +1,228 @@ +'use strict'; + +const common = require('../common'); + +if (!common.hasCrypto) + common.skip('missing crypto'); + +const { hasOpenSSL } = require('../common/crypto'); + +if (!hasOpenSSL(3, 5)) + common.skip('requires OpenSSL >= 3.5'); + +const assert = require('assert'); +const crypto = require('crypto'); +const { subtle } = globalThis.crypto; + +const vectors = require('../fixtures/crypto/ml-dsa')(); + +async function testVerify({ name, + publicKeyPem, + privateKeyPem, + signature, + data }) { + const [ + publicKey, + noVerifyPublicKey, + privateKey, + hmacKey, + rsaKeys, + ecKeys, + ] = await Promise.all([ + crypto.createPublicKey(publicKeyPem) + .toCryptoKey(name, false, ['verify']), + crypto.createPublicKey(publicKeyPem) + .toCryptoKey(name, false, [ /* No usages */ ]), + crypto.createPrivateKey(privateKeyPem) + .toCryptoKey(name, false, ['sign']), + subtle.generateKey( + { name: 'HMAC', hash: 'SHA-256' }, + false, + ['sign']), + subtle.generateKey( + { + name: 'RSA-PSS', + modulusLength: 1024, + publicExponent: new Uint8Array([1, 0, 1]), + hash: 'SHA-256', + }, + false, + ['sign']), + subtle.generateKey( + { + name: 'ECDSA', + namedCurve: 'P-256' + }, + false, + ['sign']), + ]); + + assert(await subtle.verify({ name }, publicKey, signature, data)); + + // Test verification with altered buffers + const copy = Buffer.from(data); + const sigcopy = Buffer.from(signature); + const p = subtle.verify({ name }, publicKey, sigcopy, copy); + copy[0] = 255 - copy[0]; + sigcopy[0] = 255 - sigcopy[0]; + assert(await p); + + // Test failure when using wrong key + await assert.rejects( + subtle.verify({ name }, privateKey, signature, data), { + message: /Unable to use this key to verify/ + }); + + await assert.rejects( + subtle.verify({ name }, noVerifyPublicKey, signature, data), { + message: /Unable to use this key to verify/ + }); + + // Test failure when using the wrong algorithms + await assert.rejects( + subtle.verify({ name }, hmacKey, signature, data), { + message: /Unable to use this key to verify/ + }); + + await assert.rejects( + subtle.verify({ name }, rsaKeys.publicKey, signature, data), { + message: /Unable to use this key to verify/ + }); + + await assert.rejects( + subtle.verify({ name }, ecKeys.publicKey, signature, data), { + message: /Unable to use this key to verify/ + }); + + // Test failure when signature is altered + { + const copy = Buffer.from(signature); + copy[0] = 255 - copy[0]; + assert(!(await subtle.verify( + { name }, + publicKey, + copy, + data))); + assert(!(await subtle.verify( + { name }, + publicKey, + copy.slice(1), + data))); + } + + // Test failure when data is altered + { + const copy = Buffer.from(data); + copy[0] = 255 - copy[0]; + assert(!(await subtle.verify({ name }, publicKey, signature, copy))); + } +} + +async function testSign({ name, + publicKeyPem, + privateKeyPem, + signature, + data }) { + const [ + publicKey, + privateKey, + hmacKey, + rsaKeys, + ecKeys, + ] = await Promise.all([ + crypto.createPublicKey(publicKeyPem) + .toCryptoKey(name, false, ['verify']), + crypto.createPrivateKey(privateKeyPem) + .toCryptoKey(name, false, ['sign']), + subtle.generateKey( + { name: 'HMAC', hash: 'SHA-256' }, + false, + ['sign']), + subtle.generateKey( + { + name: 'RSA-PSS', + modulusLength: 1024, + publicExponent: new Uint8Array([1, 0, 1]), + hash: 'SHA-256', + }, + false, + ['sign']), + subtle.generateKey( + { + name: 'ECDSA', + namedCurve: 'P-256' + }, + false, + ['sign']), + ]); + + { + const sig = await subtle.sign({ name }, privateKey, data); + assert.strictEqual(sig.byteLength, signature.byteLength); + assert(await subtle.verify({ name }, publicKey, sig, data)); + } + + { + const copy = Buffer.from(data); + const p = subtle.sign({ name }, privateKey, copy); + copy[0] = 255 - copy[0]; + const sig = await p; + assert(await subtle.verify({ name }, publicKey, sig, data)); + } + + // Test failure when using wrong key + await assert.rejects( + subtle.sign({ name }, publicKey, data), { + message: /Unable to use this key to sign/ + }); + + // Test failure when using the wrong algorithms + await assert.rejects( + subtle.sign({ name }, hmacKey, data), { + message: /Unable to use this key to sign/ + }); + + await assert.rejects( + subtle.sign({ name }, rsaKeys.privateKey, data), { + message: /Unable to use this key to sign/ + }); + + await assert.rejects( + subtle.sign({ name }, ecKeys.privateKey, data), { + message: /Unable to use this key to sign/ + }); +} + +(async function() { + const variations = []; + + vectors.forEach((vector) => { + variations.push(testVerify(vector)); + variations.push(testSign(vector)); + }); + + await Promise.all(variations); +})().then(common.mustCall()); + +// ContextParams context not supported +{ + const vector = vectors[0]; + const name = vector.name; + const publicKey = crypto.createPublicKey(vector.publicKeyPem) + .toCryptoKey(vector.name, false, ['verify']); + const privateKey = crypto.createPrivateKey(vector.privateKeyPem) + .toCryptoKey(vector.name, false, ['sign']); + + (async () => { + const sig = await subtle.sign({ name, context: Buffer.alloc(0) }, privateKey, vector.data); + assert.strictEqual( + await subtle.verify({ name, context: Buffer.alloc(0) }, publicKey, sig, vector.data), true); + + await assert.rejects(subtle.sign({ name, context: Buffer.alloc(1) }, privateKey, vector.data), { + message: /Non zero-length ContextParams\.context is not supported/ + }); + await assert.rejects(subtle.verify({ name, context: Buffer.alloc(1) }, publicKey, sig, vector.data), { + message: /Non zero-length ContextParams\.context is not supported/ + }); + })().then(common.mustCall()); +} diff --git a/test/parallel/test-webcrypto-sign-verify-rsa.js b/test/parallel/test-webcrypto-sign-verify-rsa.js index ef9f6e8bd45d72..7e90388cc4c270 100644 --- a/test/parallel/test-webcrypto-sign-verify-rsa.js +++ b/test/parallel/test-webcrypto-sign-verify-rsa.js @@ -236,8 +236,18 @@ async function testSaltLength(keyLength, hash, hLen) { }); for (const keyLength of [1024, 2048]) { - for (const [hash, hLen] of [['SHA-1', 20], ['SHA-256', 32], ['SHA-384', 48], ['SHA-512', 64]]) { - variations.push(testSaltLength(keyLength, hash, hLen)); + for (const [hash, hLen] of [ + ['SHA-1', 20], + ['SHA-256', 32], + ['SHA-384', 48], + ['SHA-512', 64], + ['SHA3-256', 32], + ['SHA3-384', 48], + ['SHA3-512', 64], + ]) { + if (hash.startsWith('SHA-3') && !process.features.openssl_is_boringssl) { + variations.push(testSaltLength(keyLength, hash, hLen)); + } } } diff --git a/test/parallel/test-webcrypto-sign-verify.js b/test/parallel/test-webcrypto-sign-verify.js index 1b2b40152f88d2..a43123ce294d27 100644 --- a/test/parallel/test-webcrypto-sign-verify.js +++ b/test/parallel/test-webcrypto-sign-verify.js @@ -5,6 +5,8 @@ const common = require('../common'); if (!common.hasCrypto) common.skip('missing crypto'); +const { hasOpenSSL } = require('../common/crypto'); + const assert = require('assert'); const { subtle } = globalThis.crypto; @@ -121,13 +123,11 @@ const { subtle } = globalThis.crypto; name: 'Ed25519', }, publicKey, signature, ec.encode(data))); } - if (!process.features.openssl_is_boringssl) { - test('hello world').then(common.mustCall()); - } + test('hello world').then(common.mustCall()); } // Test Sign/Verify Ed448 -{ +if (!process.features.openssl_is_boringssl) { async function test(data) { const ec = new TextEncoder(); const { publicKey, privateKey } = await subtle.generateKey({ @@ -143,7 +143,29 @@ const { subtle } = globalThis.crypto; }, publicKey, signature, ec.encode(data))); } - if (!process.features.openssl_is_boringssl) { - test('hello world').then(common.mustCall()); + test('hello world').then(common.mustCall()); +} else { + common.printSkipMessage('Skipping unsupported Ed448 test case'); +} + +// Test Sign/Verify ML-DSA +if (hasOpenSSL(3, 5)) { + async function test(name, data) { + const ec = new TextEncoder(); + const { publicKey, privateKey } = await subtle.generateKey({ + name, + }, true, ['sign', 'verify']); + + const signature = await subtle.sign({ + name, + }, privateKey, ec.encode(data)); + + assert(await subtle.verify({ + name, + }, publicKey, signature, ec.encode(data))); } + + test('ML-DSA-44', 'hello world').then(common.mustCall()); + test('ML-DSA-65', 'hello world').then(common.mustCall()); + test('ML-DSA-87', 'hello world').then(common.mustCall()); } diff --git a/test/parallel/test-webcrypto-supports.mjs b/test/parallel/test-webcrypto-supports.mjs new file mode 100644 index 00000000000000..c3c976f730e065 --- /dev/null +++ b/test/parallel/test-webcrypto-supports.mjs @@ -0,0 +1,107 @@ +import * as common from '../common/index.mjs'; + +if (!common.hasCrypto) + common.skip('missing crypto'); + +import * as assert from 'node:assert'; +const { SubtleCrypto } = globalThis; + +const sources = [ + import('../fixtures/webcrypto/supports-level-2.mjs'), + import('../fixtures/webcrypto/supports-secure-curves.mjs'), + import('../fixtures/webcrypto/supports-modern-algorithms.mjs'), + import('../fixtures/webcrypto/supports-sha3.mjs'), +]; + +const vectors = {}; + +for await (const mod of sources) { + for (const op of Object.keys(mod.vectors)) { + vectors[op] ||= []; + vectors[op] = vectors[op].concat(mod.vectors[op]); + } +} + +vectors.verify = vectors.sign; +vectors.decrypt = vectors.encrypt; +vectors.decapsulateBits = vectors.encapsulateBits; + +for (const enc of vectors.encrypt) { + for (const exp of vectors.exportKey) { + vectors.wrapKey.push([enc[0] && exp[0], enc[1], exp[1]]); + } +} + +for (const dec of vectors.decrypt) { + for (const imp of vectors.importKey) { + vectors.unwrapKey.push([dec[0] && imp[0], dec[1], imp[1]]); + } +} + +for (const exportKey of vectors.exportKey) { + if (!exportKey[0]) vectors.getPublicKey.push(exportKey); +} + +function supportsRawSecret(alg) { + if (typeof alg === 'string') { + alg = alg.toLowerCase(); + return alg.startsWith('aes') || + alg.startsWith('argon2') || + alg.startsWith('kmac') || + alg === 'chacha20-poly1305' || + alg === 'pbkdf2' || + alg === 'hkdf' || + alg === 'hmac'; + } + + if (typeof alg?.name === 'string') { + return supportsRawSecret(alg.name); + } + + return false; +} + +function supports256RawSecret(alg) { + if (!supportsRawSecret(alg)) return false; + switch (alg?.name?.toLowerCase?.()) { + case 'hmac': + case 'kmac128': + case 'kmac256': + return typeof alg.length !== 'number' || alg.length === 256; + default: + return true; + } +} + +for (const encap of vectors.encapsulateBits) { + for (const imp of vectors.importKey) { + if (supports256RawSecret(imp[1])) { + vectors.encapsulateKey.push([encap[0] && imp[0], encap[1], imp[1]]); + } else { + vectors.encapsulateKey.push([false, encap[1], imp[1]]); + } + } +} + +for (const decap of vectors.decapsulateBits) { + for (const imp of vectors.importKey) { + if (supports256RawSecret(imp[1])) { + vectors.decapsulateKey.push([decap[0] && imp[0], decap[1], imp[1]]); + } else { + vectors.decapsulateKey.push([false, decap[1], imp[1]]); + } + } +} + +for (const operation of Object.keys(vectors)) { + for (const [expectation, ...args] of vectors[operation]) { + assert.strictEqual( + SubtleCrypto.supports(operation, ...args), + expectation, + new Error( + `expected ${expectation}, got ${!expectation}`, + { cause: { operation, args } } + ) + ); + } +} diff --git a/test/parallel/test-webcrypto-webidl.js b/test/parallel/test-webcrypto-webidl.js index e1675fe5c4e558..107a28adb766be 100644 --- a/test/parallel/test-webcrypto-webidl.js +++ b/test/parallel/test-webcrypto-webidl.js @@ -462,19 +462,19 @@ const opts = { prefix, context }; }); } -// AesGcmParams +// AeadParams { for (const good of [ { name: 'AES-GCM', iv: Buffer.alloc(0) }, { name: 'AES-GCM', iv: Buffer.alloc(0), tagLength: 64 }, { name: 'AES-GCM', iv: Buffer.alloc(0), tagLength: 64, additionalData: Buffer.alloc(0) }, ]) { - assert.deepStrictEqual(converters.AesGcmParams({ ...good, filtered: 'out' }, opts), good); + assert.deepStrictEqual(converters.AeadParams({ ...good, filtered: 'out' }, opts), good); - assert.throws(() => converters.AesGcmParams({ ...good, iv: undefined }, opts), { + assert.throws(() => converters.AeadParams({ ...good, iv: undefined }, opts), { name: 'TypeError', code: 'ERR_MISSING_OPTION', - message: `${prefix}: ${context} can not be converted to 'AesGcmParams' because 'iv' is required in 'AesGcmParams'.`, + message: `${prefix}: ${context} can not be converted to 'AeadParams' because 'iv' is required in 'AeadParams'.`, }); } } diff --git a/test/parallel/test-webcrypto-wrap-unwrap.js b/test/parallel/test-webcrypto-wrap-unwrap.js index d1ca571af4be71..bd788ec4ed8828 100644 --- a/test/parallel/test-webcrypto-wrap-unwrap.js +++ b/test/parallel/test-webcrypto-wrap-unwrap.js @@ -5,6 +5,8 @@ const common = require('../common'); if (!common.hasCrypto) common.skip('missing crypto'); +const { hasOpenSSL } = require('../common/crypto'); + const assert = require('assert'); const { subtle } = globalThis.crypto; @@ -37,12 +39,37 @@ const kWrappingData = { }, pair: false }, - 'AES-KW': { +}; + +if (!process.features.openssl_is_boringssl) { + kWrappingData['AES-KW'] = { generate: { length: 128 }, wrap: { }, pair: false - } -}; + }; + kWrappingData['ChaCha20-Poly1305'] = { + wrap: { + iv: new Uint8Array(12), + additionalData: new Uint8Array(16), + tagLength: 128 + }, + pair: false + }; +} else { + common.printSkipMessage('Skipping unsupported AES-KW test case'); +} + +if (hasOpenSSL(3)) { + kWrappingData['AES-OCB'] = { + generate: { length: 128 }, + wrap: { + iv: new Uint8Array(15), + additionalData: new Uint8Array(16), + tagLength: 128 + }, + pair: false + }; +} function generateWrappingKeys() { return Promise.all(Object.keys(kWrappingData).map(async (name) => { @@ -121,14 +148,6 @@ async function generateKeysToWrap() { publicUsages: ['verify'], pair: true, }, - { - algorithm: { - name: 'Ed448', - }, - privateUsages: ['sign'], - publicUsages: ['verify'], - pair: true, - }, { algorithm: { name: 'X25519', @@ -137,14 +156,6 @@ async function generateKeysToWrap() { publicUsages: [], pair: true, }, - { - algorithm: { - name: 'X448', - }, - privateUsages: ['deriveBits'], - publicUsages: [], - pair: true, - }, { algorithm: { name: 'AES-CTR', @@ -170,10 +181,9 @@ async function generateKeysToWrap() { }, { algorithm: { - name: 'AES-KW', - length: 128 + name: 'ChaCha20-Poly1305' }, - usages: ['wrapKey', 'unwrapKey'], + usages: ['encrypt', 'decrypt'], pair: false, }, { @@ -187,6 +197,53 @@ async function generateKeysToWrap() { }, ]; + if (!process.features.openssl_is_boringssl) { + parameters.push({ + algorithm: { + name: 'AES-KW', + length: 128 + }, + usages: ['wrapKey', 'unwrapKey'], + pair: false, + }); + } else { + common.printSkipMessage('Skipping unsupported AES-KW test case'); + } + + if (hasOpenSSL(3, 5)) { + for (const name of ['ML-DSA-44', 'ML-DSA-65', 'ML-DSA-87']) { + parameters.push({ + algorithm: { name }, + privateUsages: ['sign'], + publicUsages: ['verify'], + pair: true, + }); + } + } + + if (!process.features.openssl_is_boringssl) { + parameters.push( + { + algorithm: { + name: 'Ed448', + }, + privateUsages: ['sign'], + publicUsages: ['verify'], + pair: true, + }, + { + algorithm: { + name: 'X448', + }, + privateUsages: ['deriveBits'], + publicUsages: [], + pair: true, + }, + ); + } else { + common.printSkipMessage('Skipping unsupported Curve test cases'); + } + const allkeys = await Promise.all(parameters.map(async (params) => { const usages = 'usages' in params ? params.usages : @@ -220,10 +277,29 @@ async function generateKeysToWrap() { } function getFormats(key) { - switch (key.key.type) { - case 'secret': return ['raw', 'jwk']; - case 'public': return ['spki', 'jwk']; - case 'private': return ['pkcs8', 'jwk']; + switch (key.type) { + case 'secret': { + if (key.algorithm.name === 'ChaCha20-Poly1305') return ['raw-secret', 'jwk']; + return ['raw-secret', 'raw', 'jwk']; + }; + case 'public': { + switch (key.algorithm.name.slice(0, 2)) { + case 'EC': // ECDSA, ECDH + return ['spki', 'jwk', 'raw', 'raw-public']; + case 'ML': // ML-DSA + return ['jwk', 'raw-public']; + default: + return ['spki', 'jwk']; + } + } + case 'private': { + switch (key.algorithm.name.slice(0, 2)) { + case 'ML': // ML-DSA + return ['jwk', 'raw-seed']; + default: + return ['pkcs8', 'jwk']; + } + } } } @@ -285,7 +361,7 @@ function testWrapping(name, keys) { } = kWrappingData[name]; keys.forEach((key) => { - getFormats(key).forEach((format) => { + getFormats(key.key).forEach((format) => { variations.push(testWrap(wrappingKey, unwrappingKey, key, wrap, format)); }); }); diff --git a/test/parallel/test-whatwg-webstreams-compression.js b/test/parallel/test-whatwg-webstreams-compression.js index c144a0a2e3d43d..bf87696eed1b2f 100644 --- a/test/parallel/test-whatwg-webstreams-compression.js +++ b/test/parallel/test-whatwg-webstreams-compression.js @@ -41,7 +41,7 @@ async function test(format) { ]); } -Promise.all(['gzip', 'deflate', 'deflate-raw'].map((i) => test(i))).then(common.mustCall()); +Promise.all(['gzip', 'deflate', 'deflate-raw', 'brotli'].map((i) => test(i))).then(common.mustCall()); [1, 'hello', false, {}].forEach((i) => { assert.throws(() => new CompressionStream(i), { diff --git a/test/parallel/test-worker-name.js b/test/parallel/test-worker-name.js index 30f3710a826caf..c4676f446c424c 100644 --- a/test/parallel/test-worker-name.js +++ b/test/parallel/test-worker-name.js @@ -17,7 +17,7 @@ if (!isMainThread) { const assert = require('assert'); if (isMainThread) { - const name = 'Hello Thread'; + const name = 'Hello\0Thread'; const expectedTitle = `[worker 1] ${name}`; const worker = new Worker(fixtures.path('worker-name.js'), { name, diff --git a/test/pummel/test-webcrypto-derivebits-pbkdf2.js b/test/pummel/test-webcrypto-derivebits-pbkdf2.js index 28c6fe871c578b..5db210b899efea 100644 --- a/test/pummel/test-webcrypto-derivebits-pbkdf2.js +++ b/test/pummel/test-webcrypto-derivebits-pbkdf2.js @@ -483,7 +483,6 @@ async function testDeriveBitsBadHash( ...algorithm, hash: hash.substring(0, 3) + hash.substring(4), }, baseKeys[size], 256), { - message: /Unrecognized algorithm name/, name: 'NotSupportedError', }), assert.rejects( @@ -493,7 +492,6 @@ async function testDeriveBitsBadHash( hash: 'HKDF', }, baseKeys[size], 256), { - message: /Unrecognized algorithm name/, name: 'NotSupportedError', }), ]); @@ -571,10 +569,7 @@ async function testDeriveKeyBadHash( keyType, true, usages), - { - message: /Unrecognized algorithm name/, - name: 'NotSupportedError', - }), + { name: 'NotSupportedError' }), assert.rejects( subtle.deriveKey( { @@ -585,10 +580,7 @@ async function testDeriveKeyBadHash( keyType, true, usages), - { - message: /Unrecognized algorithm name/, - name: 'NotSupportedError', - }), + { name: 'NotSupportedError' }), ]); } diff --git a/test/report/test-report-worker.js b/test/report/test-report-worker.js index 26b28219c84088..f17e3986e97813 100644 --- a/test/report/test-report-worker.js +++ b/test/report/test-report-worker.js @@ -13,7 +13,7 @@ async function basic() { parentPort.once('message', () => { /* Wait for message to stop the Worker */ }); - `, { eval: true }); + `, { eval: true, name: 'hello' }); await once(w, 'online'); @@ -22,7 +22,9 @@ async function basic() { assert.strictEqual(report.workers.length, 1); helper.validateContent(report.workers[0]); assert.strictEqual(report.workers[0].header.threadId, w.threadId); - + assert.strictEqual(report.workers[0].header.event, + 'Worker thread subreport [hello]', + report.workers[0].header.event); w.postMessage({}); await once(w, 'exit'); diff --git a/test/sequential/sequential.status b/test/sequential/sequential.status index 81a152ffa0ce67..439fa2e3cd0f22 100644 --- a/test/sequential/sequential.status +++ b/test/sequential/sequential.status @@ -51,3 +51,17 @@ test-tls-psk-client: PASS, FLAKY [$arch==arm] # https://github.com/nodejs/node/issues/49933 test-watch-mode-inspect: SKIP + +[$system==linux && $arch==ppc64] +# https://github.com/nodejs/node/issues/59561 +test-single-executable-application: SKIP +test-single-executable-application-assets: SKIP +test-single-executable-application-assets-raw: SKIP +test-single-executable-application-disable-experimental-sea-warning: SKIP +test-single-executable-application-empty: SKIP +test-single-executable-application-exec-argv: SKIP +test-single-executable-application-exec-argv-empty: SKIP +test-single-executable-application-snapshot: SKIP +test-single-executable-application-snapshot-and-code-cache: SKIP +test-single-executable-application-snapshot-worker: SKIP +test-single-executable-application-use-code-cache: SKIP diff --git a/test/sequential/test-async-wrap-getasyncid.js b/test/sequential/test-async-wrap-getasyncid.js index 64de37eb4bc96c..6d454b40f0146f 100644 --- a/test/sequential/test-async-wrap-getasyncid.js +++ b/test/sequential/test-async-wrap-getasyncid.js @@ -46,6 +46,7 @@ const { getSystemErrorName } = require('util'); delete providers.MESSAGEPORT; delete providers.WORKER; // TODO(danbev): Test for these + delete providers.ARGON2REQUEST; delete providers.JSUDPWRAP; delete providers.KEYPAIRGENREQUEST; delete providers.KEYGENREQUEST; diff --git a/test/sequential/test-single-executable-application-exec-argv-empty.js b/test/sequential/test-single-executable-application-exec-argv-empty.js new file mode 100644 index 00000000000000..953d0d0e9f871b --- /dev/null +++ b/test/sequential/test-single-executable-application-exec-argv-empty.js @@ -0,0 +1,61 @@ +'use strict'; + +require('../common'); + +const { + generateSEA, + skipIfSingleExecutableIsNotSupported, +} = require('../common/sea'); + +skipIfSingleExecutableIsNotSupported(); + +// This tests the execArgv functionality with empty array in single executable applications. + +const fixtures = require('../common/fixtures'); +const tmpdir = require('../common/tmpdir'); +const { copyFileSync, writeFileSync, existsSync } = require('fs'); +const { spawnSyncAndAssert, spawnSyncAndExitWithoutError } = require('../common/child_process'); +const { join } = require('path'); +const assert = require('assert'); + +const configFile = tmpdir.resolve('sea-config.json'); +const seaPrepBlob = tmpdir.resolve('sea-prep.blob'); +const outputFile = tmpdir.resolve(process.platform === 'win32' ? 'sea.exe' : 'sea'); + +tmpdir.refresh(); + +// Copy test fixture to working directory +copyFileSync(fixtures.path('sea-exec-argv-empty.js'), tmpdir.resolve('sea.js')); + +writeFileSync(configFile, ` +{ + "main": "sea.js", + "output": "sea-prep.blob", + "disableExperimentalSEAWarning": true, + "execArgv": [] +} +`); + +spawnSyncAndExitWithoutError( + process.execPath, + ['--experimental-sea-config', 'sea-config.json'], + { cwd: tmpdir.path }); + +assert(existsSync(seaPrepBlob)); + +generateSEA(outputFile, process.execPath, seaPrepBlob); + +// Test that empty execArgv work correctly +spawnSyncAndAssert( + outputFile, + ['user-arg'], + { + env: { + COMMON_DIRECTORY: join(__dirname, '..', 'common'), + NODE_DEBUG_NATIVE: 'SEA', + ...process.env, + } + }, + { + stdout: /empty execArgv test passed/ + }); diff --git a/test/sequential/test-single-executable-application-exec-argv-extension-cli.js b/test/sequential/test-single-executable-application-exec-argv-extension-cli.js new file mode 100644 index 00000000000000..81ff05d53ce7a4 --- /dev/null +++ b/test/sequential/test-single-executable-application-exec-argv-extension-cli.js @@ -0,0 +1,63 @@ +'use strict'; + +require('../common'); + +const { + generateSEA, + skipIfSingleExecutableIsNotSupported, +} = require('../common/sea'); + +skipIfSingleExecutableIsNotSupported(); + +// This tests the execArgvExtension "cli" mode in single executable applications. + +const fixtures = require('../common/fixtures'); +const tmpdir = require('../common/tmpdir'); +const { copyFileSync, writeFileSync, existsSync } = require('fs'); +const { spawnSyncAndAssert, spawnSyncAndExitWithoutError } = require('../common/child_process'); +const { join } = require('path'); +const assert = require('assert'); + +const configFile = tmpdir.resolve('sea-config.json'); +const seaPrepBlob = tmpdir.resolve('sea-prep.blob'); +const outputFile = tmpdir.resolve(process.platform === 'win32' ? 'sea.exe' : 'sea'); + +tmpdir.refresh(); + +// Copy test fixture to working directory +copyFileSync(fixtures.path('sea-exec-argv-extension-cli.js'), tmpdir.resolve('sea.js')); + +writeFileSync(configFile, ` +{ + "main": "sea.js", + "output": "sea-prep.blob", + "disableExperimentalSEAWarning": true, + "execArgv": ["--no-warnings"], + "execArgvExtension": "cli" +} +`); + +spawnSyncAndExitWithoutError( + process.execPath, + ['--experimental-sea-config', 'sea-config.json'], + { cwd: tmpdir.path }); + +assert(existsSync(seaPrepBlob)); + +generateSEA(outputFile, process.execPath, seaPrepBlob); + +// Test that --node-options works with execArgvExtension: "cli" +spawnSyncAndAssert( + outputFile, + ['--node-options=--max-old-space-size=1024', 'user-arg1', 'user-arg2'], + { + env: { + ...process.env, + NODE_OPTIONS: '--max-old-space-size=2048', // Should be ignored + COMMON_DIRECTORY: join(__dirname, '..', 'common'), + NODE_DEBUG_NATIVE: 'SEA', + } + }, + { + stdout: /execArgvExtension cli test passed/ + }); diff --git a/test/sequential/test-single-executable-application-exec-argv-extension-env.js b/test/sequential/test-single-executable-application-exec-argv-extension-env.js new file mode 100644 index 00000000000000..25d07bdc468f9a --- /dev/null +++ b/test/sequential/test-single-executable-application-exec-argv-extension-env.js @@ -0,0 +1,68 @@ +'use strict'; + +require('../common'); + +const { + generateSEA, + skipIfSingleExecutableIsNotSupported, +} = require('../common/sea'); + +skipIfSingleExecutableIsNotSupported(); + +// This tests the execArgvExtension "env" mode (default) in single executable applications. + +const fixtures = require('../common/fixtures'); +const tmpdir = require('../common/tmpdir'); +const { copyFileSync, writeFileSync, existsSync } = require('fs'); +const { spawnSyncAndAssert, spawnSyncAndExitWithoutError } = require('../common/child_process'); +const { join } = require('path'); +const assert = require('assert'); + +const configFile = tmpdir.resolve('sea-config.json'); +const seaPrepBlob = tmpdir.resolve('sea-prep.blob'); +const outputFile = tmpdir.resolve(process.platform === 'win32' ? 'sea.exe' : 'sea'); + +tmpdir.refresh(); + +// Copy test fixture to working directory +copyFileSync(fixtures.path('sea-exec-argv-extension-env.js'), tmpdir.resolve('sea.js')); + +writeFileSync(configFile, ` +{ + "main": "sea.js", + "output": "sea-prep.blob", + "disableExperimentalSEAWarning": true, + "execArgv": ["--no-warnings"], + "execArgvExtension": "env" +} +`); + +spawnSyncAndExitWithoutError( + process.execPath, + ['--experimental-sea-config', 'sea-config.json'], + { cwd: tmpdir.path }); + +assert(existsSync(seaPrepBlob)); + +generateSEA(outputFile, process.execPath, seaPrepBlob); + +// Test that NODE_OPTIONS works with execArgvExtension: "env" (default behavior) +spawnSyncAndAssert( + outputFile, + ['user-arg1', 'user-arg2'], + { + env: { + ...process.env, + NODE_OPTIONS: '--max-old-space-size=512', + COMMON_DIRECTORY: join(__dirname, '..', 'common'), + NODE_DEBUG_NATIVE: 'SEA', + } + }, + { + stdout: /execArgvExtension env test passed/, + stderr(output) { + assert.doesNotMatch(output, /This warning should not be shown in the output/); + return true; + }, + trim: true + }); diff --git a/test/sequential/test-single-executable-application-exec-argv-extension-none.js b/test/sequential/test-single-executable-application-exec-argv-extension-none.js new file mode 100644 index 00000000000000..272016f006f3f7 --- /dev/null +++ b/test/sequential/test-single-executable-application-exec-argv-extension-none.js @@ -0,0 +1,63 @@ +'use strict'; + +require('../common'); + +const { + generateSEA, + skipIfSingleExecutableIsNotSupported, +} = require('../common/sea'); + +skipIfSingleExecutableIsNotSupported(); + +// This tests the execArgvExtension "none" mode in single executable applications. + +const fixtures = require('../common/fixtures'); +const tmpdir = require('../common/tmpdir'); +const { copyFileSync, writeFileSync, existsSync } = require('fs'); +const { spawnSyncAndAssert, spawnSyncAndExitWithoutError } = require('../common/child_process'); +const { join } = require('path'); +const assert = require('assert'); + +const configFile = tmpdir.resolve('sea-config.json'); +const seaPrepBlob = tmpdir.resolve('sea-prep.blob'); +const outputFile = tmpdir.resolve(process.platform === 'win32' ? 'sea.exe' : 'sea'); + +tmpdir.refresh(); + +// Copy test fixture to working directory +copyFileSync(fixtures.path('sea-exec-argv-extension-none.js'), tmpdir.resolve('sea.js')); + +writeFileSync(configFile, ` +{ + "main": "sea.js", + "output": "sea-prep.blob", + "disableExperimentalSEAWarning": true, + "execArgv": ["--no-warnings"], + "execArgvExtension": "none" +} +`); + +spawnSyncAndExitWithoutError( + process.execPath, + ['--experimental-sea-config', 'sea-config.json'], + { cwd: tmpdir.path }); + +assert(existsSync(seaPrepBlob)); + +generateSEA(outputFile, process.execPath, seaPrepBlob); + +// Test that NODE_OPTIONS is ignored with execArgvExtension: "none" +spawnSyncAndAssert( + outputFile, + ['user-arg1', 'user-arg2'], + { + env: { + ...process.env, + NODE_OPTIONS: '--max-old-space-size=2048', + COMMON_DIRECTORY: join(__dirname, '..', 'common'), + NODE_DEBUG_NATIVE: 'SEA', + } + }, + { + stdout: /execArgvExtension none test passed/ + }); diff --git a/test/sequential/test-single-executable-application-exec-argv.js b/test/sequential/test-single-executable-application-exec-argv.js new file mode 100644 index 00000000000000..f24934a0fffc6f --- /dev/null +++ b/test/sequential/test-single-executable-application-exec-argv.js @@ -0,0 +1,67 @@ +'use strict'; + +require('../common'); + +const { + generateSEA, + skipIfSingleExecutableIsNotSupported, +} = require('../common/sea'); + +skipIfSingleExecutableIsNotSupported(); + +// This tests the execArgv functionality with multiple arguments in single executable applications. + +const fixtures = require('../common/fixtures'); +const tmpdir = require('../common/tmpdir'); +const { copyFileSync, writeFileSync, existsSync } = require('fs'); +const { spawnSyncAndAssert, spawnSyncAndExitWithoutError } = require('../common/child_process'); +const { join } = require('path'); +const assert = require('assert'); + +const configFile = tmpdir.resolve('sea-config.json'); +const seaPrepBlob = tmpdir.resolve('sea-prep.blob'); +const outputFile = tmpdir.resolve(process.platform === 'win32' ? 'sea.exe' : 'sea'); + +tmpdir.refresh(); + +// Copy test fixture to working directory +copyFileSync(fixtures.path('sea-exec-argv.js'), tmpdir.resolve('sea.js')); + +writeFileSync(configFile, ` +{ + "main": "sea.js", + "output": "sea-prep.blob", + "disableExperimentalSEAWarning": true, + "execArgv": ["--no-warnings", "--max-old-space-size=2048"] +} +`); + +spawnSyncAndExitWithoutError( + process.execPath, + ['--experimental-sea-config', 'sea-config.json'], + { cwd: tmpdir.path }); + +assert(existsSync(seaPrepBlob)); + +generateSEA(outputFile, process.execPath, seaPrepBlob); + +// Test that multiple execArgv are properly applied +spawnSyncAndAssert( + outputFile, + ['user-arg1', 'user-arg2'], + { + env: { + ...process.env, + NODE_NO_WARNINGS: '0', + COMMON_DIRECTORY: join(__dirname, '..', 'common'), + NODE_DEBUG_NATIVE: 'SEA', + } + }, + { + stdout: /multiple execArgv test passed/, + stderr(output) { + assert.doesNotMatch(output, /This warning should not be shown in the output/); + return true; + }, + trim: true, + }); diff --git a/test/sequential/test-watch-mode.mjs b/test/sequential/test-watch-mode.mjs index bb8b895351493e..f0a7d62e560821 100644 --- a/test/sequential/test-watch-mode.mjs +++ b/test/sequential/test-watch-mode.mjs @@ -671,7 +671,7 @@ console.log(values.random); ]); }); - it('should run when `--watch --inspect`', async () => { + it('should run when `--watch --inspect`', { skip: !process.features.inspector }, async () => { const file = createTmpFile(); const args = ['--watch', '--inspect', file]; const { stdout, stderr } = await runWriteSucceed({ file, watchedFile: file, watchFlag: null, args }); diff --git a/test/wasi/test-wasi-cant_dotdot.js b/test/wasi/test-wasi-cant_dotdot.js new file mode 100644 index 00000000000000..5740d382770bd8 --- /dev/null +++ b/test/wasi/test-wasi-cant_dotdot.js @@ -0,0 +1,7 @@ +'use strict'; + +// Tests test/wasi/wasm/cant_dotdot.wasm, see test/wasi/c/cant_dotdot.c +require('../common'); +const { testWasiPreview1 } = require('../common/wasi'); + +testWasiPreview1(['cant_dotdot']); diff --git a/test/wasi/test-wasi-clock_getres.js b/test/wasi/test-wasi-clock_getres.js new file mode 100644 index 00000000000000..609c925ab58008 --- /dev/null +++ b/test/wasi/test-wasi-clock_getres.js @@ -0,0 +1,8 @@ +'use strict'; + +// Tests test/wasi/wasm/clock_getres.wasm, see test/wasi/c/clock_getres.c +require('../common'); +const { testWasiPreview1 } = require('../common/wasi'); + +// This test is currently unsupported on IBM i PASE +testWasiPreview1(['clock_getres']); diff --git a/test/wasi/test-wasi-fd_prestat_get_refresh.js b/test/wasi/test-wasi-fd_prestat_get_refresh.js new file mode 100644 index 00000000000000..16a3785ceb1e68 --- /dev/null +++ b/test/wasi/test-wasi-fd_prestat_get_refresh.js @@ -0,0 +1,7 @@ +'use strict'; + +// Tests test/wasi/wasm/fd_prestat_get_refresh.wasm, see test/wasi/c/fd_prestat_get_refresh.c +require('../common'); +const { testWasiPreview1 } = require('../common/wasi'); + +testWasiPreview1(['fd_prestat_get_refresh']); diff --git a/test/wasi/test-wasi-ftruncate.js b/test/wasi/test-wasi-ftruncate.js new file mode 100644 index 00000000000000..c4af685ebf7fea --- /dev/null +++ b/test/wasi/test-wasi-ftruncate.js @@ -0,0 +1,7 @@ +'use strict'; + +// Tests test/wasi/wasm/ftruncate.wasm, see test/wasi/c/ftruncate.c +require('../common'); +const { testWasiPreview1 } = require('../common/wasi'); + +testWasiPreview1(['ftruncate']); diff --git a/test/wasi/test-wasi-getentropy.js b/test/wasi/test-wasi-getentropy.js new file mode 100644 index 00000000000000..75d61465ca7f79 --- /dev/null +++ b/test/wasi/test-wasi-getentropy.js @@ -0,0 +1,7 @@ +'use strict'; + +// Tests test/wasi/wasm/getentropy.wasm, see test/wasi/c/getentropy.c +require('../common'); +const { testWasiPreview1 } = require('../common/wasi'); + +testWasiPreview1(['getentropy']); diff --git a/test/wasi/test-wasi-getrusage.js b/test/wasi/test-wasi-getrusage.js new file mode 100644 index 00000000000000..845881691e3150 --- /dev/null +++ b/test/wasi/test-wasi-getrusage.js @@ -0,0 +1,7 @@ +'use strict'; + +// Tests test/wasi/wasm/getrusage.wasm, see test/wasi/c/getrusage.c +require('../common'); +const { testWasiPreview1 } = require('../common/wasi'); + +testWasiPreview1(['getrusage']); diff --git a/test/wasi/test-wasi-gettimeofday.js b/test/wasi/test-wasi-gettimeofday.js new file mode 100644 index 00000000000000..56fc652a2ef983 --- /dev/null +++ b/test/wasi/test-wasi-gettimeofday.js @@ -0,0 +1,7 @@ +'use strict'; + +// Tests test/wasi/wasm/gettimeofday.wasm, see test/wasi/c/gettimeofday.c +require('../common'); +const { testWasiPreview1 } = require('../common/wasi'); + +testWasiPreview1(['gettimeofday']); diff --git a/test/wasi/test-wasi-main_args.js b/test/wasi/test-wasi-main_args.js new file mode 100644 index 00000000000000..1667f3f4c0ad39 --- /dev/null +++ b/test/wasi/test-wasi-main_args.js @@ -0,0 +1,7 @@ +'use strict'; + +// Tests test/wasi/wasm/main_args.wasm, see test/wasi/c/main_args.c +require('../common'); +const { testWasiPreview1 } = require('../common/wasi'); + +testWasiPreview1(['main_args']); diff --git a/test/wasi/test-wasi-notdir.js b/test/wasi/test-wasi-notdir.js new file mode 100644 index 00000000000000..f277979fc0022b --- /dev/null +++ b/test/wasi/test-wasi-notdir.js @@ -0,0 +1,7 @@ +'use strict'; + +// Tests test/wasi/wasm/notdir.wasm, see test/wasi/c/notdir.c +require('../common'); +const { testWasiPreview1 } = require('../common/wasi'); + +testWasiPreview1(['notdir']); diff --git a/test/wasi/test-wasi-preopen_populates.js b/test/wasi/test-wasi-preopen_populates.js new file mode 100644 index 00000000000000..b20008d3612a8d --- /dev/null +++ b/test/wasi/test-wasi-preopen_populates.js @@ -0,0 +1,7 @@ +'use strict'; + +// Tests test/wasi/wasm/preopen_populates.wasm, see test/wasi/c/preopen_populates.c +require('../common'); +const { testWasiPreview1 } = require('../common/wasi'); + +testWasiPreview1(['preopen_populates']); diff --git a/test/wasi/test-wasi-pthread.js b/test/wasi/test-wasi-pthread.js new file mode 100644 index 00000000000000..9cbb3380763c47 --- /dev/null +++ b/test/wasi/test-wasi-pthread.js @@ -0,0 +1,7 @@ +'use strict'; + +// Tests test/wasi/wasm/pthread.wasm, see test/wasi/c/pthread.c +require('../common'); +const { testWasiPreview1 } = require('../common/wasi'); + +testWasiPreview1(['--target=wasm32-wasip1-threads', 'pthread']); diff --git a/test/wasi/test-wasi-readdir.js b/test/wasi/test-wasi-readdir.js new file mode 100644 index 00000000000000..67714fe529404f --- /dev/null +++ b/test/wasi/test-wasi-readdir.js @@ -0,0 +1,7 @@ +'use strict'; + +// Tests test/wasi/wasm/readdir.wasm, see test/wasi/c/readdir.c +require('../common'); +const { testWasiPreview1 } = require('../common/wasi'); + +testWasiPreview1(['readdir']); diff --git a/test/wasi/test-wasi-sock.js b/test/wasi/test-wasi-sock.js new file mode 100644 index 00000000000000..245ac2ea25fc8d --- /dev/null +++ b/test/wasi/test-wasi-sock.js @@ -0,0 +1,7 @@ +'use strict'; + +// Tests test/wasi/wasm/sock.wasm, see test/wasi/c/sock.c +require('../common'); +const { testWasiPreview1 } = require('../common/wasi'); + +testWasiPreview1(['sock']); diff --git a/test/wasi/test-wasi-stat.js b/test/wasi/test-wasi-stat.js new file mode 100644 index 00000000000000..6b2eceb0a1c76c --- /dev/null +++ b/test/wasi/test-wasi-stat.js @@ -0,0 +1,7 @@ +'use strict'; + +// Tests test/wasi/wasm/stat.wasm, see test/wasi/c/stat.c +require('../common'); +const { testWasiPreview1 } = require('../common/wasi'); + +testWasiPreview1(['stat']); diff --git a/test/wasi/test-wasi-write_file.js b/test/wasi/test-wasi-write_file.js new file mode 100644 index 00000000000000..eae2e64e73d2ff --- /dev/null +++ b/test/wasi/test-wasi-write_file.js @@ -0,0 +1,7 @@ +'use strict'; + +// Tests test/wasi/wasm/write_file.wasm, see test/wasi/c/write_file.c +require('../common'); +const { testWasiPreview1 } = require('../common/wasi'); + +testWasiPreview1(['write_file']); diff --git a/test/wasi/test-wasi.js b/test/wasi/test-wasi.js deleted file mode 100644 index 9dd583fc5bdcb9..00000000000000 --- a/test/wasi/test-wasi.js +++ /dev/null @@ -1,29 +0,0 @@ -'use strict'; -const common = require('../common'); -const { testWasiPreview1 } = require('../common/wasi'); - -// TODO(joyeecheung): tests that don't need special configurations can be ported -// to a special python test case configuration and get run in parallel. -// Tests that are currently unsupported on IBM i PASE. -if (!common.isIBMi) { - testWasiPreview1(['clock_getres']); - testWasiPreview1(['getrusage']); -} - -// Tests that are currently unsupported on Windows and Android. -if (!common.isWindows && process.platform !== 'android') { - testWasiPreview1(['readdir']); -} - -testWasiPreview1(['cant_dotdot']); -testWasiPreview1(['fd_prestat_get_refresh']); -testWasiPreview1(['ftruncate']); -testWasiPreview1(['getentropy']); -testWasiPreview1(['gettimeofday']); -testWasiPreview1(['main_args']); -testWasiPreview1(['notdir']); -testWasiPreview1(['preopen_populates']); -testWasiPreview1(['stat']); -testWasiPreview1(['sock']); -testWasiPreview1(['write_file']); -testWasiPreview1(['--target=wasm32-wasip1-threads', 'pthread']); diff --git a/test/wasi/wasi.status b/test/wasi/wasi.status index 2870fc67f1c776..d88bd60ae4dc13 100644 --- a/test/wasi/wasi.status +++ b/test/wasi/wasi.status @@ -6,3 +6,15 @@ prefix wasi [true] # This section applies to all platforms +[$system==ibmi] +# Unsupported on IBM i PASE +test-wasi-clock_getres: SKIP +test-wasi-getrusage: SKIP + +[$system==win32 || $system==android] +# Unsupported on Windows and Android +test-wasi-readdir: SKIP + +[$system==linux] +# https://github.com/nodejs/node/issues/59146 +test-wasi-pthread: PASS, FLAKY diff --git a/tools/certdata.txt b/tools/certdata.txt index 1ed5a248b57a62..aeedd56c53b172 100644 --- a/tools/certdata.txt +++ b/tools/certdata.txt @@ -200,7 +200,7 @@ END CKA_SERIAL_NUMBER MULTILINE_OCTAL \002\013\004\000\000\000\000\001\025\113\132\303\224 END -CKA_TRUST_SERVER_AUTH CK_TRUST CKT_NSS_TRUSTED_DELEGATOR +CKA_TRUST_SERVER_AUTH CK_TRUST CKT_NSS_MUST_VERIFY_TRUST CKA_TRUST_EMAIL_PROTECTION CK_TRUST CKT_NSS_TRUSTED_DELEGATOR CKA_TRUST_CODE_SIGNING CK_TRUST CKT_NSS_MUST_VERIFY_TRUST CKA_TRUST_STEP_UP_APPROVED CK_BBOOL CK_FALSE @@ -366,141 +366,7 @@ END CKA_SERIAL_NUMBER MULTILINE_OCTAL \002\004\070\143\336\370 END -CKA_TRUST_SERVER_AUTH CK_TRUST CKT_NSS_TRUSTED_DELEGATOR -CKA_TRUST_EMAIL_PROTECTION CK_TRUST CKT_NSS_TRUSTED_DELEGATOR -CKA_TRUST_CODE_SIGNING CK_TRUST CKT_NSS_MUST_VERIFY_TRUST -CKA_TRUST_STEP_UP_APPROVED CK_BBOOL CK_FALSE - -# -# Certificate "Baltimore CyberTrust Root" -# -# Issuer: CN=Baltimore CyberTrust Root,OU=CyberTrust,O=Baltimore,C=IE -# Serial Number: 33554617 (0x20000b9) -# Subject: CN=Baltimore CyberTrust Root,OU=CyberTrust,O=Baltimore,C=IE -# Not Valid Before: Fri May 12 18:46:00 2000 -# Not Valid After : Mon May 12 23:59:00 2025 -# Fingerprint (SHA-256): 16:AF:57:A9:F6:76:B0:AB:12:60:95:AA:5E:BA:DE:F2:2A:B3:11:19:D6:44:AC:95:CD:4B:93:DB:F3:F2:6A:EB -# Fingerprint (SHA1): D4:DE:20:D0:5E:66:FC:53:FE:1A:50:88:2C:78:DB:28:52:CA:E4:74 -CKA_CLASS CK_OBJECT_CLASS CKO_CERTIFICATE -CKA_TOKEN CK_BBOOL CK_TRUE -CKA_PRIVATE CK_BBOOL CK_FALSE -CKA_MODIFIABLE CK_BBOOL CK_FALSE -CKA_LABEL UTF8 "Baltimore CyberTrust Root" -CKA_CERTIFICATE_TYPE CK_CERTIFICATE_TYPE CKC_X_509 -CKA_SUBJECT MULTILINE_OCTAL -\060\132\061\013\060\011\006\003\125\004\006\023\002\111\105\061 -\022\060\020\006\003\125\004\012\023\011\102\141\154\164\151\155 -\157\162\145\061\023\060\021\006\003\125\004\013\023\012\103\171 -\142\145\162\124\162\165\163\164\061\042\060\040\006\003\125\004 -\003\023\031\102\141\154\164\151\155\157\162\145\040\103\171\142 -\145\162\124\162\165\163\164\040\122\157\157\164 -END -CKA_ID UTF8 "0" -CKA_ISSUER MULTILINE_OCTAL -\060\132\061\013\060\011\006\003\125\004\006\023\002\111\105\061 -\022\060\020\006\003\125\004\012\023\011\102\141\154\164\151\155 -\157\162\145\061\023\060\021\006\003\125\004\013\023\012\103\171 -\142\145\162\124\162\165\163\164\061\042\060\040\006\003\125\004 -\003\023\031\102\141\154\164\151\155\157\162\145\040\103\171\142 -\145\162\124\162\165\163\164\040\122\157\157\164 -END -CKA_SERIAL_NUMBER MULTILINE_OCTAL -\002\004\002\000\000\271 -END -CKA_VALUE MULTILINE_OCTAL -\060\202\003\167\060\202\002\137\240\003\002\001\002\002\004\002 -\000\000\271\060\015\006\011\052\206\110\206\367\015\001\001\005 -\005\000\060\132\061\013\060\011\006\003\125\004\006\023\002\111 -\105\061\022\060\020\006\003\125\004\012\023\011\102\141\154\164 -\151\155\157\162\145\061\023\060\021\006\003\125\004\013\023\012 -\103\171\142\145\162\124\162\165\163\164\061\042\060\040\006\003 -\125\004\003\023\031\102\141\154\164\151\155\157\162\145\040\103 -\171\142\145\162\124\162\165\163\164\040\122\157\157\164\060\036 -\027\015\060\060\060\065\061\062\061\070\064\066\060\060\132\027 -\015\062\065\060\065\061\062\062\063\065\071\060\060\132\060\132 -\061\013\060\011\006\003\125\004\006\023\002\111\105\061\022\060 -\020\006\003\125\004\012\023\011\102\141\154\164\151\155\157\162 -\145\061\023\060\021\006\003\125\004\013\023\012\103\171\142\145 -\162\124\162\165\163\164\061\042\060\040\006\003\125\004\003\023 -\031\102\141\154\164\151\155\157\162\145\040\103\171\142\145\162 -\124\162\165\163\164\040\122\157\157\164\060\202\001\042\060\015 -\006\011\052\206\110\206\367\015\001\001\001\005\000\003\202\001 -\017\000\060\202\001\012\002\202\001\001\000\243\004\273\042\253 -\230\075\127\350\046\162\232\265\171\324\051\342\341\350\225\200 -\261\260\343\133\216\053\051\232\144\337\241\135\355\260\011\005 -\155\333\050\056\316\142\242\142\376\264\210\332\022\353\070\353 -\041\235\300\101\053\001\122\173\210\167\323\034\217\307\272\271 -\210\265\152\011\347\163\350\021\100\247\321\314\312\142\215\055 -\345\217\013\246\120\322\250\120\303\050\352\365\253\045\207\212 -\232\226\034\251\147\270\077\014\325\367\371\122\023\057\302\033 -\325\160\160\360\217\300\022\312\006\313\232\341\331\312\063\172 -\167\326\370\354\271\361\150\104\102\110\023\322\300\302\244\256 -\136\140\376\266\246\005\374\264\335\007\131\002\324\131\030\230 -\143\365\245\143\340\220\014\175\135\262\006\172\363\205\352\353 -\324\003\256\136\204\076\137\377\025\355\151\274\371\071\066\162 -\165\317\167\122\115\363\311\220\054\271\075\345\311\043\123\077 -\037\044\230\041\134\007\231\051\275\306\072\354\347\156\206\072 -\153\227\164\143\063\275\150\030\061\360\170\215\166\277\374\236 -\216\135\052\206\247\115\220\334\047\032\071\002\003\001\000\001 -\243\105\060\103\060\035\006\003\125\035\016\004\026\004\024\345 -\235\131\060\202\107\130\314\254\372\010\124\066\206\173\072\265 -\004\115\360\060\022\006\003\125\035\023\001\001\377\004\010\060 -\006\001\001\377\002\001\003\060\016\006\003\125\035\017\001\001 -\377\004\004\003\002\001\006\060\015\006\011\052\206\110\206\367 -\015\001\001\005\005\000\003\202\001\001\000\205\014\135\216\344 -\157\121\150\102\005\240\335\273\117\047\045\204\003\275\367\144 -\375\055\327\060\343\244\020\027\353\332\051\051\266\171\077\166 -\366\031\023\043\270\020\012\371\130\244\324\141\160\275\004\141 -\152\022\212\027\325\012\275\305\274\060\174\326\351\014\045\215 -\206\100\117\354\314\243\176\070\306\067\021\117\355\335\150\061 -\216\114\322\263\001\164\356\276\165\136\007\110\032\177\160\377 -\026\134\204\300\171\205\270\005\375\177\276\145\021\243\017\300 -\002\264\370\122\067\071\004\325\251\061\172\030\277\240\052\364 -\022\231\367\243\105\202\343\074\136\365\235\236\265\310\236\174 -\056\310\244\236\116\010\024\113\155\375\160\155\153\032\143\275 -\144\346\037\267\316\360\362\237\056\273\033\267\362\120\210\163 -\222\302\342\343\026\215\232\062\002\253\216\030\335\351\020\021 -\356\176\065\253\220\257\076\060\224\172\320\063\075\247\145\017 -\365\374\216\236\142\317\107\104\054\001\135\273\035\265\062\322 -\107\322\070\056\320\376\201\334\062\152\036\265\356\074\325\374 -\347\201\035\031\303\044\102\352\143\071\251 -END -CKA_NSS_MOZILLA_CA_POLICY CK_BBOOL CK_TRUE -CKA_NSS_SERVER_DISTRUST_AFTER CK_BBOOL CK_FALSE -CKA_NSS_EMAIL_DISTRUST_AFTER CK_BBOOL CK_FALSE - -# Trust for "Baltimore CyberTrust Root" -# Issuer: CN=Baltimore CyberTrust Root,OU=CyberTrust,O=Baltimore,C=IE -# Serial Number: 33554617 (0x20000b9) -# Subject: CN=Baltimore CyberTrust Root,OU=CyberTrust,O=Baltimore,C=IE -# Not Valid Before: Fri May 12 18:46:00 2000 -# Not Valid After : Mon May 12 23:59:00 2025 -# Fingerprint (SHA-256): 16:AF:57:A9:F6:76:B0:AB:12:60:95:AA:5E:BA:DE:F2:2A:B3:11:19:D6:44:AC:95:CD:4B:93:DB:F3:F2:6A:EB -# Fingerprint (SHA1): D4:DE:20:D0:5E:66:FC:53:FE:1A:50:88:2C:78:DB:28:52:CA:E4:74 -CKA_CLASS CK_OBJECT_CLASS CKO_NSS_TRUST -CKA_TOKEN CK_BBOOL CK_TRUE -CKA_PRIVATE CK_BBOOL CK_FALSE -CKA_MODIFIABLE CK_BBOOL CK_FALSE -CKA_LABEL UTF8 "Baltimore CyberTrust Root" -CKA_CERT_SHA1_HASH MULTILINE_OCTAL -\324\336\040\320\136\146\374\123\376\032\120\210\054\170\333\050 -\122\312\344\164 -END -CKA_CERT_MD5_HASH MULTILINE_OCTAL -\254\266\224\245\234\027\340\327\221\122\233\261\227\006\246\344 -END -CKA_ISSUER MULTILINE_OCTAL -\060\132\061\013\060\011\006\003\125\004\006\023\002\111\105\061 -\022\060\020\006\003\125\004\012\023\011\102\141\154\164\151\155 -\157\162\145\061\023\060\021\006\003\125\004\013\023\012\103\171 -\142\145\162\124\162\165\163\164\061\042\060\040\006\003\125\004 -\003\023\031\102\141\154\164\151\155\157\162\145\040\103\171\142 -\145\162\124\162\165\163\164\040\122\157\157\164 -END -CKA_SERIAL_NUMBER MULTILINE_OCTAL -\002\004\002\000\000\271 -END -CKA_TRUST_SERVER_AUTH CK_TRUST CKT_NSS_TRUSTED_DELEGATOR +CKA_TRUST_SERVER_AUTH CK_TRUST CKT_NSS_MUST_VERIFY_TRUST CKA_TRUST_EMAIL_PROTECTION CK_TRUST CKT_NSS_TRUSTED_DELEGATOR CKA_TRUST_CODE_SIGNING CK_TRUST CKT_NSS_MUST_VERIFY_TRUST CKA_TRUST_STEP_UP_APPROVED CK_BBOOL CK_FALSE @@ -946,7 +812,7 @@ END CKA_SERIAL_NUMBER MULTILINE_OCTAL \002\001\001 END -CKA_TRUST_SERVER_AUTH CK_TRUST CKT_NSS_TRUSTED_DELEGATOR +CKA_TRUST_SERVER_AUTH CK_TRUST CKT_NSS_MUST_VERIFY_TRUST CKA_TRUST_EMAIL_PROTECTION CK_TRUST CKT_NSS_TRUSTED_DELEGATOR CKA_TRUST_CODE_SIGNING CK_TRUST CKT_NSS_MUST_VERIFY_TRUST CKA_TRUST_STEP_UP_APPROVED CK_BBOOL CK_FALSE @@ -1452,7 +1318,7 @@ CKA_SERIAL_NUMBER MULTILINE_OCTAL \002\020\120\224\154\354\030\352\325\234\115\325\227\357\165\217 \240\255 END -CKA_TRUST_SERVER_AUTH CK_TRUST CKT_NSS_TRUSTED_DELEGATOR +CKA_TRUST_SERVER_AUTH CK_TRUST CKT_NSS_MUST_VERIFY_TRUST CKA_TRUST_EMAIL_PROTECTION CK_TRUST CKT_NSS_TRUSTED_DELEGATOR CKA_TRUST_CODE_SIGNING CK_TRUST CKT_NSS_MUST_VERIFY_TRUST CKA_TRUST_STEP_UP_APPROVED CK_BBOOL CK_FALSE @@ -1598,8 +1464,8 @@ END CKA_SERIAL_NUMBER MULTILINE_OCTAL \002\001\000 END -CKA_TRUST_SERVER_AUTH CK_TRUST CKT_NSS_TRUSTED_DELEGATOR -CKA_TRUST_EMAIL_PROTECTION CK_TRUST CKT_NSS_TRUSTED_DELEGATOR +CKA_TRUST_SERVER_AUTH CK_TRUST CKT_NSS_MUST_VERIFY_TRUST +CKA_TRUST_EMAIL_PROTECTION CK_TRUST CKT_NSS_MUST_VERIFY_TRUST CKA_TRUST_CODE_SIGNING CK_TRUST CKT_NSS_MUST_VERIFY_TRUST CKA_TRUST_STEP_UP_APPROVED CK_BBOOL CK_FALSE @@ -1745,8 +1611,8 @@ END CKA_SERIAL_NUMBER MULTILINE_OCTAL \002\001\000 END -CKA_TRUST_SERVER_AUTH CK_TRUST CKT_NSS_TRUSTED_DELEGATOR -CKA_TRUST_EMAIL_PROTECTION CK_TRUST CKT_NSS_TRUSTED_DELEGATOR +CKA_TRUST_SERVER_AUTH CK_TRUST CKT_NSS_MUST_VERIFY_TRUST +CKA_TRUST_EMAIL_PROTECTION CK_TRUST CKT_NSS_MUST_VERIFY_TRUST CKA_TRUST_CODE_SIGNING CK_TRUST CKT_NSS_MUST_VERIFY_TRUST CKA_TRUST_STEP_UP_APPROVED CK_BBOOL CK_FALSE @@ -3323,9 +3189,13 @@ CKA_VALUE MULTILINE_OCTAL \201\370\021\234 END CKA_NSS_MOZILLA_CA_POLICY CK_BBOOL CK_TRUE -CKA_NSS_SERVER_DISTRUST_AFTER CK_BBOOL CK_FALSE +# For Server Distrust After: Tue Apr 15 23:59:59 2025 +CKA_NSS_SERVER_DISTRUST_AFTER MULTILINE_OCTAL +\062\065\060\064\061\065\062\063\065\071\065\071\132 +END CKA_NSS_EMAIL_DISTRUST_AFTER CK_BBOOL CK_FALSE + # Trust for "ePKI Root Certification Authority" # Issuer: OU=ePKI Root Certification Authority,O="Chunghwa Telecom Co., Ltd.",C=TW # Serial Number:15:c8:bd:65:47:5c:af:b8:97:00:5e:e4:06:d2:bc:9d @@ -25971,6 +25841,576 @@ CKA_TRUST_EMAIL_PROTECTION CK_TRUST CKT_NSS_MUST_VERIFY_TRUST CKA_TRUST_CODE_SIGNING CK_TRUST CKT_NSS_MUST_VERIFY_TRUST CKA_TRUST_STEP_UP_APPROVED CK_BBOOL CK_FALSE +# +# Certificate "TrustAsia SMIME ECC Root CA" +# +# Issuer: CN=TrustAsia SMIME ECC Root CA,O="TrustAsia Technologies, Inc.",C=CN +# Serial Number:5a:c2:f8:29:4f:e3:7d:c5:5e:1d:18:6f:3b:93:20:1f:ff:7b:ba:2d +# Subject: CN=TrustAsia SMIME ECC Root CA,O="TrustAsia Technologies, Inc.",C=CN +# Not Valid Before: Wed May 15 05:41:59 2024 +# Not Valid After : Sun May 15 05:41:58 2044 +# Fingerprint (SHA-256): 43:64:72:C1:00:9A:32:5C:54:F1:A5:BB:B5:46:8A:7B:AE:EC:CB:E0:5D:E5:F0:99:CB:70:D3:FE:41:E1:3C:16 +# Fingerprint (SHA1): 8C:0D:AA:FE:13:FA:59:F2:DB:9D:0C:97:DA:12:A2:45:1A:02:13:54 +CKA_CLASS CK_OBJECT_CLASS CKO_CERTIFICATE +CKA_TOKEN CK_BBOOL CK_TRUE +CKA_PRIVATE CK_BBOOL CK_FALSE +CKA_MODIFIABLE CK_BBOOL CK_FALSE +CKA_LABEL UTF8 "TrustAsia SMIME ECC Root CA" +CKA_CERTIFICATE_TYPE CK_CERTIFICATE_TYPE CKC_X_509 +CKA_SUBJECT MULTILINE_OCTAL +\060\132\061\013\060\011\006\003\125\004\006\023\002\103\116\061 +\045\060\043\006\003\125\004\012\023\034\124\162\165\163\164\101 +\163\151\141\040\124\145\143\150\156\157\154\157\147\151\145\163 +\054\040\111\156\143\056\061\044\060\042\006\003\125\004\003\023 +\033\124\162\165\163\164\101\163\151\141\040\123\115\111\115\105 +\040\105\103\103\040\122\157\157\164\040\103\101 +END +CKA_ID UTF8 "0" +CKA_ISSUER MULTILINE_OCTAL +\060\132\061\013\060\011\006\003\125\004\006\023\002\103\116\061 +\045\060\043\006\003\125\004\012\023\034\124\162\165\163\164\101 +\163\151\141\040\124\145\143\150\156\157\154\157\147\151\145\163 +\054\040\111\156\143\056\061\044\060\042\006\003\125\004\003\023 +\033\124\162\165\163\164\101\163\151\141\040\123\115\111\115\105 +\040\105\103\103\040\122\157\157\164\040\103\101 +END +CKA_SERIAL_NUMBER MULTILINE_OCTAL +\002\024\132\302\370\051\117\343\175\305\136\035\030\157\073\223 +\040\037\377\173\272\055 +END +CKA_VALUE MULTILINE_OCTAL +\060\202\002\066\060\202\001\273\240\003\002\001\002\002\024\132 +\302\370\051\117\343\175\305\136\035\030\157\073\223\040\037\377 +\173\272\055\060\012\006\010\052\206\110\316\075\004\003\003\060 +\132\061\013\060\011\006\003\125\004\006\023\002\103\116\061\045 +\060\043\006\003\125\004\012\023\034\124\162\165\163\164\101\163 +\151\141\040\124\145\143\150\156\157\154\157\147\151\145\163\054 +\040\111\156\143\056\061\044\060\042\006\003\125\004\003\023\033 +\124\162\165\163\164\101\163\151\141\040\123\115\111\115\105\040 +\105\103\103\040\122\157\157\164\040\103\101\060\036\027\015\062 +\064\060\065\061\065\060\065\064\061\065\071\132\027\015\064\064 +\060\065\061\065\060\065\064\061\065\070\132\060\132\061\013\060 +\011\006\003\125\004\006\023\002\103\116\061\045\060\043\006\003 +\125\004\012\023\034\124\162\165\163\164\101\163\151\141\040\124 +\145\143\150\156\157\154\157\147\151\145\163\054\040\111\156\143 +\056\061\044\060\042\006\003\125\004\003\023\033\124\162\165\163 +\164\101\163\151\141\040\123\115\111\115\105\040\105\103\103\040 +\122\157\157\164\040\103\101\060\166\060\020\006\007\052\206\110 +\316\075\002\001\006\005\053\201\004\000\042\003\142\000\004\315 +\331\373\047\275\151\354\206\311\220\103\261\160\027\224\247\311 +\167\043\072\077\043\145\323\034\243\035\036\102\124\040\353\320 +\102\273\131\271\213\254\362\206\215\113\216\035\252\226\042\043 +\071\166\155\222\233\353\042\313\172\103\242\163\375\163\113\302 +\045\017\052\261\151\306\377\105\337\251\074\225\200\273\251\036 +\112\223\057\354\034\035\210\364\021\222\067\237\067\230\162\243 +\102\060\100\060\017\006\003\125\035\023\001\001\377\004\005\060 +\003\001\001\377\060\035\006\003\125\035\016\004\026\004\024\061 +\147\346\162\262\015\346\042\240\254\317\176\042\247\131\062\343 +\146\043\245\060\016\006\003\125\035\017\001\001\377\004\004\003 +\002\001\006\060\012\006\010\052\206\110\316\075\004\003\003\003 +\151\000\060\146\002\061\000\335\072\114\215\244\306\177\355\275 +\245\306\117\076\375\061\113\050\326\212\126\337\145\026\167\207 +\115\373\272\062\010\201\340\236\063\110\213\237\224\206\357\001 +\053\224\346\214\326\324\216\002\061\000\237\201\234\260\046\375 +\053\326\362\365\161\204\236\250\307\212\214\326\130\021\122\265 +\270\004\313\314\161\165\143\342\306\031\070\331\155\154\031\161 +\244\026\031\041\223\272\006\104\050\154 +END +CKA_NSS_MOZILLA_CA_POLICY CK_BBOOL CK_TRUE +CKA_NSS_SERVER_DISTRUST_AFTER CK_BBOOL CK_FALSE +CKA_NSS_EMAIL_DISTRUST_AFTER CK_BBOOL CK_FALSE + +# Trust for "TrustAsia SMIME ECC Root CA" +# Issuer: CN=TrustAsia SMIME ECC Root CA,O="TrustAsia Technologies, Inc.",C=CN +# Serial Number:5a:c2:f8:29:4f:e3:7d:c5:5e:1d:18:6f:3b:93:20:1f:ff:7b:ba:2d +# Subject: CN=TrustAsia SMIME ECC Root CA,O="TrustAsia Technologies, Inc.",C=CN +# Not Valid Before: Wed May 15 05:41:59 2024 +# Not Valid After : Sun May 15 05:41:58 2044 +# Fingerprint (SHA-256): 43:64:72:C1:00:9A:32:5C:54:F1:A5:BB:B5:46:8A:7B:AE:EC:CB:E0:5D:E5:F0:99:CB:70:D3:FE:41:E1:3C:16 +# Fingerprint (SHA1): 8C:0D:AA:FE:13:FA:59:F2:DB:9D:0C:97:DA:12:A2:45:1A:02:13:54 +CKA_CLASS CK_OBJECT_CLASS CKO_NSS_TRUST +CKA_TOKEN CK_BBOOL CK_TRUE +CKA_PRIVATE CK_BBOOL CK_FALSE +CKA_MODIFIABLE CK_BBOOL CK_FALSE +CKA_LABEL UTF8 "TrustAsia SMIME ECC Root CA" +CKA_CERT_SHA1_HASH MULTILINE_OCTAL +\214\015\252\376\023\372\131\362\333\235\014\227\332\022\242\105 +\032\002\023\124 +END +CKA_CERT_MD5_HASH MULTILINE_OCTAL +\225\175\377\225\115\114\152\373\215\014\017\317\102\333\357\040 +END +CKA_ISSUER MULTILINE_OCTAL +\060\132\061\013\060\011\006\003\125\004\006\023\002\103\116\061 +\045\060\043\006\003\125\004\012\023\034\124\162\165\163\164\101 +\163\151\141\040\124\145\143\150\156\157\154\157\147\151\145\163 +\054\040\111\156\143\056\061\044\060\042\006\003\125\004\003\023 +\033\124\162\165\163\164\101\163\151\141\040\123\115\111\115\105 +\040\105\103\103\040\122\157\157\164\040\103\101 +END +CKA_SERIAL_NUMBER MULTILINE_OCTAL +\002\024\132\302\370\051\117\343\175\305\136\035\030\157\073\223 +\040\037\377\173\272\055 +END +CKA_TRUST_SERVER_AUTH CK_TRUST CKT_NSS_MUST_VERIFY_TRUST +CKA_TRUST_EMAIL_PROTECTION CK_TRUST CKT_NSS_TRUSTED_DELEGATOR +CKA_TRUST_CODE_SIGNING CK_TRUST CKT_NSS_MUST_VERIFY_TRUST +CKA_TRUST_STEP_UP_APPROVED CK_BBOOL CK_FALSE + +# +# Certificate "TrustAsia SMIME RSA Root CA" +# +# Issuer: CN=TrustAsia SMIME RSA Root CA,O="TrustAsia Technologies, Inc.",C=CN +# Serial Number:5a:ee:71:df:de:0c:57:85:b5:bb:36:22:d7:b8:76:46:02:73:ca:ff +# Subject: CN=TrustAsia SMIME RSA Root CA,O="TrustAsia Technologies, Inc.",C=CN +# Not Valid Before: Wed May 15 05:42:01 2024 +# Not Valid After : Sun May 15 05:42:00 2044 +# Fingerprint (SHA-256): C7:79:6B:EB:62:C1:01:BB:14:3D:26:2A:7C:96:A0:C6:16:81:83:22:3E:F5:0D:69:96:32:D8:6E:03:B8:CC:9B +# Fingerprint (SHA1): 8C:69:21:7C:CE:49:73:36:5A:5C:EF:2B:B0:86:97:50:E3:E3:33:65 +CKA_CLASS CK_OBJECT_CLASS CKO_CERTIFICATE +CKA_TOKEN CK_BBOOL CK_TRUE +CKA_PRIVATE CK_BBOOL CK_FALSE +CKA_MODIFIABLE CK_BBOOL CK_FALSE +CKA_LABEL UTF8 "TrustAsia SMIME RSA Root CA" +CKA_CERTIFICATE_TYPE CK_CERTIFICATE_TYPE CKC_X_509 +CKA_SUBJECT MULTILINE_OCTAL +\060\132\061\013\060\011\006\003\125\004\006\023\002\103\116\061 +\045\060\043\006\003\125\004\012\023\034\124\162\165\163\164\101 +\163\151\141\040\124\145\143\150\156\157\154\157\147\151\145\163 +\054\040\111\156\143\056\061\044\060\042\006\003\125\004\003\023 +\033\124\162\165\163\164\101\163\151\141\040\123\115\111\115\105 +\040\122\123\101\040\122\157\157\164\040\103\101 +END +CKA_ID UTF8 "0" +CKA_ISSUER MULTILINE_OCTAL +\060\132\061\013\060\011\006\003\125\004\006\023\002\103\116\061 +\045\060\043\006\003\125\004\012\023\034\124\162\165\163\164\101 +\163\151\141\040\124\145\143\150\156\157\154\157\147\151\145\163 +\054\040\111\156\143\056\061\044\060\042\006\003\125\004\003\023 +\033\124\162\165\163\164\101\163\151\141\040\123\115\111\115\105 +\040\122\123\101\040\122\157\157\164\040\103\101 +END +CKA_SERIAL_NUMBER MULTILINE_OCTAL +\002\024\132\356\161\337\336\014\127\205\265\273\066\042\327\270 +\166\106\002\163\312\377 +END +CKA_VALUE MULTILINE_OCTAL +\060\202\005\204\060\202\003\154\240\003\002\001\002\002\024\132 +\356\161\337\336\014\127\205\265\273\066\042\327\270\166\106\002 +\163\312\377\060\015\006\011\052\206\110\206\367\015\001\001\014 +\005\000\060\132\061\013\060\011\006\003\125\004\006\023\002\103 +\116\061\045\060\043\006\003\125\004\012\023\034\124\162\165\163 +\164\101\163\151\141\040\124\145\143\150\156\157\154\157\147\151 +\145\163\054\040\111\156\143\056\061\044\060\042\006\003\125\004 +\003\023\033\124\162\165\163\164\101\163\151\141\040\123\115\111 +\115\105\040\122\123\101\040\122\157\157\164\040\103\101\060\036 +\027\015\062\064\060\065\061\065\060\065\064\062\060\061\132\027 +\015\064\064\060\065\061\065\060\065\064\062\060\060\132\060\132 +\061\013\060\011\006\003\125\004\006\023\002\103\116\061\045\060 +\043\006\003\125\004\012\023\034\124\162\165\163\164\101\163\151 +\141\040\124\145\143\150\156\157\154\157\147\151\145\163\054\040 +\111\156\143\056\061\044\060\042\006\003\125\004\003\023\033\124 +\162\165\163\164\101\163\151\141\040\123\115\111\115\105\040\122 +\123\101\040\122\157\157\164\040\103\101\060\202\002\042\060\015 +\006\011\052\206\110\206\367\015\001\001\001\005\000\003\202\002 +\017\000\060\202\002\012\002\202\002\001\000\230\225\234\255\074 +\131\163\323\223\166\246\110\124\246\034\210\162\114\115\341\202 +\377\032\023\037\120\336\214\331\220\102\321\274\231\323\067\243 +\327\161\072\312\335\136\033\220\141\102\156\217\100\001\163\175 +\037\061\272\324\035\156\004\322\252\220\204\112\063\012\104\237 +\132\031\037\264\156\055\150\374\257\300\237\112\020\022\277\076 +\202\302\202\260\057\347\220\157\220\144\020\203\354\354\066\224 +\014\174\117\340\002\015\075\207\171\102\115\000\345\060\256\160 +\037\025\375\372\317\122\045\066\115\171\040\273\361\020\230\340 +\140\207\311\365\170\014\042\355\002\346\021\154\361\233\132\221 +\021\164\206\320\376\170\222\226\004\303\230\357\306\160\257\251 +\220\252\026\366\042\355\330\372\106\050\161\342\311\010\300\120 +\274\166\243\272\055\226\362\021\371\244\275\020\126\370\047\155 +\053\113\144\210\334\027\135\317\000\010\212\222\256\032\314\022 +\327\337\360\020\050\363\315\072\352\072\022\214\033\347\141\011 +\103\152\344\070\351\376\164\071\333\315\012\073\303\117\123\326 +\256\161\325\070\203\207\037\316\347\114\363\021\125\207\337\267 +\257\367\353\032\206\020\155\250\355\110\335\062\320\333\252\052 +\013\162\167\051\167\375\172\350\077\170\040\052\150\276\336\157 +\010\346\265\205\107\200\067\371\161\226\213\245\230\126\227\174 +\021\145\026\216\324\316\330\047\324\371\132\305\247\215\273\321 +\067\015\245\253\145\135\332\164\223\210\136\333\165\366\177\233 +\302\232\261\155\010\002\014\230\112\273\315\060\257\166\065\002 +\205\126\043\121\011\100\011\005\367\360\075\036\302\310\172\032 +\272\305\101\025\271\015\166\264\264\244\270\105\375\215\243\073 +\075\003\052\315\263\225\177\064\173\335\241\117\331\072\377\047 +\365\015\302\001\234\377\131\165\132\032\311\347\322\375\177\117 +\017\265\376\015\074\346\240\326\373\265\166\134\264\060\115\020 +\077\376\342\353\132\016\261\117\117\120\176\010\063\002\221\127 +\062\111\211\247\100\020\120\027\153\237\143\272\262\262\126\230 +\166\364\377\162\351\004\234\324\247\114\117\366\072\037\132\124 +\061\157\313\272\062\255\370\155\257\105\307\173\143\104\337\302 +\375\170\350\112\212\260\164\165\026\336\217\002\003\001\000\001 +\243\102\060\100\060\017\006\003\125\035\023\001\001\377\004\005 +\060\003\001\001\377\060\035\006\003\125\035\016\004\026\004\024 +\200\032\252\103\301\311\177\213\032\226\105\274\075\273\153\110 +\122\154\133\253\060\016\006\003\125\035\017\001\001\377\004\004 +\003\002\001\006\060\015\006\011\052\206\110\206\367\015\001\001 +\014\005\000\003\202\002\001\000\052\165\201\241\202\040\352\177 +\126\256\011\060\227\020\170\364\331\100\024\122\244\356\152\177 +\360\011\050\326\020\143\340\116\173\311\061\307\010\245\027\353 +\257\206\342\227\007\117\251\016\242\205\314\355\244\200\201\035 +\132\123\006\074\114\032\251\267\204\206\317\174\365\205\012\160 +\231\320\355\066\221\215\347\167\056\316\204\234\050\206\055\045 +\065\214\114\001\346\175\333\141\135\342\167\204\323\001\321\142 +\335\014\242\012\144\244\374\360\355\072\110\362\071\354\065\326 +\241\172\027\161\354\323\322\354\270\046\373\255\025\160\126\112 +\317\144\300\271\140\133\367\346\315\240\302\003\053\371\262\345 +\332\272\245\067\034\330\215\332\171\164\052\147\344\242\172\307 +\054\215\245\301\152\201\154\314\365\251\071\152\042\175\242\342 +\010\315\351\223\333\272\313\037\165\167\236\367\230\257\024\344 +\073\240\114\247\162\347\265\136\123\111\314\377\222\127\361\131 +\053\011\272\351\122\223\300\017\105\013\141\326\154\131\163\013 +\330\371\001\057\332\003\117\067\034\364\337\203\136\013\101\240 +\261\174\071\000\241\154\320\330\264\221\162\053\033\077\242\232 +\321\304\337\053\325\310\225\175\204\315\166\033\334\344\044\306 +\077\025\042\075\205\247\102\053\355\176\161\131\141\110\302\075 +\221\143\100\174\051\263\200\026\012\045\374\030\140\016\123\255 +\016\052\316\374\101\072\315\261\344\152\153\314\071\155\310\115 +\145\270\252\223\210\170\156\017\002\213\340\226\216\164\037\320 +\176\341\164\267\366\201\152\344\043\254\216\375\217\065\035\232 +\161\012\117\052\125\057\376\030\301\000\367\173\110\212\145\257 +\157\010\035\103\347\132\270\037\244\350\003\353\375\114\265\264 +\223\250\061\336\103\235\022\275\347\351\244\322\337\157\374\157 +\106\243\357\061\116\215\027\253\033\251\340\370\251\064\126\241 +\016\347\164\354\250\207\205\330\167\065\113\215\076\156\162\174 +\110\314\037\162\263\161\164\324\044\206\274\310\300\343\171\024 +\001\316\331\372\201\306\134\263\371\135\014\070\276\316\362\216 +\215\137\075\045\341\121\071\043\250\352\000\216\004\263\277\162 +\240\302\272\203\373\315\244\227\165\040\232\277\247\120\342\335 +\255\054\037\364\077\243\310\061 +END +CKA_NSS_MOZILLA_CA_POLICY CK_BBOOL CK_TRUE +CKA_NSS_SERVER_DISTRUST_AFTER CK_BBOOL CK_FALSE +CKA_NSS_EMAIL_DISTRUST_AFTER CK_BBOOL CK_FALSE + +# Trust for "TrustAsia SMIME RSA Root CA" +# Issuer: CN=TrustAsia SMIME RSA Root CA,O="TrustAsia Technologies, Inc.",C=CN +# Serial Number:5a:ee:71:df:de:0c:57:85:b5:bb:36:22:d7:b8:76:46:02:73:ca:ff +# Subject: CN=TrustAsia SMIME RSA Root CA,O="TrustAsia Technologies, Inc.",C=CN +# Not Valid Before: Wed May 15 05:42:01 2024 +# Not Valid After : Sun May 15 05:42:00 2044 +# Fingerprint (SHA-256): C7:79:6B:EB:62:C1:01:BB:14:3D:26:2A:7C:96:A0:C6:16:81:83:22:3E:F5:0D:69:96:32:D8:6E:03:B8:CC:9B +# Fingerprint (SHA1): 8C:69:21:7C:CE:49:73:36:5A:5C:EF:2B:B0:86:97:50:E3:E3:33:65 +CKA_CLASS CK_OBJECT_CLASS CKO_NSS_TRUST +CKA_TOKEN CK_BBOOL CK_TRUE +CKA_PRIVATE CK_BBOOL CK_FALSE +CKA_MODIFIABLE CK_BBOOL CK_FALSE +CKA_LABEL UTF8 "TrustAsia SMIME RSA Root CA" +CKA_CERT_SHA1_HASH MULTILINE_OCTAL +\214\151\041\174\316\111\163\066\132\134\357\053\260\206\227\120 +\343\343\063\145 +END +CKA_CERT_MD5_HASH MULTILINE_OCTAL +\151\206\230\353\337\305\241\132\301\244\153\324\074\243\373\266 +END +CKA_ISSUER MULTILINE_OCTAL +\060\132\061\013\060\011\006\003\125\004\006\023\002\103\116\061 +\045\060\043\006\003\125\004\012\023\034\124\162\165\163\164\101 +\163\151\141\040\124\145\143\150\156\157\154\157\147\151\145\163 +\054\040\111\156\143\056\061\044\060\042\006\003\125\004\003\023 +\033\124\162\165\163\164\101\163\151\141\040\123\115\111\115\105 +\040\122\123\101\040\122\157\157\164\040\103\101 +END +CKA_SERIAL_NUMBER MULTILINE_OCTAL +\002\024\132\356\161\337\336\014\127\205\265\273\066\042\327\270 +\166\106\002\163\312\377 +END +CKA_TRUST_SERVER_AUTH CK_TRUST CKT_NSS_MUST_VERIFY_TRUST +CKA_TRUST_EMAIL_PROTECTION CK_TRUST CKT_NSS_TRUSTED_DELEGATOR +CKA_TRUST_CODE_SIGNING CK_TRUST CKT_NSS_MUST_VERIFY_TRUST +CKA_TRUST_STEP_UP_APPROVED CK_BBOOL CK_FALSE + +# +# Certificate "TrustAsia TLS ECC Root CA" +# +# Issuer: CN=TrustAsia TLS ECC Root CA,O="TrustAsia Technologies, Inc.",C=CN +# Serial Number:36:74:e1:4d:7c:65:13:c9:ac:83:55:25:a0:3e:52:7e:2f:50:68:c7 +# Subject: CN=TrustAsia TLS ECC Root CA,O="TrustAsia Technologies, Inc.",C=CN +# Not Valid Before: Wed May 15 05:41:56 2024 +# Not Valid After : Sun May 15 05:41:55 2044 +# Fingerprint (SHA-256): C0:07:6B:9E:F0:53:1F:B1:A6:56:D6:7C:4E:BE:97:CD:5D:BA:A4:1E:F4:45:98:AC:C2:48:98:78:C9:2D:87:11 +# Fingerprint (SHA1): B5:EC:39:F3:A1:66:37:AE:C3:05:94:57:E2:BE:11:BE:B7:A1:7F:36 +CKA_CLASS CK_OBJECT_CLASS CKO_CERTIFICATE +CKA_TOKEN CK_BBOOL CK_TRUE +CKA_PRIVATE CK_BBOOL CK_FALSE +CKA_MODIFIABLE CK_BBOOL CK_FALSE +CKA_LABEL UTF8 "TrustAsia TLS ECC Root CA" +CKA_CERTIFICATE_TYPE CK_CERTIFICATE_TYPE CKC_X_509 +CKA_SUBJECT MULTILINE_OCTAL +\060\130\061\013\060\011\006\003\125\004\006\023\002\103\116\061 +\045\060\043\006\003\125\004\012\023\034\124\162\165\163\164\101 +\163\151\141\040\124\145\143\150\156\157\154\157\147\151\145\163 +\054\040\111\156\143\056\061\042\060\040\006\003\125\004\003\023 +\031\124\162\165\163\164\101\163\151\141\040\124\114\123\040\105 +\103\103\040\122\157\157\164\040\103\101 +END +CKA_ID UTF8 "0" +CKA_ISSUER MULTILINE_OCTAL +\060\130\061\013\060\011\006\003\125\004\006\023\002\103\116\061 +\045\060\043\006\003\125\004\012\023\034\124\162\165\163\164\101 +\163\151\141\040\124\145\143\150\156\157\154\157\147\151\145\163 +\054\040\111\156\143\056\061\042\060\040\006\003\125\004\003\023 +\031\124\162\165\163\164\101\163\151\141\040\124\114\123\040\105 +\103\103\040\122\157\157\164\040\103\101 +END +CKA_SERIAL_NUMBER MULTILINE_OCTAL +\002\024\066\164\341\115\174\145\023\311\254\203\125\045\240\076 +\122\176\057\120\150\307 +END +CKA_VALUE MULTILINE_OCTAL +\060\202\002\061\060\202\001\267\240\003\002\001\002\002\024\066 +\164\341\115\174\145\023\311\254\203\125\045\240\076\122\176\057 +\120\150\307\060\012\006\010\052\206\110\316\075\004\003\003\060 +\130\061\013\060\011\006\003\125\004\006\023\002\103\116\061\045 +\060\043\006\003\125\004\012\023\034\124\162\165\163\164\101\163 +\151\141\040\124\145\143\150\156\157\154\157\147\151\145\163\054 +\040\111\156\143\056\061\042\060\040\006\003\125\004\003\023\031 +\124\162\165\163\164\101\163\151\141\040\124\114\123\040\105\103 +\103\040\122\157\157\164\040\103\101\060\036\027\015\062\064\060 +\065\061\065\060\065\064\061\065\066\132\027\015\064\064\060\065 +\061\065\060\065\064\061\065\065\132\060\130\061\013\060\011\006 +\003\125\004\006\023\002\103\116\061\045\060\043\006\003\125\004 +\012\023\034\124\162\165\163\164\101\163\151\141\040\124\145\143 +\150\156\157\154\157\147\151\145\163\054\040\111\156\143\056\061 +\042\060\040\006\003\125\004\003\023\031\124\162\165\163\164\101 +\163\151\141\040\124\114\123\040\105\103\103\040\122\157\157\164 +\040\103\101\060\166\060\020\006\007\052\206\110\316\075\002\001 +\006\005\053\201\004\000\042\003\142\000\004\270\177\245\133\077 +\001\076\175\360\210\155\256\051\230\341\233\134\123\231\333\367 +\010\377\325\152\340\216\313\104\246\360\301\214\275\117\324\316 +\324\210\123\350\136\127\327\116\276\054\077\363\022\247\000\351 +\202\343\052\133\062\174\113\233\024\051\236\370\055\203\265\353 +\216\061\111\075\046\141\351\035\162\213\211\266\012\273\234\063 +\065\017\332\354\336\251\112\037\311\063\261\243\102\060\100\060 +\017\006\003\125\035\023\001\001\377\004\005\060\003\001\001\377 +\060\035\006\003\125\035\016\004\026\004\024\054\205\123\273\261 +\103\315\062\352\236\243\207\376\242\230\250\246\223\351\020\060 +\016\006\003\125\035\017\001\001\377\004\004\003\002\001\006\060 +\012\006\010\052\206\110\316\075\004\003\003\003\150\000\060\145 +\002\060\124\107\327\303\055\141\206\110\364\171\132\125\015\065 +\057\137\015\366\147\154\167\100\032\106\347\370\150\133\116\047 +\035\270\230\175\177\223\277\010\115\304\332\105\060\241\017\136 +\112\170\002\061\000\243\221\207\362\021\063\203\303\301\212\221 +\072\114\053\120\261\275\042\224\135\065\211\163\203\305\233\031 +\376\264\241\351\324\241\146\266\001\245\066\371\330\150\141\050 +\267\164\341\242\061 +END +CKA_NSS_MOZILLA_CA_POLICY CK_BBOOL CK_TRUE +CKA_NSS_SERVER_DISTRUST_AFTER CK_BBOOL CK_FALSE +CKA_NSS_EMAIL_DISTRUST_AFTER CK_BBOOL CK_FALSE + +# Trust for "TrustAsia TLS ECC Root CA" +# Issuer: CN=TrustAsia TLS ECC Root CA,O="TrustAsia Technologies, Inc.",C=CN +# Serial Number:36:74:e1:4d:7c:65:13:c9:ac:83:55:25:a0:3e:52:7e:2f:50:68:c7 +# Subject: CN=TrustAsia TLS ECC Root CA,O="TrustAsia Technologies, Inc.",C=CN +# Not Valid Before: Wed May 15 05:41:56 2024 +# Not Valid After : Sun May 15 05:41:55 2044 +# Fingerprint (SHA-256): C0:07:6B:9E:F0:53:1F:B1:A6:56:D6:7C:4E:BE:97:CD:5D:BA:A4:1E:F4:45:98:AC:C2:48:98:78:C9:2D:87:11 +# Fingerprint (SHA1): B5:EC:39:F3:A1:66:37:AE:C3:05:94:57:E2:BE:11:BE:B7:A1:7F:36 +CKA_CLASS CK_OBJECT_CLASS CKO_NSS_TRUST +CKA_TOKEN CK_BBOOL CK_TRUE +CKA_PRIVATE CK_BBOOL CK_FALSE +CKA_MODIFIABLE CK_BBOOL CK_FALSE +CKA_LABEL UTF8 "TrustAsia TLS ECC Root CA" +CKA_CERT_SHA1_HASH MULTILINE_OCTAL +\265\354\071\363\241\146\067\256\303\005\224\127\342\276\021\276 +\267\241\177\066 +END +CKA_CERT_MD5_HASH MULTILINE_OCTAL +\011\110\004\167\322\374\145\223\161\146\261\021\225\117\006\214 +END +CKA_ISSUER MULTILINE_OCTAL +\060\130\061\013\060\011\006\003\125\004\006\023\002\103\116\061 +\045\060\043\006\003\125\004\012\023\034\124\162\165\163\164\101 +\163\151\141\040\124\145\143\150\156\157\154\157\147\151\145\163 +\054\040\111\156\143\056\061\042\060\040\006\003\125\004\003\023 +\031\124\162\165\163\164\101\163\151\141\040\124\114\123\040\105 +\103\103\040\122\157\157\164\040\103\101 +END +CKA_SERIAL_NUMBER MULTILINE_OCTAL +\002\024\066\164\341\115\174\145\023\311\254\203\125\045\240\076 +\122\176\057\120\150\307 +END +CKA_TRUST_SERVER_AUTH CK_TRUST CKT_NSS_TRUSTED_DELEGATOR +CKA_TRUST_EMAIL_PROTECTION CK_TRUST CKT_NSS_MUST_VERIFY_TRUST +CKA_TRUST_CODE_SIGNING CK_TRUST CKT_NSS_MUST_VERIFY_TRUST +CKA_TRUST_STEP_UP_APPROVED CK_BBOOL CK_FALSE + +# +# Certificate "TrustAsia TLS RSA Root CA" +# +# Issuer: CN=TrustAsia TLS RSA Root CA,O="TrustAsia Technologies, Inc.",C=CN +# Serial Number:1c:18:d8:cf:e5:53:3f:22:35:46:53:54:24:3c:6c:47:d1:5c:4a:9c +# Subject: CN=TrustAsia TLS RSA Root CA,O="TrustAsia Technologies, Inc.",C=CN +# Not Valid Before: Wed May 15 05:41:57 2024 +# Not Valid After : Sun May 15 05:41:56 2044 +# Fingerprint (SHA-256): 06:C0:8D:7D:AF:D8:76:97:1E:B1:12:4F:E6:7F:84:7E:C0:C7:A1:58:D3:EA:53:CB:E9:40:E2:EA:97:91:F4:C3 +# Fingerprint (SHA1): A5:46:50:C5:62:EA:95:9A:1A:A7:04:6F:17:58:C7:29:53:3D:03:FA +CKA_CLASS CK_OBJECT_CLASS CKO_CERTIFICATE +CKA_TOKEN CK_BBOOL CK_TRUE +CKA_PRIVATE CK_BBOOL CK_FALSE +CKA_MODIFIABLE CK_BBOOL CK_FALSE +CKA_LABEL UTF8 "TrustAsia TLS RSA Root CA" +CKA_CERTIFICATE_TYPE CK_CERTIFICATE_TYPE CKC_X_509 +CKA_SUBJECT MULTILINE_OCTAL +\060\130\061\013\060\011\006\003\125\004\006\023\002\103\116\061 +\045\060\043\006\003\125\004\012\023\034\124\162\165\163\164\101 +\163\151\141\040\124\145\143\150\156\157\154\157\147\151\145\163 +\054\040\111\156\143\056\061\042\060\040\006\003\125\004\003\023 +\031\124\162\165\163\164\101\163\151\141\040\124\114\123\040\122 +\123\101\040\122\157\157\164\040\103\101 +END +CKA_ID UTF8 "0" +CKA_ISSUER MULTILINE_OCTAL +\060\130\061\013\060\011\006\003\125\004\006\023\002\103\116\061 +\045\060\043\006\003\125\004\012\023\034\124\162\165\163\164\101 +\163\151\141\040\124\145\143\150\156\157\154\157\147\151\145\163 +\054\040\111\156\143\056\061\042\060\040\006\003\125\004\003\023 +\031\124\162\165\163\164\101\163\151\141\040\124\114\123\040\122 +\123\101\040\122\157\157\164\040\103\101 +END +CKA_SERIAL_NUMBER MULTILINE_OCTAL +\002\024\034\030\330\317\345\123\077\042\065\106\123\124\044\074 +\154\107\321\134\112\234 +END +CKA_VALUE MULTILINE_OCTAL +\060\202\005\200\060\202\003\150\240\003\002\001\002\002\024\034 +\030\330\317\345\123\077\042\065\106\123\124\044\074\154\107\321 +\134\112\234\060\015\006\011\052\206\110\206\367\015\001\001\014 +\005\000\060\130\061\013\060\011\006\003\125\004\006\023\002\103 +\116\061\045\060\043\006\003\125\004\012\023\034\124\162\165\163 +\164\101\163\151\141\040\124\145\143\150\156\157\154\157\147\151 +\145\163\054\040\111\156\143\056\061\042\060\040\006\003\125\004 +\003\023\031\124\162\165\163\164\101\163\151\141\040\124\114\123 +\040\122\123\101\040\122\157\157\164\040\103\101\060\036\027\015 +\062\064\060\065\061\065\060\065\064\061\065\067\132\027\015\064 +\064\060\065\061\065\060\065\064\061\065\066\132\060\130\061\013 +\060\011\006\003\125\004\006\023\002\103\116\061\045\060\043\006 +\003\125\004\012\023\034\124\162\165\163\164\101\163\151\141\040 +\124\145\143\150\156\157\154\157\147\151\145\163\054\040\111\156 +\143\056\061\042\060\040\006\003\125\004\003\023\031\124\162\165 +\163\164\101\163\151\141\040\124\114\123\040\122\123\101\040\122 +\157\157\164\040\103\101\060\202\002\042\060\015\006\011\052\206 +\110\206\367\015\001\001\001\005\000\003\202\002\017\000\060\202 +\002\012\002\202\002\001\000\303\026\270\033\152\244\104\163\345 +\326\116\364\271\317\133\013\301\321\232\201\365\143\260\217\103 +\301\273\010\132\032\172\341\007\166\046\037\217\151\126\276\376 +\066\140\320\014\203\315\224\352\347\305\055\134\057\005\026\002 +\236\012\250\057\345\140\046\124\226\370\230\100\035\254\256\235 +\164\147\057\124\373\224\143\230\343\046\112\203\306\225\266\011 +\103\120\315\015\175\336\104\016\140\022\117\133\065\275\277\231 +\070\155\175\154\332\342\150\163\037\371\061\245\031\020\163\005 +\052\303\062\031\205\352\064\252\335\102\036\060\215\077\236\265 +\036\141\325\157\275\000\162\162\255\022\077\252\246\111\163\362 +\206\025\225\014\020\137\121\144\316\377\167\270\311\155\254\345 +\325\230\361\231\056\154\343\311\104\371\265\103\047\010\115\366 +\176\336\104\171\273\262\214\034\332\323\113\154\056\326\303\170 +\267\114\325\244\344\332\334\212\216\016\377\017\303\246\140\046 +\050\317\066\277\372\127\012\354\133\077\351\060\026\173\304\333 +\304\324\144\240\060\373\345\375\035\161\222\335\051\217\101\130 +\336\000\255\072\375\075\174\032\250\261\027\360\116\064\170\130 +\045\326\205\041\353\171\035\320\334\253\317\142\074\260\307\227 +\213\326\320\237\323\376\074\336\305\343\374\072\203\160\204\041 +\035\011\302\241\374\273\050\041\145\123\140\172\220\155\226\070 +\133\377\361\327\172\133\155\323\311\160\111\112\272\035\072\320 +\120\332\062\040\031\344\213\077\353\325\026\046\067\074\373\165 +\236\260\007\160\270\024\140\167\334\366\017\134\122\012\274\135 +\155\215\121\012\332\305\030\310\233\155\334\260\203\263\177\243 +\116\170\114\230\045\253\362\176\056\040\136\202\025\246\326\330 +\217\254\345\315\062\206\310\371\344\332\211\342\073\076\223\305 +\023\152\377\307\367\030\374\147\265\357\030\054\124\253\100\323 +\154\115\355\305\310\267\070\117\144\304\224\114\117\240\315\256 +\245\306\047\045\165\047\155\306\332\234\216\260\005\231\325\050 +\021\305\041\324\270\374\307\226\253\112\144\131\141\153\023\374 +\113\314\222\246\017\244\121\277\016\130\220\331\202\203\071\246 +\137\263\372\156\222\263\315\063\016\205\250\361\051\323\253\131 +\255\326\333\336\324\053\201\002\003\001\000\001\243\102\060\100 +\060\017\006\003\125\035\023\001\001\377\004\005\060\003\001\001 +\377\060\035\006\003\125\035\016\004\026\004\024\270\007\221\171 +\134\006\364\106\375\173\131\312\132\046\221\247\105\053\370\123 +\060\016\006\003\125\035\017\001\001\377\004\004\003\002\001\006 +\060\015\006\011\052\206\110\206\367\015\001\001\014\005\000\003 +\202\002\001\000\041\233\152\005\040\135\030\026\247\022\244\367 +\107\077\315\312\073\256\216\300\202\316\334\110\045\170\027\154 +\340\340\160\304\326\226\331\331\366\277\172\234\023\273\123\225 +\222\377\317\222\340\363\305\047\065\127\250\073\242\031\353\370 +\212\243\004\336\151\213\344\074\074\275\345\257\320\022\033\345 +\211\153\163\233\157\045\002\113\220\202\257\100\302\255\272\232 +\140\044\132\201\115\005\030\243\342\063\171\172\013\037\223\355 +\354\071\112\365\023\004\354\215\164\235\201\012\250\157\350\166 +\376\213\117\053\151\022\206\012\064\066\335\202\233\157\117\340 +\163\335\111\177\050\076\311\073\243\127\004\330\223\331\074\051 +\333\022\020\150\341\304\211\270\006\040\347\033\172\274\261\223 +\141\232\031\100\021\265\152\205\127\001\361\266\132\055\046\163 +\047\226\371\271\077\133\320\210\235\344\240\375\272\116\225\011 +\203\177\167\334\121\005\224\235\040\161\166\261\354\372\211\265 +\234\056\205\361\271\133\044\132\261\011\253\332\312\032\007\315 +\206\337\333\151\333\264\110\030\000\055\274\242\304\211\105\043 +\245\016\341\104\145\076\212\301\152\060\035\342\140\370\072\252 +\207\011\102\271\201\222\334\045\210\230\211\361\174\065\361\142 +\312\356\171\370\031\057\376\137\370\333\207\316\352\250\017\014 +\246\350\013\240\074\340\153\224\230\234\177\255\006\346\034\352 +\102\020\137\342\046\025\074\067\071\367\327\274\356\270\345\357 +\003\356\275\042\373\050\206\064\130\304\132\177\141\242\170\017 +\136\363\312\235\274\033\074\247\310\055\366\247\042\021\312\003 +\330\347\147\012\212\016\313\065\216\064\071\330\310\352\215\237 +\144\340\067\260\154\327\305\217\301\220\141\046\333\044\214\036 +\112\300\210\337\065\220\316\077\002\364\144\141\336\307\310\007 +\225\336\062\030\072\217\242\102\100\044\345\326\063\135\174\256 +\357\261\115\117\324\127\220\065\152\334\256\004\227\111\211\064 +\227\056\060\004\347\230\367\333\013\001\220\301\037\012\077\370 +\302\376\116\372\333\232\163\163\026\274\005\270\171\330\131\257 +\006\347\077\147\070\071\261\174\253\224\340\051\024\246\050\362 +\337\155\342\232\333\124\103\370\107\130\243\135\164\025\234\301 +\253\260\307\064 +END +CKA_NSS_MOZILLA_CA_POLICY CK_BBOOL CK_TRUE +CKA_NSS_SERVER_DISTRUST_AFTER CK_BBOOL CK_FALSE +CKA_NSS_EMAIL_DISTRUST_AFTER CK_BBOOL CK_FALSE + +# Trust for "TrustAsia TLS RSA Root CA" +# Issuer: CN=TrustAsia TLS RSA Root CA,O="TrustAsia Technologies, Inc.",C=CN +# Serial Number:1c:18:d8:cf:e5:53:3f:22:35:46:53:54:24:3c:6c:47:d1:5c:4a:9c +# Subject: CN=TrustAsia TLS RSA Root CA,O="TrustAsia Technologies, Inc.",C=CN +# Not Valid Before: Wed May 15 05:41:57 2024 +# Not Valid After : Sun May 15 05:41:56 2044 +# Fingerprint (SHA-256): 06:C0:8D:7D:AF:D8:76:97:1E:B1:12:4F:E6:7F:84:7E:C0:C7:A1:58:D3:EA:53:CB:E9:40:E2:EA:97:91:F4:C3 +# Fingerprint (SHA1): A5:46:50:C5:62:EA:95:9A:1A:A7:04:6F:17:58:C7:29:53:3D:03:FA +CKA_CLASS CK_OBJECT_CLASS CKO_NSS_TRUST +CKA_TOKEN CK_BBOOL CK_TRUE +CKA_PRIVATE CK_BBOOL CK_FALSE +CKA_MODIFIABLE CK_BBOOL CK_FALSE +CKA_LABEL UTF8 "TrustAsia TLS RSA Root CA" +CKA_CERT_SHA1_HASH MULTILINE_OCTAL +\245\106\120\305\142\352\225\232\032\247\004\157\027\130\307\051 +\123\075\003\372 +END +CKA_CERT_MD5_HASH MULTILINE_OCTAL +\073\236\303\206\017\064\074\153\305\106\304\216\035\347\031\022 +END +CKA_ISSUER MULTILINE_OCTAL +\060\130\061\013\060\011\006\003\125\004\006\023\002\103\116\061 +\045\060\043\006\003\125\004\012\023\034\124\162\165\163\164\101 +\163\151\141\040\124\145\143\150\156\157\154\157\147\151\145\163 +\054\040\111\156\143\056\061\042\060\040\006\003\125\004\003\023 +\031\124\162\165\163\164\101\163\151\141\040\124\114\123\040\122 +\123\101\040\122\157\157\164\040\103\101 +END +CKA_SERIAL_NUMBER MULTILINE_OCTAL +\002\024\034\030\330\317\345\123\077\042\065\106\123\124\044\074 +\154\107\321\134\112\234 +END +CKA_TRUST_SERVER_AUTH CK_TRUST CKT_NSS_TRUSTED_DELEGATOR +CKA_TRUST_EMAIL_PROTECTION CK_TRUST CKT_NSS_MUST_VERIFY_TRUST +CKA_TRUST_CODE_SIGNING CK_TRUST CKT_NSS_MUST_VERIFY_TRUST +CKA_TRUST_STEP_UP_APPROVED CK_BBOOL CK_FALSE + # # Certificate "D-TRUST EV Root CA 2 2023" # @@ -26138,3 +26578,343 @@ CKA_TRUST_SERVER_AUTH CK_TRUST CKT_NSS_TRUSTED_DELEGATOR CKA_TRUST_EMAIL_PROTECTION CK_TRUST CKT_NSS_MUST_VERIFY_TRUST CKA_TRUST_CODE_SIGNING CK_TRUST CKT_NSS_MUST_VERIFY_TRUST CKA_TRUST_STEP_UP_APPROVED CK_BBOOL CK_FALSE + +# +# Certificate "SwissSign RSA SMIME Root CA 2022 - 1" +# +# Issuer: CN=SwissSign RSA SMIME Root CA 2022 - 1,O=SwissSign AG,C=CH +# Serial Number:46:0e:d4:01:71:90:a0:1a:83:2c:4a:42:10:28:15:d2:61:1b:ad:32 +# Subject: CN=SwissSign RSA SMIME Root CA 2022 - 1,O=SwissSign AG,C=CH +# Not Valid Before: Wed Jun 08 10:53:13 2022 +# Not Valid After : Sat Jun 08 10:53:13 2047 +# Fingerprint (SHA-256): 9A:12:C3:92:BF:E5:78:91:A0:C5:45:30:9D:4D:9F:D5:67:E4:80:CB:61:3D:63:42:27:8B:19:5C:79:A7:93:1F +# Fingerprint (SHA1): 14:D7:65:62:74:10:50:47:9F:8B:32:C6:86:8A:18:FA:E1:19:99:B0 +CKA_CLASS CK_OBJECT_CLASS CKO_CERTIFICATE +CKA_TOKEN CK_BBOOL CK_TRUE +CKA_PRIVATE CK_BBOOL CK_FALSE +CKA_MODIFIABLE CK_BBOOL CK_FALSE +CKA_LABEL UTF8 "SwissSign RSA SMIME Root CA 2022 - 1" +CKA_CERTIFICATE_TYPE CK_CERTIFICATE_TYPE CKC_X_509 +CKA_SUBJECT MULTILINE_OCTAL +\060\123\061\013\060\011\006\003\125\004\006\023\002\103\110\061 +\025\060\023\006\003\125\004\012\023\014\123\167\151\163\163\123 +\151\147\156\040\101\107\061\055\060\053\006\003\125\004\003\023 +\044\123\167\151\163\163\123\151\147\156\040\122\123\101\040\123 +\115\111\115\105\040\122\157\157\164\040\103\101\040\062\060\062 +\062\040\055\040\061 +END +CKA_ID UTF8 "0" +CKA_ISSUER MULTILINE_OCTAL +\060\123\061\013\060\011\006\003\125\004\006\023\002\103\110\061 +\025\060\023\006\003\125\004\012\023\014\123\167\151\163\163\123 +\151\147\156\040\101\107\061\055\060\053\006\003\125\004\003\023 +\044\123\167\151\163\163\123\151\147\156\040\122\123\101\040\123 +\115\111\115\105\040\122\157\157\164\040\103\101\040\062\060\062 +\062\040\055\040\061 +END +CKA_SERIAL_NUMBER MULTILINE_OCTAL +\002\024\106\016\324\001\161\220\240\032\203\054\112\102\020\050 +\025\322\141\033\255\062 +END +CKA_VALUE MULTILINE_OCTAL +\060\202\005\227\060\202\003\177\240\003\002\001\002\002\024\106 +\016\324\001\161\220\240\032\203\054\112\102\020\050\025\322\141 +\033\255\062\060\015\006\011\052\206\110\206\367\015\001\001\013 +\005\000\060\123\061\013\060\011\006\003\125\004\006\023\002\103 +\110\061\025\060\023\006\003\125\004\012\023\014\123\167\151\163 +\163\123\151\147\156\040\101\107\061\055\060\053\006\003\125\004 +\003\023\044\123\167\151\163\163\123\151\147\156\040\122\123\101 +\040\123\115\111\115\105\040\122\157\157\164\040\103\101\040\062 +\060\062\062\040\055\040\061\060\036\027\015\062\062\060\066\060 +\070\061\060\065\063\061\063\132\027\015\064\067\060\066\060\070 +\061\060\065\063\061\063\132\060\123\061\013\060\011\006\003\125 +\004\006\023\002\103\110\061\025\060\023\006\003\125\004\012\023 +\014\123\167\151\163\163\123\151\147\156\040\101\107\061\055\060 +\053\006\003\125\004\003\023\044\123\167\151\163\163\123\151\147 +\156\040\122\123\101\040\123\115\111\115\105\040\122\157\157\164 +\040\103\101\040\062\060\062\062\040\055\040\061\060\202\002\042 +\060\015\006\011\052\206\110\206\367\015\001\001\001\005\000\003 +\202\002\017\000\060\202\002\012\002\202\002\001\000\324\373\372 +\077\206\242\231\160\011\072\311\326\241\116\001\316\106\265\055 +\044\110\015\105\351\254\311\032\327\062\200\244\346\323\312\326 +\362\051\067\354\232\054\326\201\316\346\033\235\261\017\101\337 +\130\323\137\252\240\171\131\013\214\255\371\305\371\034\373\303 +\351\065\100\164\303\301\014\313\147\373\234\136\014\043\371\320 +\057\372\114\202\105\034\265\365\037\370\031\156\332\223\267\116 +\150\203\176\142\055\035\214\225\037\160\331\316\132\214\041\135 +\250\276\010\251\126\155\331\367\271\220\231\307\073\327\275\236 +\056\311\147\127\056\041\316\360\062\203\373\004\001\371\267\202 +\104\015\262\313\172\011\241\110\076\117\300\013\152\033\033\354 +\317\035\236\177\006\220\254\050\005\013\250\346\124\073\242\247 +\054\071\023\231\250\373\064\353\365\360\275\001\330\210\021\310 +\256\272\031\002\111\002\334\212\054\264\275\141\140\354\235\170 +\134\152\213\057\027\206\136\362\117\223\000\153\135\305\262\251 +\245\030\352\346\155\176\127\005\373\007\374\274\174\241\103\261 +\146\034\250\147\373\253\067\366\252\252\270\172\101\367\071\325 +\214\237\256\162\066\047\344\142\306\103\231\361\241\101\351\377 +\211\237\017\111\257\311\024\006\027\047\144\071\015\245\264\052 +\133\354\074\373\237\305\137\306\073\135\046\031\362\204\051\270 +\225\104\011\122\375\154\060\320\142\217\050\245\201\226\053\224 +\057\046\165\344\011\214\012\337\070\363\176\135\070\216\202\232 +\214\334\236\256\316\022\102\072\042\362\065\215\117\322\032\310 +\111\063\013\372\066\077\377\054\153\152\040\161\114\315\255\020 +\077\151\062\204\216\134\356\123\210\104\342\335\314\003\060\222 +\206\171\220\052\113\165\373\122\142\304\364\157\115\367\333\043 +\241\030\335\105\123\027\232\143\044\110\211\042\315\176\233\001 +\034\200\050\003\217\133\053\372\037\343\174\031\275\346\217\261 +\100\166\240\362\307\067\262\062\224\020\062\315\247\023\227\361 +\073\054\172\064\042\173\164\072\100\342\257\202\370\301\301\170 +\374\137\344\065\177\300\364\114\360\114\262\352\207\003\162\045 +\131\203\052\010\244\121\336\317\356\250\347\350\100\077\210\360 +\066\253\011\116\330\240\250\177\363\341\011\333\271\002\003\001 +\000\001\243\143\060\141\060\017\006\003\125\035\023\001\001\377 +\004\005\060\003\001\001\377\060\016\006\003\125\035\017\001\001 +\377\004\004\003\002\001\006\060\037\006\003\125\035\043\004\030 +\060\026\200\024\314\056\255\211\214\203\343\100\243\045\151\245 +\352\222\175\322\067\072\307\306\060\035\006\003\125\035\016\004 +\026\004\024\314\056\255\211\214\203\343\100\243\045\151\245\352 +\222\175\322\067\072\307\306\060\015\006\011\052\206\110\206\367 +\015\001\001\013\005\000\003\202\002\001\000\000\007\146\026\245 +\355\307\271\277\274\310\221\255\130\355\136\022\005\263\366\106 +\233\173\344\204\020\057\007\261\132\136\062\156\155\000\360\136 +\307\232\142\071\207\115\344\074\363\276\366\272\152\123\154\204 +\155\007\324\141\312\147\146\276\233\076\105\060\235\121\322\327 +\132\102\256\235\222\016\043\253\320\025\224\076\250\232\312\233 +\355\301\302\137\041\243\231\200\374\271\246\141\016\120\200\200 +\047\307\350\175\240\324\224\172\230\003\347\356\102\213\363\122 +\120\134\357\276\260\166\132\034\011\241\277\203\300\036\166\145 +\320\045\101\133\151\221\223\167\371\214\113\360\054\333\233\006 +\000\027\203\372\132\224\264\206\211\357\274\012\013\022\370\006 +\115\322\163\342\221\070\135\271\152\113\117\247\257\332\046\045 +\222\043\032\174\037\047\015\221\204\357\027\162\366\106\353\153 +\073\067\251\324\325\232\143\272\136\171\214\052\265\233\242\064 +\265\314\226\047\371\171\010\074\177\155\340\377\174\334\305\046 +\257\303\241\172\151\026\335\207\065\163\306\153\061\153\126\001 +\055\117\231\331\356\312\341\320\203\336\266\205\347\026\370\321 +\256\063\110\373\032\317\135\137\212\204\141\164\007\157\031\260 +\121\041\262\241\004\261\173\123\217\357\242\105\255\037\230\335 +\371\152\004\154\252\066\305\257\214\352\051\111\112\347\377\127 +\263\306\074\021\111\030\027\346\146\205\341\350\043\362\213\016 +\074\223\304\077\166\116\176\142\242\051\313\026\060\336\054\151 +\257\221\247\342\127\345\063\114\277\330\216\007\242\177\241\065 +\106\151\112\250\040\051\377\353\336\040\225\131\261\256\115\060 +\153\330\232\264\003\037\302\070\344\040\325\016\326\032\060\114 +\150\234\214\340\355\241\017\216\102\135\353\200\052\022\171\246 +\027\027\264\225\052\216\064\221\247\275\321\254\250\032\103\041 +\213\336\357\340\254\106\045\145\012\306\053\241\233\076\042\350 +\030\070\265\046\366\054\312\010\040\336\060\321\255\124\375\334 +\110\025\237\315\247\101\327\067\070\205\060\117\022\210\103\064 +\203\327\052\115\324\000\127\134\345\312\360\335\341\222\050\053 +\030\165\275\107\244\102\023\351\126\132\215\112\136\316\016\165 +\065\200\163\031\017\054\051\213\273\225\270 +END +CKA_NSS_MOZILLA_CA_POLICY CK_BBOOL CK_TRUE +CKA_NSS_SERVER_DISTRUST_AFTER CK_BBOOL CK_FALSE +CKA_NSS_EMAIL_DISTRUST_AFTER CK_BBOOL CK_FALSE + +# Trust for "SwissSign RSA SMIME Root CA 2022 - 1" +# Issuer: CN=SwissSign RSA SMIME Root CA 2022 - 1,O=SwissSign AG,C=CH +# Serial Number:46:0e:d4:01:71:90:a0:1a:83:2c:4a:42:10:28:15:d2:61:1b:ad:32 +# Subject: CN=SwissSign RSA SMIME Root CA 2022 - 1,O=SwissSign AG,C=CH +# Not Valid Before: Wed Jun 08 10:53:13 2022 +# Not Valid After : Sat Jun 08 10:53:13 2047 +# Fingerprint (SHA-256): 9A:12:C3:92:BF:E5:78:91:A0:C5:45:30:9D:4D:9F:D5:67:E4:80:CB:61:3D:63:42:27:8B:19:5C:79:A7:93:1F +# Fingerprint (SHA1): 14:D7:65:62:74:10:50:47:9F:8B:32:C6:86:8A:18:FA:E1:19:99:B0 +CKA_CLASS CK_OBJECT_CLASS CKO_NSS_TRUST +CKA_TOKEN CK_BBOOL CK_TRUE +CKA_PRIVATE CK_BBOOL CK_FALSE +CKA_MODIFIABLE CK_BBOOL CK_FALSE +CKA_LABEL UTF8 "SwissSign RSA SMIME Root CA 2022 - 1" +CKA_CERT_SHA1_HASH MULTILINE_OCTAL +\024\327\145\142\164\020\120\107\237\213\062\306\206\212\030\372 +\341\031\231\260 +END +CKA_CERT_MD5_HASH MULTILINE_OCTAL +\232\063\065\330\302\042\023\366\370\153\226\000\022\032\110\141 +END +CKA_ISSUER MULTILINE_OCTAL +\060\123\061\013\060\011\006\003\125\004\006\023\002\103\110\061 +\025\060\023\006\003\125\004\012\023\014\123\167\151\163\163\123 +\151\147\156\040\101\107\061\055\060\053\006\003\125\004\003\023 +\044\123\167\151\163\163\123\151\147\156\040\122\123\101\040\123 +\115\111\115\105\040\122\157\157\164\040\103\101\040\062\060\062 +\062\040\055\040\061 +END +CKA_SERIAL_NUMBER MULTILINE_OCTAL +\002\024\106\016\324\001\161\220\240\032\203\054\112\102\020\050 +\025\322\141\033\255\062 +END +CKA_TRUST_SERVER_AUTH CK_TRUST CKT_NSS_MUST_VERIFY_TRUST +CKA_TRUST_EMAIL_PROTECTION CK_TRUST CKT_NSS_TRUSTED_DELEGATOR +CKA_TRUST_CODE_SIGNING CK_TRUST CKT_NSS_MUST_VERIFY_TRUST +CKA_TRUST_STEP_UP_APPROVED CK_BBOOL CK_FALSE + +# +# Certificate "SwissSign RSA TLS Root CA 2022 - 1" +# +# Issuer: CN=SwissSign RSA TLS Root CA 2022 - 1,O=SwissSign AG,C=CH +# Serial Number:43:fa:0c:5f:4e:1b:80:18:44:ef:d1:b4:4f:35:1f:44:f4:80:ed:cb +# Subject: CN=SwissSign RSA TLS Root CA 2022 - 1,O=SwissSign AG,C=CH +# Not Valid Before: Wed Jun 08 11:08:22 2022 +# Not Valid After : Sat Jun 08 11:08:22 2047 +# Fingerprint (SHA-256): 19:31:44:F4:31:E0:FD:DB:74:07:17:D4:DE:92:6A:57:11:33:88:4B:43:60:D3:0E:27:29:13:CB:E6:60:CE:41 +# Fingerprint (SHA1): 81:34:0A:BE:4C:CD:CE:CC:E7:7D:CC:8A:D4:57:E2:45:A0:77:5D:CE +CKA_CLASS CK_OBJECT_CLASS CKO_CERTIFICATE +CKA_TOKEN CK_BBOOL CK_TRUE +CKA_PRIVATE CK_BBOOL CK_FALSE +CKA_MODIFIABLE CK_BBOOL CK_FALSE +CKA_LABEL UTF8 "SwissSign RSA TLS Root CA 2022 - 1" +CKA_CERTIFICATE_TYPE CK_CERTIFICATE_TYPE CKC_X_509 +CKA_SUBJECT MULTILINE_OCTAL +\060\121\061\013\060\011\006\003\125\004\006\023\002\103\110\061 +\025\060\023\006\003\125\004\012\023\014\123\167\151\163\163\123 +\151\147\156\040\101\107\061\053\060\051\006\003\125\004\003\023 +\042\123\167\151\163\163\123\151\147\156\040\122\123\101\040\124 +\114\123\040\122\157\157\164\040\103\101\040\062\060\062\062\040 +\055\040\061 +END +CKA_ID UTF8 "0" +CKA_ISSUER MULTILINE_OCTAL +\060\121\061\013\060\011\006\003\125\004\006\023\002\103\110\061 +\025\060\023\006\003\125\004\012\023\014\123\167\151\163\163\123 +\151\147\156\040\101\107\061\053\060\051\006\003\125\004\003\023 +\042\123\167\151\163\163\123\151\147\156\040\122\123\101\040\124 +\114\123\040\122\157\157\164\040\103\101\040\062\060\062\062\040 +\055\040\061 +END +CKA_SERIAL_NUMBER MULTILINE_OCTAL +\002\024\103\372\014\137\116\033\200\030\104\357\321\264\117\065 +\037\104\364\200\355\313 +END +CKA_VALUE MULTILINE_OCTAL +\060\202\005\223\060\202\003\173\240\003\002\001\002\002\024\103 +\372\014\137\116\033\200\030\104\357\321\264\117\065\037\104\364 +\200\355\313\060\015\006\011\052\206\110\206\367\015\001\001\013 +\005\000\060\121\061\013\060\011\006\003\125\004\006\023\002\103 +\110\061\025\060\023\006\003\125\004\012\023\014\123\167\151\163 +\163\123\151\147\156\040\101\107\061\053\060\051\006\003\125\004 +\003\023\042\123\167\151\163\163\123\151\147\156\040\122\123\101 +\040\124\114\123\040\122\157\157\164\040\103\101\040\062\060\062 +\062\040\055\040\061\060\036\027\015\062\062\060\066\060\070\061 +\061\060\070\062\062\132\027\015\064\067\060\066\060\070\061\061 +\060\070\062\062\132\060\121\061\013\060\011\006\003\125\004\006 +\023\002\103\110\061\025\060\023\006\003\125\004\012\023\014\123 +\167\151\163\163\123\151\147\156\040\101\107\061\053\060\051\006 +\003\125\004\003\023\042\123\167\151\163\163\123\151\147\156\040 +\122\123\101\040\124\114\123\040\122\157\157\164\040\103\101\040 +\062\060\062\062\040\055\040\061\060\202\002\042\060\015\006\011 +\052\206\110\206\367\015\001\001\001\005\000\003\202\002\017\000 +\060\202\002\012\002\202\002\001\000\313\052\150\342\013\303\127 +\274\065\143\274\160\245\073\363\214\074\116\127\226\156\303\116 +\066\244\366\002\312\036\252\256\270\336\250\257\035\166\332\272 +\065\320\221\160\007\337\263\006\362\212\362\056\125\121\173\273 +\054\044\313\177\222\046\200\243\264\224\366\202\241\244\350\372 +\165\035\131\363\007\152\141\144\342\306\214\225\257\243\273\216 +\157\126\317\161\314\136\201\141\015\155\362\253\002\056\244\227 +\345\161\374\212\260\221\040\133\234\164\122\155\256\025\047\131 +\170\362\011\312\145\016\177\313\364\353\347\334\251\114\167\366 +\053\026\004\225\256\234\161\245\077\052\332\101\102\347\074\204 +\020\364\341\075\214\153\342\053\221\107\125\117\270\126\276\105 +\336\042\121\115\116\050\331\137\031\101\006\217\016\115\006\340 +\160\100\043\001\152\344\313\023\233\163\254\115\024\110\222\055 +\376\155\247\370\207\153\171\165\341\276\020\261\252\210\100\131 +\124\327\317\304\320\233\104\263\070\151\144\214\201\321\043\176 +\252\071\074\073\017\237\112\173\202\312\153\157\312\042\076\061 +\320\260\320\052\034\222\212\217\330\031\234\107\344\076\014\271 +\302\315\276\101\014\370\244\107\005\333\301\027\060\070\072\151 +\334\315\303\151\043\375\232\017\002\316\020\152\316\312\370\271 +\051\243\066\211\206\256\013\300\117\143\271\006\131\111\136\016 +\301\151\263\012\363\167\176\056\235\214\263\047\230\322\231\215 +\045\247\037\206\263\246\124\160\070\374\175\135\350\117\203\014 +\321\223\345\022\344\124\332\076\362\255\072\336\076\074\105\360 +\050\017\006\271\341\333\227\173\231\105\236\335\376\225\131\004 +\057\165\077\323\256\211\231\206\254\024\264\250\204\372\310\135 +\073\033\130\223\301\027\224\125\310\013\343\202\171\204\237\363 +\000\204\064\356\334\061\325\217\362\372\117\226\114\006\252\170 +\373\336\144\242\043\315\037\076\305\214\274\067\124\016\273\132 +\162\125\357\310\133\265\162\370\170\337\067\040\114\127\221\163 +\222\163\254\030\167\103\202\040\151\354\351\254\051\106\345\013 +\116\370\067\163\211\226\212\034\155\275\357\276\330\266\364\312 +\300\375\107\360\256\013\130\040\305\310\035\066\256\227\215\120 +\203\046\044\051\367\235\073\017\005\002\003\001\000\001\243\143 +\060\141\060\017\006\003\125\035\023\001\001\377\004\005\060\003 +\001\001\377\060\016\006\003\125\035\017\001\001\377\004\004\003 +\002\001\006\060\037\006\003\125\035\043\004\030\060\026\200\024 +\157\216\142\213\223\103\260\341\100\366\247\303\375\361\017\270 +\017\025\070\245\060\035\006\003\125\035\016\004\026\004\024\157 +\216\142\213\223\103\260\341\100\366\247\303\375\361\017\270\017 +\025\070\245\060\015\006\011\052\206\110\206\367\015\001\001\013 +\005\000\003\202\002\001\000\254\054\051\101\175\372\134\365\032 +\225\030\277\054\251\212\251\044\124\165\365\270\100\253\313\250 +\044\121\053\030\077\143\251\256\230\126\053\005\103\042\243\267 +\327\106\236\300\052\022\075\216\226\226\100\337\014\063\213\153 +\067\221\072\225\273\071\051\155\300\002\154\212\224\013\007\002 +\115\030\076\373\373\173\365\166\075\233\366\136\060\006\130\063 +\036\252\170\325\346\124\004\072\262\202\011\215\316\026\063\131 +\105\050\361\245\243\227\016\103\043\375\013\040\200\220\377\343 +\046\317\270\144\221\345\005\217\023\240\166\015\327\067\014\020 +\210\226\364\076\276\225\275\361\303\175\360\243\303\171\107\013 +\134\222\025\143\355\122\165\212\347\106\151\313\121\125\013\052 +\114\365\362\144\117\251\134\377\147\062\216\125\055\062\202\034 +\200\057\152\221\370\313\274\176\030\242\046\250\056\243\123\050 +\207\355\127\345\145\172\116\000\112\133\116\123\311\142\066\275 +\302\216\133\353\314\156\047\201\030\131\213\104\143\237\325\014 +\145\364\051\145\177\221\054\345\177\176\350\211\317\217\040\313 +\155\007\102\021\121\046\062\212\056\072\107\023\270\215\275\107 +\015\011\360\026\244\355\226\206\056\031\330\276\214\072\350\105 +\056\021\272\256\132\347\271\277\261\314\217\340\240\377\270\263 +\321\205\173\171\146\243\071\265\073\146\330\100\276\317\267\147 +\213\110\311\031\045\125\374\275\215\317\136\332\116\246\362\151 +\316\375\177\114\167\320\301\106\065\230\134\043\233\002\105\103 +\224\132\335\274\107\255\042\376\272\136\057\221\051\051\206\173 +\041\336\156\144\267\313\015\217\067\133\243\010\152\353\364\335 +\002\217\120\003\002\261\270\067\150\226\120\353\270\137\324\050 +\212\245\042\014\212\204\360\131\056\325\067\321\141\345\102\163 +\130\052\201\367\166\333\342\342\115\015\137\366\267\276\005\264 +\256\116\015\336\026\075\003\201\263\046\136\113\270\113\000\317 +\377\214\027\272\154\140\055\047\207\067\044\346\172\140\057\265 +\323\203\004\252\117\103\165\242\301\203\262\047\230\053\261\016 +\200\272\300\205\136\102\271\337\261\140\221\323\353\030\176\160 +\170\256\166\203\276\161\132\320\220\343\312\301\026\045\147\112 +\360\266\173\272\341\234\331 +END +CKA_NSS_MOZILLA_CA_POLICY CK_BBOOL CK_TRUE +CKA_NSS_SERVER_DISTRUST_AFTER CK_BBOOL CK_FALSE +CKA_NSS_EMAIL_DISTRUST_AFTER CK_BBOOL CK_FALSE + +# Trust for "SwissSign RSA TLS Root CA 2022 - 1" +# Issuer: CN=SwissSign RSA TLS Root CA 2022 - 1,O=SwissSign AG,C=CH +# Serial Number:43:fa:0c:5f:4e:1b:80:18:44:ef:d1:b4:4f:35:1f:44:f4:80:ed:cb +# Subject: CN=SwissSign RSA TLS Root CA 2022 - 1,O=SwissSign AG,C=CH +# Not Valid Before: Wed Jun 08 11:08:22 2022 +# Not Valid After : Sat Jun 08 11:08:22 2047 +# Fingerprint (SHA-256): 19:31:44:F4:31:E0:FD:DB:74:07:17:D4:DE:92:6A:57:11:33:88:4B:43:60:D3:0E:27:29:13:CB:E6:60:CE:41 +# Fingerprint (SHA1): 81:34:0A:BE:4C:CD:CE:CC:E7:7D:CC:8A:D4:57:E2:45:A0:77:5D:CE +CKA_CLASS CK_OBJECT_CLASS CKO_NSS_TRUST +CKA_TOKEN CK_BBOOL CK_TRUE +CKA_PRIVATE CK_BBOOL CK_FALSE +CKA_MODIFIABLE CK_BBOOL CK_FALSE +CKA_LABEL UTF8 "SwissSign RSA TLS Root CA 2022 - 1" +CKA_CERT_SHA1_HASH MULTILINE_OCTAL +\201\064\012\276\114\315\316\314\347\175\314\212\324\127\342\105 +\240\167\135\316 +END +CKA_CERT_MD5_HASH MULTILINE_OCTAL +\026\056\344\031\166\201\205\272\216\221\130\361\025\357\162\071 +END +CKA_ISSUER MULTILINE_OCTAL +\060\121\061\013\060\011\006\003\125\004\006\023\002\103\110\061 +\025\060\023\006\003\125\004\012\023\014\123\167\151\163\163\123 +\151\147\156\040\101\107\061\053\060\051\006\003\125\004\003\023 +\042\123\167\151\163\163\123\151\147\156\040\122\123\101\040\124 +\114\123\040\122\157\157\164\040\103\101\040\062\060\062\062\040 +\055\040\061 +END +CKA_SERIAL_NUMBER MULTILINE_OCTAL +\002\024\103\372\014\137\116\033\200\030\104\357\321\264\117\065 +\037\104\364\200\355\313 +END +CKA_TRUST_SERVER_AUTH CK_TRUST CKT_NSS_TRUSTED_DELEGATOR +CKA_TRUST_EMAIL_PROTECTION CK_TRUST CKT_NSS_MUST_VERIFY_TRUST +CKA_TRUST_CODE_SIGNING CK_TRUST CKT_NSS_MUST_VERIFY_TRUST +CKA_TRUST_STEP_UP_APPROVED CK_BBOOL CK_FALSE diff --git a/tools/doc/type-parser.mjs b/tools/doc/type-parser.mjs index b6d0a69d46743b..361487ab254ac2 100644 --- a/tools/doc/type-parser.mjs +++ b/tools/doc/type-parser.mjs @@ -93,12 +93,14 @@ const customTypesMap = { 'CryptoKey': 'webcrypto.html#class-cryptokey', 'CryptoKeyPair': 'webcrypto.html#class-cryptokeypair', 'Crypto': 'webcrypto.html#class-crypto', + 'EncapsulatedBits': 'webcrypto.html#class-encapsulatedbits', + 'EncapsulatedKey': 'webcrypto.html#class-encapsulatedkey', 'SubtleCrypto': 'webcrypto.html#class-subtlecrypto', 'RsaOaepParams': 'webcrypto.html#class-rsaoaepparams', 'AesCtrParams': 'webcrypto.html#class-aesctrparams', 'AesCbcParams': 'webcrypto.html#class-aescbcparams', 'AesDerivedKeyParams': 'webcrypto.html#class-aesderivedkeyparams', - 'AesGcmParams': 'webcrypto.html#class-aesgcmparams', + 'AeadParams': 'webcrypto.html#class-aeadparams', 'EcdhKeyDeriveParams': 'webcrypto.html#class-ecdhkeyderiveparams', 'HkdfParams': 'webcrypto.html#class-hkdfparams', 'KeyAlgorithm': 'webcrypto.html#class-keyalgorithm', @@ -120,6 +122,8 @@ const customTypesMap = { 'EcdsaParams': 'webcrypto.html#class-ecdsaparams', 'RsaPssParams': 'webcrypto.html#class-rsapssparams', 'Ed448Params': 'webcrypto.html#class-ed448params', + 'ContextParams': 'webcrypto.html#class-contextparams', + 'CShakeParams': 'webcrypto.html#class-cshakeparams', 'dgram.Socket': 'dgram.html#class-dgramsocket', diff --git a/tools/eslint/package-lock.json b/tools/eslint/package-lock.json index 5e090c1887846f..2e33138a2109a8 100644 --- a/tools/eslint/package-lock.json +++ b/tools/eslint/package-lock.json @@ -481,9 +481,9 @@ } }, "node_modules/@eslint/plugin-kit": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.3.3.tgz", - "integrity": "sha512-1+WqvgNMhmlAambTvT3KPtCl/Ibr68VldY2XY40SL1CE0ZXiakFR/cbTspaF5HsnpDMvcYYoJHfl4980NBjGag==", + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.3.4.tgz", + "integrity": "sha512-Ul5l+lHEcw3L5+k8POx6r74mxEYKG5kOb6Xpy2gCRW6zweT6TEhAf8vhxGgjhqrd/VO/Dirhsb+1hNpD1ue9hw==", "license": "Apache-2.0", "dependencies": { "@eslint/core": "^0.15.1", diff --git a/tools/gyp/CHANGELOG.md b/tools/gyp/CHANGELOG.md index c1c1c5909dc729..7af27a435c409b 100644 --- a/tools/gyp/CHANGELOG.md +++ b/tools/gyp/CHANGELOG.md @@ -1,5 +1,13 @@ # Changelog +## [0.20.3](https://github.com/nodejs/gyp-next/compare/v0.20.2...v0.20.3) (2025-08-20) + + +### Bug Fixes + +* compilation failure on the OpenHarmony platform ([#301](https://github.com/nodejs/gyp-next/issues/301)) ([0cf7a14](https://github.com/nodejs/gyp-next/commit/0cf7a142be06f686b8b42849791de902f177cf9f)) +* make xcode_emulation handle `xcodebuild` not in the `PATH` ([#303](https://github.com/nodejs/gyp-next/issues/303)) ([8224dee](https://github.com/nodejs/gyp-next/commit/8224deef984add7e7afe846cfb82c9d3fa6da1fb)) + ## [0.20.2](https://github.com/nodejs/gyp-next/compare/v0.20.1...v0.20.2) (2025-06-22) diff --git a/tools/gyp/pylib/gyp/generator/make.py b/tools/gyp/pylib/gyp/generator/make.py index 7118492e77f763..0ba1a8c4e1050e 100644 --- a/tools/gyp/pylib/gyp/generator/make.py +++ b/tools/gyp/pylib/gyp/generator/make.py @@ -1880,7 +1880,7 @@ def WriteTarget( self.flavor not in ("mac", "openbsd", "netbsd", "win") and not self.is_standalone_static_library ): - if self.flavor in ("linux", "android"): + if self.flavor in ("linux", "android", "openharmony"): self.WriteMakeRule( [self.output_binary], link_deps, @@ -1894,7 +1894,7 @@ def WriteTarget( part_of_all, postbuilds=postbuilds, ) - elif self.flavor in ("linux", "android"): + elif self.flavor in ("linux", "android", "openharmony"): self.WriteMakeRule( [self.output_binary], link_deps, diff --git a/tools/gyp/pylib/gyp/xcode_emulation.py b/tools/gyp/pylib/gyp/xcode_emulation.py index 0746865dc84b72..08e645c57d5cda 100644 --- a/tools/gyp/pylib/gyp/xcode_emulation.py +++ b/tools/gyp/pylib/gyp/xcode_emulation.py @@ -521,7 +521,7 @@ def _GetSdkVersionInfoItem(self, sdk, infoitem): # most sensible route and should still do the right thing. try: return GetStdoutQuiet(["xcrun", "--sdk", sdk, infoitem]) - except GypError: + except (GypError, OSError): pass def _SdkRoot(self, configname): @@ -1354,7 +1354,7 @@ def _DefaultSdkRoot(self): return default_sdk_root try: all_sdks = GetStdout(["xcodebuild", "-showsdks"]) - except GypError: + except (GypError, OSError): # If xcodebuild fails, there will be no valid SDKs return "" for line in all_sdks.splitlines(): @@ -1508,7 +1508,8 @@ def XcodeVersion(): raise GypError("xcodebuild returned unexpected results") version = version_list[0].split()[-1] # Last word on first line build = version_list[-1].split()[-1] # Last word on last line - except GypError: # Xcode not installed so look for XCode Command Line Tools + except (GypError, OSError): + # Xcode not installed so look for XCode Command Line Tools version = CLTVersion() # macOS Catalina returns 11.0.0.0.1.1567737322 if not version: raise GypError("No Xcode or CLT version detected!") @@ -1541,14 +1542,14 @@ def CLTVersion(): try: output = GetStdout(["/usr/sbin/pkgutil", "--pkg-info", key]) return re.search(regex, output).groupdict()["version"] - except GypError: + except (GypError, OSError): continue regex = re.compile(r"Command Line Tools for Xcode\s+(?P\S+)") try: output = GetStdout(["/usr/sbin/softwareupdate", "--history"]) return re.search(regex, output).groupdict()["version"] - except GypError: + except (GypError, OSError): return None diff --git a/tools/gyp/pyproject.toml b/tools/gyp/pyproject.toml index 62fb2bf8cadf76..b233d8504df687 100644 --- a/tools/gyp/pyproject.toml +++ b/tools/gyp/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "gyp-next" -version = "0.20.2" +version = "0.20.3" authors = [ { name="Node.js contributors", email="ryzokuken@disroot.org" }, ] diff --git a/tools/test.py b/tools/test.py index e27222af3ac519..59801e1b07498b 100755 --- a/tools/test.py +++ b/tools/test.py @@ -788,6 +788,11 @@ def Execute(args, context, timeout=None, env=None, disable_core_files=False, for key, value in env.items(): env_copy[key] = value + # We append NODE_SKIP_FLAG_CHECK (ref: test/common/index.js) + # to avoid parsing the test files twice when looking for + # flags or environment variables defined via // Flags: and // Env: + env_copy["NODE_SKIP_FLAG_CHECK"] = "true" + preexec_fn = None def disableCoreFiles(): diff --git a/typings/internalBinding/url.d.ts b/typings/internalBinding/url.d.ts index b2e1a92ade9141..5ad5932d091f33 100644 --- a/typings/internalBinding/url.d.ts +++ b/typings/internalBinding/url.d.ts @@ -10,4 +10,6 @@ export interface URLBinding { format(input: string, fragment?: boolean, unicode?: boolean, search?: boolean, auth?: boolean): string; parse(input: string, base?: string): string | false; update(input: string, actionType: typeof urlUpdateActions, value: string): string | false; + getOrigin(input: string): string; + pathToFileURL(input: string, isWindows: boolean, hostname?: string): string; }