diff --git a/.eslintrc.json b/.eslintrc.json new file mode 100644 index 0000000..6777565 --- /dev/null +++ b/.eslintrc.json @@ -0,0 +1,20 @@ +{ + "extends": [ + "@super-protocol/eslint-config-typescript/eslint/node-esm" + ], + "ignorePatterns": ["dist/**"], + "overrides": [ + { + "files": [ + "test/**/*.ts" + ], + "rules": { + "@typescript-eslint/explicit-function-return-type": "off", + "@typescript-eslint/no-explicit-any": "off", + "require-await": "off", + "no-mixed-operators": "off", + "@typescript-eslint/no-empty-function": "off" + } + } + ] +} diff --git a/.github/workflows/npm-publish.yml b/.github/workflows/npm-publish.yml new file mode 100644 index 0000000..b22d72e --- /dev/null +++ b/.github/workflows/npm-publish.yml @@ -0,0 +1,60 @@ +# This workflow will run tests using node and then publish a package to npm when a tag is pushed + +name: Build and publish npm package + +on: + push: + tags: + - v*.*.* + workflow_dispatch: + +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + - uses: actions/setup-node@v6 + with: + node-version: 24 + scope: super-protocol + - run: npm ci --no-fund + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + - run: npm run build + - name: Check ESM build + run: node -e "import('./dist/mjs/index.js')" + - run: npm run test + + publish: + needs: test + runs-on: ubuntu-latest + permissions: + contents: read + id-token: write + steps: + - uses: actions/checkout@v6 + - uses: actions/setup-node@v6 + with: + node-version: 24 + scope: super-protocol + registry-url: 'https://registry.npmjs.org' + - run: npm ci --no-fund + - name: Resolve npm dist-tag from version + id: release_meta + shell: bash + run: | + VERSION=$(node -p "require('./package.json').version") + if [[ "$VERSION" == *"-alpha."* ]]; then + echo "npm_tag=alpha" >> "$GITHUB_OUTPUT" + echo "is_prerelease=true" >> "$GITHUB_OUTPUT" + elif [[ "$VERSION" == *"-beta."* ]]; then + echo "npm_tag=beta" >> "$GITHUB_OUTPUT" + echo "is_prerelease=true" >> "$GITHUB_OUTPUT" + elif [[ "$VERSION" == *"-rc."* ]]; then + echo "npm_tag=rc" >> "$GITHUB_OUTPUT" + echo "is_prerelease=true" >> "$GITHUB_OUTPUT" + else + echo "npm_tag=latest" >> "$GITHUB_OUTPUT" + echo "is_prerelease=false" >> "$GITHUB_OUTPUT" + fi + - run: npm publish --access public --tag "${{ steps.release_meta.outputs.npm_tag }}" diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml new file mode 100644 index 0000000..fff9edc --- /dev/null +++ b/.github/workflows/pull-request.yml @@ -0,0 +1,34 @@ +name: "🔍🧪 Lint & unit tests on PR" + +on: + pull_request: + branches: + - main + - develop + - staging + workflow_dispatch: + +jobs: + test: + name: "🔍 Lint · 🏗️ Build · 🧪 Tests" + runs-on: ubuntu-latest + steps: + - name: "📥 Checkout" + uses: actions/checkout@v6 + - name: "⬢ Setup Node.js" + uses: actions/setup-node@v6 + with: + node-version: 24 + scope: super-protocol + - name: "📦 npm ci" + run: npm ci --no-fund + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + - name: "🔍 ESLint" + run: npm run lint + - name: "🏗️ Build" + run: npm run build + - name: "⚡ Smoke-check ESM import" + run: node -e "import('./dist/mjs/index.js')" + - name: "🧪 Vitest" + run: npm run test diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..770dbbe --- /dev/null +++ b/.gitignore @@ -0,0 +1,120 @@ +# ide +.idea +.vs +.claude + +# build +/build + +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +lerna-debug.log* + +# Diagnostic reports (https://nodejs.org/api/report.html) +report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage +*.lcov + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ +jspm_packages/ + +# TypeScript v1 declaration files +typings/ + +# TypeScript cache +*.tsbuildinfo + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Microbundle cache +.rpt2_cache/ +.rts2_cache_cjs/ +.rts2_cache_es/ +.rts2_cache_umd/ + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variables file +.env +.env.test + +# parcel-bundler cache (https://parceljs.org/) +.cache + +# Next.js build output +.next + +# Nuxt.js build / generate output +.nuxt +dist + +# Gatsby files +.cache/ +# Comment in the public line in if your project uses Gatsby and *not* Next.js +# https://nextjs.org/blog/next-9-1#public-directory-support +# public + +# vuepress build output +.vuepress/dist + +# Serverless directories +.serverless/ + +# FuseBox cache +.fusebox/ + +# DynamoDB Local files +.dynamodb/ + +# TernJS port file +.tern-port + +# MacOS +.DS_Store + +# docs +auto-docs + +tmp/ diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..72446f4 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "typescript.tsdk": "node_modules/typescript/lib" +} diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..14a02e8 --- /dev/null +++ b/LICENSE @@ -0,0 +1,66 @@ +Business Source License 1.1 + +License text copyright (c) 2024 Super Protocol, All Rights Reserved. +"Business Source License" is a trademark of MariaDB Corporation Ab. + +----------------------------------------------------------------------------- + +Parameters + +- Licensor: + Super Protocol + +- Licensed Work: + Super Protocol Software + The Licensed Work is (c) 2024 Super Protocol + +- Additional Use Grant: + None + +- Change Date: + 2028-01-01 + +- Change License: + GNU General Public License v2.0 or later + +----------------------------------------------------------------------------- + +Terms + +The Licensor hereby grants you the right to copy, modify, create derivative works, redistribute, and make non-production use of the Licensed Work. The Licensor may make an Additional Use Grant permitting limited production use. + +Effective on the Change Date, or the fourth anniversary of the first publicly available distribution of a specific version of the Licensed Work under this License, whichever comes first, the Licensor hereby grants you rights under the terms of the Change License, and the rights granted in the paragraph above terminate. + +If your use of the Licensed Work does not comply with the requirements currently in effect as described in this License, you must purchase a commercial license from the Licensor, its affiliated entities, or authorized resellers, or you must refrain from using the Licensed Work. + +All copies of the original and modified Licensed Work, and derivative works of the Licensed Work, are subject to this License. This License applies separately for each version of the Licensed Work and the Change Date may vary for each version of the Licensed Work released by Licensor. + +You must conspicuously display this License on each original or modified copy of the Licensed Work. If you receive the Licensed Work in original or modified form from a third party, the terms and conditions set forth in this License apply to your use of that work. + +Any use of the Licensed Work in violation of this License will automatically terminate your rights under this License for the current and all other versions of the Licensed Work. + +This License does not grant you any right in any trademark or logo of Licensor or its affiliates (provided that you may use a trademark or logo of Licensor as expressly required by this License). + +TO THE EXTENT PERMITTED BY APPLICABLE LAW, THE LICENSED WORK IS PROVIDED ON AN "AS IS" BASIS. LICENSOR HEREBY DISCLAIMS ALL WARRANTIES AND CONDITIONS, EXPRESS OR IMPLIED, INCLUDING (WITHOUT LIMITATION) WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT, AND TITLE. + +MariaDB hereby grants you permission to use this License’s text to license your works, and to refer to it using the trademark "Business Source License," as long as you comply with the Covenants of Licensor below. + +----------------------------------------------------------------------------- + +Covenants of Licensor + +In consideration of the right to use this License’s text and the "Business Source License" name and trademark, Licensor covenants to MariaDB, and to all other recipients of the licensed work to be provided by Licensor: + +1. To specify as the Change License the GPL Version 2.0 or any later version, or a license that is compatible with GPL Version 2.0 or a later version, where "compatible" means that software provided under the Change License can be included in a program with software provided under GPL Version 2.0 or a later version. Licensor may specify additional Change Licenses without limitation. + +2. To either: (a) specify an additional grant of rights to use that does not impose any additional restriction on the rights granted in this License, as the Additional Use Grant; or (b) insert the text "None." + +3. To specify a Change Date. + +4. Not to modify this License in any other way. + +----------------------------------------------------------------------------- + +Notice + +The Business Source License (this document, or the "License") is not an Open Source license. However, the Licensed Work will eventually be made available under an Open Source License, as stated in this License. \ No newline at end of file diff --git a/README.md b/README.md index 330b52a..540f5db 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,150 @@ # sp-certs -X509 certificate generation and validation library + +X509 certificate generation, parsing and chain validation helpers extracted from `swarm-contracts-sdk`. + +The library is browser-compatible and can be used in both Node.js and browser environments. + +## Installation + +```bash +npm install @super-protocol/certs +``` + +## Generate a self-signed CA certificate + +```ts +import { + CertificateGenerator, + CryptoKeysTransformer, + type GenerateCertParams, +} from '@super-protocol/certs'; + +const keys = await CertificateGenerator.generateKeys('ECDSA-P-256-SHA256'); + +const certParams: GenerateCertParams = { + subject: { + commonName: 'Root CA', + organization: 'Example Inc', + country: 'US', + }, + issuer: { + commonName: 'Root CA', + organization: 'Example Inc', + country: 'US', + }, + notAfter: new Date(Date.now() + 365 * 24 * 60 * 60 * 1000), + ca: true, + dnsNames: ['ca.example.com'], + publicKey: keys.publicKey, + privateKey: keys.privateKey, +}; + +const certPem = await CertificateGenerator.generateCert(certParams); +const privateKeyPem = await CryptoKeysTransformer.cryptoKeyToPkcs8Pem(keys.privateKey); +const publicKeyPem = await CryptoKeysTransformer.cryptoKeyToSpkiPem(keys.publicKey); + +console.log(certPem); +console.log(privateKeyPem); +console.log(publicKeyPem); +``` + +## Generate a leaf certificate signed by a CA + +```ts +import { CertificateGenerator, type GenerateCertParams } from '@super-protocol/certs'; + +const caKeys = await CertificateGenerator.generateKeys('ECDSA-P-256-SHA256'); +const caSubject = { + commonName: 'Root CA', + organization: 'Example Inc', + country: 'US', +}; + +const caCertPem = await CertificateGenerator.generateCert({ + subject: caSubject, + issuer: caSubject, + notAfter: new Date(Date.now() + 365 * 24 * 60 * 60 * 1000), + ca: true, + publicKey: caKeys.publicKey, + privateKey: caKeys.privateKey, +}); + +const leafKeys = await CertificateGenerator.generateKeys('ECDSA-P-256-SHA256'); + +const leafCertParams: GenerateCertParams = { + subject: { + commonName: 'api.example.com', + organization: 'Example Inc', + country: 'US', + }, + issuer: caSubject, + notAfter: new Date(Date.now() + 90 * 24 * 60 * 60 * 1000), + dnsNames: ['api.example.com', '127.0.0.1'], + publicKey: leafKeys.publicKey, + privateKey: caKeys.privateKey, +}; + +const leafCertPem = await CertificateGenerator.generateCert(leafCertParams); + +console.log(caCertPem); +console.log(leafCertPem); +``` + +## Validate a certificate chain + +```ts +import { Validator } from '@super-protocol/certs'; + +const result = await Validator.validateCertChain( + [leafCertPem, intermediateCertPem], + rootCertPem, + { + offline: true, + }, +); + +if (!result.isValid) { + throw new Error(result.errorMessage); +} +``` + +## Validate a certificate chain with system root certificates + +```ts +import { rootCertificates } from 'tls'; +import { Validator } from '@super-protocol/certs'; + +const result = await Validator.validateCertChain( + [leafCertPem, intermediateCertPem], + rootCertificates, + { + offline: true, + }, +); + +if (!result.isValid) { + throw new Error(result.errorMessage); +} +``` + +## Validate with OCSP/CRL checks enabled + +`ocspExtensionOids` is a list of certificate OIDs that will also be included in the OCSP request as additional extensions. + +```ts +import { Validator } from '@super-protocol/certs'; + +const result = await Validator.validateCertChain( + [leafCertPem, intermediateCertPem], + rootCertPem, + { + checkCrl: true, + checkOcsp: true, + ocspExtensionOids: ['1.2.3.4.5.6.7'], + }, +); + +if (!result.isValid) { + console.error(result.errorMessage); +} +``` diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..4a6503d --- /dev/null +++ b/package-lock.json @@ -0,0 +1,3928 @@ +{ + "name": "@super-protocol/certs", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "@super-protocol/certs", + "version": "1.0.0", + "license": "BSL-1.1", + "dependencies": { + "@peculiar/asn1-ocsp": "^2.3.15", + "@peculiar/asn1-schema": "^2.3.15", + "@peculiar/asn1-x509": "^2.3.15", + "@peculiar/webcrypto": "^1.5.0", + "@peculiar/x509": "^1.12.4", + "asn1js": "^3.0.5", + "node-forge": "^1.4.0", + "pkijs": "^3.2.4" + }, + "devDependencies": { + "@super-protocol/eslint-config-typescript": "^2.0.1", + "@types/node": "^24.0.0", + "@types/node-forge": "^1.3.1", + "eslint": "^8.57.1", + "vite-tsconfig-paths": "^6.1.1", + "vitest": "4.0.15" + }, + "engines": { + "node": ">=24" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.7.tgz", + "integrity": "sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.7.tgz", + "integrity": "sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.7.tgz", + "integrity": "sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.7.tgz", + "integrity": "sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.7.tgz", + "integrity": "sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.7.tgz", + "integrity": "sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.7.tgz", + "integrity": "sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.7.tgz", + "integrity": "sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.7.tgz", + "integrity": "sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.7.tgz", + "integrity": "sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.7.tgz", + "integrity": "sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.7.tgz", + "integrity": "sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.7.tgz", + "integrity": "sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.7.tgz", + "integrity": "sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.7.tgz", + "integrity": "sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.7.tgz", + "integrity": "sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.7.tgz", + "integrity": "sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.7.tgz", + "integrity": "sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.7.tgz", + "integrity": "sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.7.tgz", + "integrity": "sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.7.tgz", + "integrity": "sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.7.tgz", + "integrity": "sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.7.tgz", + "integrity": "sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.7.tgz", + "integrity": "sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.7.tgz", + "integrity": "sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.7.tgz", + "integrity": "sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", + "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", + "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.6.0", + "globals": "^13.19.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", + "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@eslint/eslintrc/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@eslint/js": { + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.1.tgz", + "integrity": "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", + "integrity": "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==", + "deprecated": "Use @eslint/config-array instead", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanwhocodes/object-schema": "^2.0.3", + "debug": "^4.3.1", + "minimatch": "^3.0.5" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "node_modules/@humanwhocodes/config-array/node_modules/brace-expansion": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", + "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@humanwhocodes/config-array/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/object-schema": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", + "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", + "deprecated": "Use @eslint/object-schema instead", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@jridgewell/sourcemap-codec": { + "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/@microsoft/tsdoc": { + "version": "0.14.2", + "resolved": "https://registry.npmjs.org/@microsoft/tsdoc/-/tsdoc-0.14.2.tgz", + "integrity": "sha512-9b8mPpKrfeGRuhFH5iO1iwCLeIIsV6+H1sRfxbkoGXIyQE2BTsPd9zqSqQJ+pv5sJ/hT5M1zvOFL02MnEezFug==", + "dev": true, + "license": "MIT" + }, + "node_modules/@microsoft/tsdoc-config": { + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/@microsoft/tsdoc-config/-/tsdoc-config-0.16.2.tgz", + "integrity": "sha512-OGiIzzoBLgWWR0UdRJX98oYO+XKGf7tiK4Zk6tQ/E4IJqGCe7dvkTvgDZV5cFJUzLGDOjeAXrnZoA6QkVySuxw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@microsoft/tsdoc": "0.14.2", + "ajv": "~6.12.6", + "jju": "~1.4.0", + "resolve": "~1.19.0" + } + }, + "node_modules/@noble/hashes": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.4.0.tgz", + "integrity": "sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg==", + "license": "MIT", + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@peculiar/asn1-cms": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-cms/-/asn1-cms-2.5.0.tgz", + "integrity": "sha512-p0SjJ3TuuleIvjPM4aYfvYw8Fk1Hn/zAVyPJZTtZ2eE9/MIer6/18ROxX6N/e6edVSfvuZBqhxAj3YgsmSjQ/A==", + "license": "MIT", + "dependencies": { + "@peculiar/asn1-schema": "^2.5.0", + "@peculiar/asn1-x509": "^2.5.0", + "@peculiar/asn1-x509-attr": "^2.5.0", + "asn1js": "^3.0.6", + "tslib": "^2.8.1" + } + }, + "node_modules/@peculiar/asn1-csr": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-csr/-/asn1-csr-2.5.0.tgz", + "integrity": "sha512-ioigvA6WSYN9h/YssMmmoIwgl3RvZlAYx4A/9jD2qaqXZwGcNlAxaw54eSx2QG1Yu7YyBC5Rku3nNoHrQ16YsQ==", + "license": "MIT", + "dependencies": { + "@peculiar/asn1-schema": "^2.5.0", + "@peculiar/asn1-x509": "^2.5.0", + "asn1js": "^3.0.6", + "tslib": "^2.8.1" + } + }, + "node_modules/@peculiar/asn1-ecc": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-ecc/-/asn1-ecc-2.5.0.tgz", + "integrity": "sha512-t4eYGNhXtLRxaP50h3sfO6aJebUCDGQACoeexcelL4roMFRRVgB20yBIu2LxsPh/tdW9I282gNgMOyg3ywg/mg==", + "license": "MIT", + "dependencies": { + "@peculiar/asn1-schema": "^2.5.0", + "@peculiar/asn1-x509": "^2.5.0", + "asn1js": "^3.0.6", + "tslib": "^2.8.1" + } + }, + "node_modules/@peculiar/asn1-ocsp": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-ocsp/-/asn1-ocsp-2.5.0.tgz", + "integrity": "sha512-hxXPiMfQA+dqzd+A8SuRcwX3ucfQpEMR0wr9MUIUsSRAL0sIhTy+sf2dMiu1TCrmY/t6/zHKsvGLLNQItTuV9w==", + "license": "MIT", + "dependencies": { + "@peculiar/asn1-schema": "^2.5.0", + "@peculiar/asn1-x509": "^2.5.0", + "asn1js": "^3.0.6", + "tslib": "^2.8.1" + } + }, + "node_modules/@peculiar/asn1-pfx": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-pfx/-/asn1-pfx-2.5.0.tgz", + "integrity": "sha512-Vj0d0wxJZA+Ztqfb7W+/iu8Uasw6hhKtCdLKXLG/P3kEPIQpqGI4P4YXlROfl7gOCqFIbgsj1HzFIFwQ5s20ug==", + "license": "MIT", + "dependencies": { + "@peculiar/asn1-cms": "^2.5.0", + "@peculiar/asn1-pkcs8": "^2.5.0", + "@peculiar/asn1-rsa": "^2.5.0", + "@peculiar/asn1-schema": "^2.5.0", + "asn1js": "^3.0.6", + "tslib": "^2.8.1" + } + }, + "node_modules/@peculiar/asn1-pkcs8": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-pkcs8/-/asn1-pkcs8-2.5.0.tgz", + "integrity": "sha512-L7599HTI2SLlitlpEP8oAPaJgYssByI4eCwQq2C9eC90otFpm8MRn66PpbKviweAlhinWQ3ZjDD2KIVtx7PaVw==", + "license": "MIT", + "dependencies": { + "@peculiar/asn1-schema": "^2.5.0", + "@peculiar/asn1-x509": "^2.5.0", + "asn1js": "^3.0.6", + "tslib": "^2.8.1" + } + }, + "node_modules/@peculiar/asn1-pkcs9": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-pkcs9/-/asn1-pkcs9-2.5.0.tgz", + "integrity": "sha512-UgqSMBLNLR5TzEZ5ZzxR45Nk6VJrammxd60WMSkofyNzd3DQLSNycGWSK5Xg3UTYbXcDFyG8pA/7/y/ztVCa6A==", + "license": "MIT", + "dependencies": { + "@peculiar/asn1-cms": "^2.5.0", + "@peculiar/asn1-pfx": "^2.5.0", + "@peculiar/asn1-pkcs8": "^2.5.0", + "@peculiar/asn1-schema": "^2.5.0", + "@peculiar/asn1-x509": "^2.5.0", + "@peculiar/asn1-x509-attr": "^2.5.0", + "asn1js": "^3.0.6", + "tslib": "^2.8.1" + } + }, + "node_modules/@peculiar/asn1-rsa": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-rsa/-/asn1-rsa-2.5.0.tgz", + "integrity": "sha512-qMZ/vweiTHy9syrkkqWFvbT3eLoedvamcUdnnvwyyUNv5FgFXA3KP8td+ATibnlZ0EANW5PYRm8E6MJzEB/72Q==", + "license": "MIT", + "dependencies": { + "@peculiar/asn1-schema": "^2.5.0", + "@peculiar/asn1-x509": "^2.5.0", + "asn1js": "^3.0.6", + "tslib": "^2.8.1" + } + }, + "node_modules/@peculiar/asn1-schema": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-schema/-/asn1-schema-2.5.0.tgz", + "integrity": "sha512-YM/nFfskFJSlHqv59ed6dZlLZqtZQwjRVJ4bBAiWV08Oc+1rSd5lDZcBEx0lGDHfSoH3UziI2pXt2UM33KerPQ==", + "license": "MIT", + "dependencies": { + "asn1js": "^3.0.6", + "pvtsutils": "^1.3.6", + "tslib": "^2.8.1" + } + }, + "node_modules/@peculiar/asn1-x509": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-x509/-/asn1-x509-2.5.0.tgz", + "integrity": "sha512-CpwtMCTJvfvYTFMuiME5IH+8qmDe3yEWzKHe7OOADbGfq7ohxeLaXwQo0q4du3qs0AII3UbLCvb9NF/6q0oTKQ==", + "license": "MIT", + "dependencies": { + "@peculiar/asn1-schema": "^2.5.0", + "asn1js": "^3.0.6", + "pvtsutils": "^1.3.6", + "tslib": "^2.8.1" + } + }, + "node_modules/@peculiar/asn1-x509-attr": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-x509-attr/-/asn1-x509-attr-2.5.0.tgz", + "integrity": "sha512-9f0hPOxiJDoG/bfNLAFven+Bd4gwz/VzrCIIWc1025LEI4BXO0U5fOCTNDPbbp2ll+UzqKsZ3g61mpBp74gk9A==", + "license": "MIT", + "dependencies": { + "@peculiar/asn1-schema": "^2.5.0", + "@peculiar/asn1-x509": "^2.5.0", + "asn1js": "^3.0.6", + "tslib": "^2.8.1" + } + }, + "node_modules/@peculiar/json-schema": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/@peculiar/json-schema/-/json-schema-1.1.12.tgz", + "integrity": "sha512-coUfuoMeIB7B8/NMekxaDzLhaYmp0HZNPEjYRm9goRou8UZIC3z21s0sL9AWoCw4EG876QyO3kYrc61WNF9B/w==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@peculiar/webcrypto": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@peculiar/webcrypto/-/webcrypto-1.5.0.tgz", + "integrity": "sha512-BRs5XUAwiyCDQMsVA9IDvDa7UBR9gAvPHgugOeGng3YN6vJ9JYonyDc0lNczErgtCWtucjR5N7VtaonboD/ezg==", + "license": "MIT", + "dependencies": { + "@peculiar/asn1-schema": "^2.3.8", + "@peculiar/json-schema": "^1.1.12", + "pvtsutils": "^1.3.5", + "tslib": "^2.6.2", + "webcrypto-core": "^1.8.0" + }, + "engines": { + "node": ">=10.12.0" + } + }, + "node_modules/@peculiar/x509": { + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/@peculiar/x509/-/x509-1.14.0.tgz", + "integrity": "sha512-Yc4PDxN3OrxUPiXgU63c+ZRXKGE8YKF2McTciYhUHFtHVB0KMnjeFSU0qpztGhsp4P0uKix4+J2xEpIEDu8oXg==", + "license": "MIT", + "dependencies": { + "@peculiar/asn1-cms": "^2.5.0", + "@peculiar/asn1-csr": "^2.5.0", + "@peculiar/asn1-ecc": "^2.5.0", + "@peculiar/asn1-pkcs9": "^2.5.0", + "@peculiar/asn1-rsa": "^2.5.0", + "@peculiar/asn1-schema": "^2.5.0", + "@peculiar/asn1-x509": "^2.5.0", + "pvtsutils": "^1.3.6", + "reflect-metadata": "^0.2.2", + "tslib": "^2.8.1", + "tsyringe": "^4.10.0" + } + }, + "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/@rollup/rollup-android-arm-eabi": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.60.2.tgz", + "integrity": "sha512-dnlp69efPPg6Uaw2dVqzWRfAWRnYVb1XJ8CyyhIbZeaq4CA5/mLeZ1IEt9QqQxmbdvagjLIm2ZL8BxXv5lH4Yw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.60.2.tgz", + "integrity": "sha512-OqZTwDRDchGRHHm/hwLOL7uVPB9aUvI0am/eQuWMNyFHf5PSEQmyEeYYheA0EPPKUO/l0uigCp+iaTjoLjVoHg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.60.2.tgz", + "integrity": "sha512-UwRE7CGpvSVEQS8gUMBe1uADWjNnVgP3Iusyda1nSRwNDCsRjnGc7w6El6WLQsXmZTbLZx9cecegumcitNfpmA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.60.2.tgz", + "integrity": "sha512-gjEtURKLCC5VXm1I+2i1u9OhxFsKAQJKTVB8WvDAHF+oZlq0GTVFOlTlO1q3AlCTE/DF32c16ESvfgqR7343/g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.60.2.tgz", + "integrity": "sha512-Bcl6CYDeAgE70cqZaMojOi/eK63h5Me97ZqAQoh77VPjMysA/4ORQBRGo3rRy45x4MzVlU9uZxs8Uwy7ZaKnBw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.60.2.tgz", + "integrity": "sha512-LU+TPda3mAE2QB0/Hp5VyeKJivpC6+tlOXd1VMoXV/YFMvk/MNk5iXeBfB4MQGRWyOYVJ01625vjkr0Az98OJQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.60.2.tgz", + "integrity": "sha512-2QxQrM+KQ7DAW4o22j+XZ6RKdxjLD7BOWTP0Bv0tmjdyhXSsr2Ul1oJDQqh9Zf5qOwTuTc7Ek83mOFaKnodPjg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.60.2.tgz", + "integrity": "sha512-TbziEu2DVsTEOPif2mKWkMeDMLoYjx95oESa9fkQQK7r/Orta0gnkcDpzwufEcAO2BLBsD7mZkXGFqEdMRRwfw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.60.2.tgz", + "integrity": "sha512-bO/rVDiDUuM2YfuCUwZ1t1cP+/yqjqz+Xf2VtkdppefuOFS2OSeAfgafaHNkFn0t02hEyXngZkxtGqXcXwO8Rg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.60.2.tgz", + "integrity": "sha512-hr26p7e93Rl0Za+JwW7EAnwAvKkehh12BU1Llm9Ykiibg4uIr2rbpxG9WCf56GuvidlTG9KiiQT/TXT1yAWxTA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.60.2.tgz", + "integrity": "sha512-pOjB/uSIyDt+ow3k/RcLvUAOGpysT2phDn7TTUB3n75SlIgZzM6NKAqlErPhoFU+npgY3/n+2HYIQVbF70P9/A==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.60.2.tgz", + "integrity": "sha512-2/w+q8jszv9Ww1c+6uJT3OwqhdmGP2/4T17cu8WuwyUuuaCDDJ2ojdyYwZzCxx0GcsZBhzi3HmH+J5pZNXnd+Q==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.60.2.tgz", + "integrity": "sha512-11+aL5vKheYgczxtPVVRhdptAM2H7fcDR5Gw4/bTcteuZBlH4oP9f5s9zYO9aGZvoGeBpqXI/9TZZihZ609wKw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.60.2.tgz", + "integrity": "sha512-i16fokAGK46IVZuV8LIIwMdtqhin9hfYkCh8pf8iC3QU3LpwL+1FSFGej+O7l3E/AoknL6Dclh2oTdnRMpTzFQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.60.2.tgz", + "integrity": "sha512-49FkKS6RGQoriDSK/6E2GkAsAuU5kETFCh7pG4yD/ylj9rKhTmO3elsnmBvRD4PgJPds5W2PkhC82aVwmUcJ7A==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.60.2.tgz", + "integrity": "sha512-mjYNkHPfGpUR00DuM1ZZIgs64Hpf4bWcz9Z41+4Q+pgDx73UwWdAYyf6EG/lRFldmdHHzgrYyge5akFUW0D3mQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.60.2.tgz", + "integrity": "sha512-ALyvJz965BQk8E9Al/JDKKDLH2kfKFLTGMlgkAbbYtZuJt9LU8DW3ZoDMCtQpXAltZxwBHevXz5u+gf0yA0YoA==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.60.2.tgz", + "integrity": "sha512-UQjrkIdWrKI626Du8lCQ6MJp/6V1LAo2bOK9OTu4mSn8GGXIkPXk/Vsp4bLHCd9Z9Iz2OTEaokUE90VweJgIYQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.60.2.tgz", + "integrity": "sha512-bTsRGj6VlSdn/XD4CGyzMnzaBs9bsRxy79eTqTCBsA8TMIEky7qg48aPkvJvFe1HyzQ5oMZdg7AnVlWQSKLTnw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.60.2.tgz", + "integrity": "sha512-6d4Z3534xitaA1FcMWP7mQPq5zGwBmGbhphh2DwaA1aNIXUu3KTOfwrWpbwI4/Gr0uANo7NTtaykFyO2hPuFLg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.60.2.tgz", + "integrity": "sha512-NetAg5iO2uN7eB8zE5qrZ3CSil+7IJt4WDFLcC75Ymywq1VZVD6qJ6EvNLjZ3rEm6gB7XW5JdT60c6MN35Z85Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.60.2.tgz", + "integrity": "sha512-NCYhOotpgWZ5kdxCZsv6Iudx0wX8980Q/oW4pNFNihpBKsDbEA1zpkfxJGC0yugsUuyDZ7gL37dbzwhR0VI7pQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.60.2.tgz", + "integrity": "sha512-RXsaOqXxfoUBQoOgvmmijVxJnW2IGB0eoMO7F8FAjaj0UTywUO/luSqimWBJn04WNgUkeNhh7fs7pESXajWmkg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.60.2.tgz", + "integrity": "sha512-qdAzEULD+/hzObedtmV6iBpdL5TIbKVztGiK7O3/KYSf+HIzU257+MX1EXJcyIiDbMAqmbwaufcYPvyRryeZtA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.60.2.tgz", + "integrity": "sha512-Nd/SgG27WoA9e+/TdK74KnHz852TLa94ovOYySo/yMPuTmpckK/jIF2jSwS3g7ELSKXK13/cVdmg1Z/DaCWKxA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@standard-schema/spec": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", + "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@super-protocol/eslint-config-typescript": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@super-protocol/eslint-config-typescript/-/eslint-config-typescript-2.0.1.tgz", + "integrity": "sha512-ym7/7qFyDu33mbcmpzTKfFCtsm9cCgOG4tN7AQHL8Y6Gnde3qqIE7ReqX5Cz92QHxPUTausJJAwdHp60abYfLw==", + "dev": true, + "license": "ISC", + "peerDependencies": { + "@typescript-eslint/eslint-plugin": "^7.1.1", + "@typescript-eslint/parser": "^7.1.1", + "eslint": "^8.57.0", + "eslint-config-prettier": "^9.1.0", + "eslint-plugin-prettier": "^5.1.3", + "eslint-plugin-require-extensions": "^0.1.3", + "eslint-plugin-tsdoc": "^0.2.17", + "eslint-plugin-unused-imports": "^3.1.0", + "prettier": "^3.2.5", + "typescript": "~5.3.3" + } + }, + "node_modules/@types/chai": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", + "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/deep-eql": "*", + "assertion-error": "^2.0.1" + } + }, + "node_modules/@types/deep-eql": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", + "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "24.12.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.12.2.tgz", + "integrity": "sha512-A1sre26ke7HDIuY/M23nd9gfB+nrmhtYyMINbjI1zHJxYteKR6qSMX56FsmjMcDb3SMcjJg5BiRRgOCC/yBD0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.16.0" + } + }, + "node_modules/@types/node-forge": { + "version": "1.3.14", + "resolved": "https://registry.npmjs.org/@types/node-forge/-/node-forge-1.3.14.tgz", + "integrity": "sha512-mhVF2BnD4BO+jtOp7z1CdzaK4mbuK0LLQYAvdOLqHTavxFNq4zA1EmYkpnFjP8HOUzedfQkRnp0E2ulSAYSzAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.18.0.tgz", + "integrity": "sha512-94EQTWZ40mzBc42ATNIBimBEDltSJ9RQHCC8vc/PDbxi4k8dVwUAv4o98dk50M1zB+JGFxp43FP7f8+FP8R6Sw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@eslint-community/regexpp": "^4.10.0", + "@typescript-eslint/scope-manager": "7.18.0", + "@typescript-eslint/type-utils": "7.18.0", + "@typescript-eslint/utils": "7.18.0", + "@typescript-eslint/visitor-keys": "7.18.0", + "graphemer": "^1.4.0", + "ignore": "^5.3.1", + "natural-compare": "^1.4.0", + "ts-api-utils": "^1.3.0" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^7.0.0", + "eslint": "^8.56.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.18.0.tgz", + "integrity": "sha512-4Z+L8I2OqhZV8qA132M4wNL30ypZGYOQVBfMgxDH/K5UX0PNqTu1c6za9ST5r9+tavvHiTWmBnKzpCJ/GlVFtg==", + "dev": true, + "license": "BSD-2-Clause", + "peer": true, + "dependencies": { + "@typescript-eslint/scope-manager": "7.18.0", + "@typescript-eslint/types": "7.18.0", + "@typescript-eslint/typescript-estree": "7.18.0", + "@typescript-eslint/visitor-keys": "7.18.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.56.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.18.0.tgz", + "integrity": "sha512-jjhdIE/FPF2B7Z1uzc6i3oWKbGcHb87Qw7AWj6jmEqNOfDFbJWtjt/XfwCpvNkpGWlcJaog5vTR+VV8+w9JflA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "7.18.0", + "@typescript-eslint/visitor-keys": "7.18.0" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.18.0.tgz", + "integrity": "sha512-XL0FJXuCLaDuX2sYqZUUSOJ2sG5/i1AAze+axqmLnSkNEVMVYLF+cbwlB2w8D1tinFuSikHmFta+P+HOofrLeA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/typescript-estree": "7.18.0", + "@typescript-eslint/utils": "7.18.0", + "debug": "^4.3.4", + "ts-api-utils": "^1.3.0" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.56.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/types": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.18.0.tgz", + "integrity": "sha512-iZqi+Ds1y4EDYUtlOOC+aUmxnE9xS/yCigkjA7XpTKV6nCBd3Hp/PRGGmdwnfkV2ThMyYldP1wRpm/id99spTQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.18.0.tgz", + "integrity": "sha512-aP1v/BSPnnyhMHts8cf1qQ6Q1IFwwRvAQGRvBFkWlo3/lH29OXA3Pts+c10nxRxIBrDnoMqzhgdwVe5f2D6OzA==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@typescript-eslint/types": "7.18.0", + "@typescript-eslint/visitor-keys": "7.18.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^1.3.0" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.18.0.tgz", + "integrity": "sha512-kK0/rNa2j74XuHVcoCZxdFBMF+aq/vH83CXAOHieC+2Gis4mF8jJXT5eAfyD3K0sAxtPuwxaIOIOvhwzVDt/kw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.4.0", + "@typescript-eslint/scope-manager": "7.18.0", + "@typescript-eslint/types": "7.18.0", + "@typescript-eslint/typescript-estree": "7.18.0" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.56.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.18.0.tgz", + "integrity": "sha512-cDF0/Gf81QpY3xYyJKDV14Zwdmid5+uuENhjH2EqFaF0ni+yAyq/LzMaIJdhNJXZI7uLzwIlA+V7oWoyn6Curg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "7.18.0", + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "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/@vitest/expect": { + "version": "4.0.15", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.0.15.tgz", + "integrity": "sha512-Gfyva9/GxPAWXIWjyGDli9O+waHDC0Q0jaLdFP1qPAUUfo1FEXPXUfUkp3eZA0sSq340vPycSyOlYUeM15Ft1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@standard-schema/spec": "^1.0.0", + "@types/chai": "^5.2.2", + "@vitest/spy": "4.0.15", + "@vitest/utils": "4.0.15", + "chai": "^6.2.1", + "tinyrainbow": "^3.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/mocker": { + "version": "4.0.15", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.0.15.tgz", + "integrity": "sha512-CZ28GLfOEIFkvCFngN8Sfx5h+Se0zN+h4B7yOsPVCcgtiO7t5jt9xQh2E1UkFep+eb9fjyMfuC5gBypwb07fvQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "4.0.15", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.21" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^6.0.0 || ^7.0.0-0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/@vitest/pretty-format": { + "version": "4.0.15", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.0.15.tgz", + "integrity": "sha512-SWdqR8vEv83WtZcrfLNqlqeQXlQLh2iilO1Wk1gv4eiHKjEzvgHb2OVc3mIPyhZE6F+CtfYjNlDJwP5MN6Km7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^3.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "4.0.15", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.0.15.tgz", + "integrity": "sha512-+A+yMY8dGixUhHmNdPUxOh0la6uVzun86vAbuMT3hIDxMrAOmn5ILBHm8ajrqHE0t8R9T1dGnde1A5DTnmi3qw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "4.0.15", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "4.0.15", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.0.15.tgz", + "integrity": "sha512-A7Ob8EdFZJIBjLjeO0DZF4lqR6U7Ydi5/5LIZ0xcI+23lYlsYJAfGn8PrIWTYdZQRNnSRlzhg0zyGu37mVdy5g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.0.15", + "magic-string": "^0.30.21", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "4.0.15", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.0.15.tgz", + "integrity": "sha512-+EIjOJmnY6mIfdXtE/bnozKEvTC4Uczg19yeZ2vtCz5Yyb0QQ31QWVQ8hswJ3Ysx/K2EqaNsVanjr//2+P3FHw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "4.0.15", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.0.15.tgz", + "integrity": "sha512-HXjPW2w5dxhTD0dLwtYHDnelK3j8sR8cWIaLxr22evTyY6q8pRCjZSmhRWVjBaOVXChQd6AwMzi9pucorXCPZA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.0.15", + "tinyrainbow": "^3.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/acorn": { + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", + "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", + "dev": true, + "license": "MIT", + "peer": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ajv": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", + "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "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/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/asn1js": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/asn1js/-/asn1js-3.0.6.tgz", + "integrity": "sha512-UOCGPYbl0tv8+006qks/dTgV9ajs97X2p0FAbyS2iyCRrmLSRolDaHdp+v/CLgnzHc3fVB+CwYiUmei7ndFcgA==", + "license": "BSD-3-Clause", + "dependencies": { + "pvtsutils": "^1.3.6", + "pvutils": "^1.1.3", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/brace-expansion": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.0.tgz", + "integrity": "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/bytestreamjs": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/bytestreamjs/-/bytestreamjs-2.0.1.tgz", + "integrity": "sha512-U1Z/ob71V/bXfVABvNr/Kumf5VyeQRBEm6Txb0PQ6S7V5GpBM3w4Cbqz/xPDicR5tN0uvDifng8C+5qECeGwyQ==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/chai": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz", + "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/es-module-lexer": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", + "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", + "dev": true, + "license": "MIT" + }, + "node_modules/esbuild": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.7.tgz", + "integrity": "sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.27.7", + "@esbuild/android-arm": "0.27.7", + "@esbuild/android-arm64": "0.27.7", + "@esbuild/android-x64": "0.27.7", + "@esbuild/darwin-arm64": "0.27.7", + "@esbuild/darwin-x64": "0.27.7", + "@esbuild/freebsd-arm64": "0.27.7", + "@esbuild/freebsd-x64": "0.27.7", + "@esbuild/linux-arm": "0.27.7", + "@esbuild/linux-arm64": "0.27.7", + "@esbuild/linux-ia32": "0.27.7", + "@esbuild/linux-loong64": "0.27.7", + "@esbuild/linux-mips64el": "0.27.7", + "@esbuild/linux-ppc64": "0.27.7", + "@esbuild/linux-riscv64": "0.27.7", + "@esbuild/linux-s390x": "0.27.7", + "@esbuild/linux-x64": "0.27.7", + "@esbuild/netbsd-arm64": "0.27.7", + "@esbuild/netbsd-x64": "0.27.7", + "@esbuild/openbsd-arm64": "0.27.7", + "@esbuild/openbsd-x64": "0.27.7", + "@esbuild/openharmony-arm64": "0.27.7", + "@esbuild/sunos-x64": "0.27.7", + "@esbuild/win32-arm64": "0.27.7", + "@esbuild/win32-ia32": "0.27.7", + "@esbuild/win32-x64": "0.27.7" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz", + "integrity": "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==", + "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/eslintrc": "^2.1.4", + "@eslint/js": "8.57.1", + "@humanwhocodes/config-array": "^0.13.0", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "@ungap/structured-clone": "^1.2.0", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.2.2", + "eslint-visitor-keys": "^3.4.3", + "espree": "^9.6.1", + "esquery": "^1.4.2", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "graphemer": "^1.4.0", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3", + "strip-ansi": "^6.0.1", + "text-table": "^0.2.0" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-config-prettier": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-9.1.2.tgz", + "integrity": "sha512-iI1f+D2ViGn+uvv5HuHVUamg8ll4tN+JRHGc6IJi4TP9Kl976C57fzPXgseXNs8v0iA8aSJpHsTWjDb9QJamGQ==", + "dev": true, + "license": "MIT", + "peer": true, + "bin": { + "eslint-config-prettier": "bin/cli.js" + }, + "peerDependencies": { + "eslint": ">=7.0.0" + } + }, + "node_modules/eslint-plugin-prettier": { + "version": "5.5.5", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.5.5.tgz", + "integrity": "sha512-hscXkbqUZ2sPithAuLm5MXL+Wph+U7wHngPBv9OMWwlP8iaflyxpjTYZkmdgB4/vPIhemRlBEoLrH7UC1n7aUw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "prettier-linter-helpers": "^1.0.1", + "synckit": "^0.11.12" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint-plugin-prettier" + }, + "peerDependencies": { + "@types/eslint": ">=8.0.0", + "eslint": ">=8.0.0", + "eslint-config-prettier": ">= 7.0.0 <10.0.0 || >=10.1.0", + "prettier": ">=3.0.0" + }, + "peerDependenciesMeta": { + "@types/eslint": { + "optional": true + }, + "eslint-config-prettier": { + "optional": true + } + } + }, + "node_modules/eslint-plugin-require-extensions": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/eslint-plugin-require-extensions/-/eslint-plugin-require-extensions-0.1.3.tgz", + "integrity": "sha512-T3c1PZ9PIdI3hjV8LdunfYI8gj017UQjzAnCrxuo3wAjneDbTPHdE3oNWInOjMA+z/aBkUtlW5vC0YepYMZIug==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "engines": { + "node": ">=16" + }, + "peerDependencies": { + "eslint": "*" + } + }, + "node_modules/eslint-plugin-tsdoc": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/eslint-plugin-tsdoc/-/eslint-plugin-tsdoc-0.2.17.tgz", + "integrity": "sha512-xRmVi7Zx44lOBuYqG8vzTXuL6IdGOeF9nHX17bjJ8+VE6fsxpdGem0/SBTmAwgYMKYB1WBkqRJVQ+n8GK041pA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@microsoft/tsdoc": "0.14.2", + "@microsoft/tsdoc-config": "0.16.2" + } + }, + "node_modules/eslint-plugin-unused-imports": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-unused-imports/-/eslint-plugin-unused-imports-3.2.0.tgz", + "integrity": "sha512-6uXyn6xdINEpxE1MtDjxQsyXB37lfyO2yKGVVgtD7WEWQGORSOZjgrD6hBhvGv4/SO+TOlS+UnC6JppRqbuwGQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "eslint-rule-composer": "^0.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "peerDependencies": { + "@typescript-eslint/eslint-plugin": "6 - 7", + "eslint": "8" + }, + "peerDependenciesMeta": { + "@typescript-eslint/eslint-plugin": { + "optional": true + } + } + }, + "node_modules/eslint-rule-composer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/eslint-rule-composer/-/eslint-rule-composer-0.3.0.tgz", + "integrity": "sha512-bt+Sh8CtDmn2OajxvNO+BX7Wn4CIWMpTRm3MaiKPCQcnnlm0CS2mhui6QaoeQugs+3Kj2ESKEEGJUdVafwhiCg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/eslint-scope": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/brace-expansion": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", + "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/eslint/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/espree": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.9.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", + "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/expect-type": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz", + "integrity": "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-diff": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz", + "integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fastq": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", + "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^3.0.4" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", + "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.3", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/flatted": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", + "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", + "dev": true, + "license": "ISC" + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true, + "license": "ISC" + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "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/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/glob/node_modules/brace-expansion": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", + "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/glob/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/globals": { + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globrex": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/globrex/-/globrex-0.1.2.tgz", + "integrity": "sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==", + "dev": true, + "license": "MIT" + }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true, + "license": "MIT" + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/hasown": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.3.tgz", + "integrity": "sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "dev": true, + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/jju": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/jju/-/jju-1.4.0.tgz", + "integrity": "sha512-8wb9Yw966OSxApiCt0K3yNJL8pnNeIv+OEq2YMidz4FKP6nonSRoOXc80iXY4JaN2FC11B9qsNmDsm+ZOfMROA==", + "dev": true, + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/minimatch": { + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.2" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-forge": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.4.0.tgz", + "integrity": "sha512-LarFH0+6VfriEhqMMcLX2F7SwSXeWwnEAJEsYm5QKWchiVYVvJyV9v7UDvUv+w5HO23ZpQTXDv/GxdDdMyOuoQ==", + "license": "(BSD-3-Clause OR GPL-2.0)", + "engines": { + "node": ">= 6.13.0" + } + }, + "node_modules/obug": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.1.tgz", + "integrity": "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==", + "dev": true, + "funding": [ + "https://github.com/sponsors/sxzz", + "https://opencollective.com/debug" + ], + "license": "MIT" + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true, + "license": "MIT" + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pkijs": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/pkijs/-/pkijs-3.3.3.tgz", + "integrity": "sha512-+KD8hJtqQMYoTuL1bbGOqxb4z+nZkTAwVdNtWwe8Tc2xNbEmdJYIYoc6Qt0uF55e6YW6KuTHw1DjQ18gMhzepw==", + "license": "BSD-3-Clause", + "dependencies": { + "@noble/hashes": "1.4.0", + "asn1js": "^3.0.6", + "bytestreamjs": "^2.0.1", + "pvtsutils": "^1.3.6", + "pvutils": "^1.1.3", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/postcss": { + "version": "8.5.12", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.12.tgz", + "integrity": "sha512-W62t/Se6rA0Az3DfCL0AqJwXuKwBeYg6nOaIgzP+xZ7N5BFCI7DYi1qs6ygUYT6rvfi6t9k65UMLJC+PHZpDAA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prettier": { + "version": "3.8.3", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.3.tgz", + "integrity": "sha512-7igPTM53cGHMW8xWuVTydi2KO233VFiTNyF5hLJqpilHfmn8C8gPf+PS7dUT64YcXFbiMGZxS9pCSxL/Dxm/Jw==", + "dev": true, + "license": "MIT", + "peer": true, + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/prettier-linter-helpers": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.1.tgz", + "integrity": "sha512-SxToR7P8Y2lWmv/kTzVLC1t/GDI2WGjMwNhLLE9qtH8Q13C+aEmuRlzDst4Up4s0Wc8sF2M+J57iB3cMLqftfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-diff": "^1.1.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/pvtsutils": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/pvtsutils/-/pvtsutils-1.3.6.tgz", + "integrity": "sha512-PLgQXQ6H2FWCaeRak8vvk1GW462lMxB5s3Jm673N82zI4vqtVUPuZdffdZbPDFRoU8kAhItWFtPCWiPpp4/EDg==", + "license": "MIT", + "dependencies": { + "tslib": "^2.8.1" + } + }, + "node_modules/pvutils": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/pvutils/-/pvutils-1.1.5.tgz", + "integrity": "sha512-KTqnxsgGiQ6ZAzZCVlJH5eOjSnvlyEgx1m8bkRJfOhmGRqfo5KLvmAlACQkrjEtOQ4B7wF9TdSLIs9O90MX9xA==", + "license": "MIT", + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/reflect-metadata": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.2.2.tgz", + "integrity": "sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==", + "license": "Apache-2.0" + }, + "node_modules/resolve": { + "version": "1.19.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.19.0.tgz", + "integrity": "sha512-rArEXAgsBG4UgRGcynxWIWKFvh/XZCcS8UJdHhwy91zwAvCZIbcs+vAbflgBnNjYMs/i/i+/Ux6IZhML1yPvxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-core-module": "^2.1.0", + "path-parse": "^1.0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rollup": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.60.2.tgz", + "integrity": "sha512-J9qZyW++QK/09NyN/zeO0dG/1GdGfyp9lV8ajHnRVLfo/uFsbji5mHnDgn/qYdUHyCkM2N+8VyspgZclfAh0eQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.60.2", + "@rollup/rollup-android-arm64": "4.60.2", + "@rollup/rollup-darwin-arm64": "4.60.2", + "@rollup/rollup-darwin-x64": "4.60.2", + "@rollup/rollup-freebsd-arm64": "4.60.2", + "@rollup/rollup-freebsd-x64": "4.60.2", + "@rollup/rollup-linux-arm-gnueabihf": "4.60.2", + "@rollup/rollup-linux-arm-musleabihf": "4.60.2", + "@rollup/rollup-linux-arm64-gnu": "4.60.2", + "@rollup/rollup-linux-arm64-musl": "4.60.2", + "@rollup/rollup-linux-loong64-gnu": "4.60.2", + "@rollup/rollup-linux-loong64-musl": "4.60.2", + "@rollup/rollup-linux-ppc64-gnu": "4.60.2", + "@rollup/rollup-linux-ppc64-musl": "4.60.2", + "@rollup/rollup-linux-riscv64-gnu": "4.60.2", + "@rollup/rollup-linux-riscv64-musl": "4.60.2", + "@rollup/rollup-linux-s390x-gnu": "4.60.2", + "@rollup/rollup-linux-x64-gnu": "4.60.2", + "@rollup/rollup-linux-x64-musl": "4.60.2", + "@rollup/rollup-openbsd-x64": "4.60.2", + "@rollup/rollup-openharmony-arm64": "4.60.2", + "@rollup/rollup-win32-arm64-msvc": "4.60.2", + "@rollup/rollup-win32-ia32-msvc": "4.60.2", + "@rollup/rollup-win32-x64-gnu": "4.60.2", + "@rollup/rollup-win32-x64-msvc": "4.60.2", + "fsevents": "~2.3.2" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true, + "license": "ISC" + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true, + "license": "MIT" + }, + "node_modules/std-env": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz", + "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==", + "dev": true, + "license": "MIT" + }, + "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==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/synckit": { + "version": "0.11.12", + "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.12.tgz", + "integrity": "sha512-Bh7QjT8/SuKUIfObSXNHNSK6WHo6J1tHCqJsuaFDP7gP0fkzSfTxI8y85JrppZ0h8l0maIgc2tfuZQ6/t3GtnQ==", + "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/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinybench": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyexec": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.1.1.tgz", + "integrity": "sha512-VKS/ZaQhhkKFMANmAOhhXVoIfBXblQxGX1myCQ2faQrfmobMftXeJPcZGp0gS07ocvGJWDLZGyOZDadDBqYIJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.16", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz", + "integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyglobby/node_modules/fdir": { + "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" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/tinyglobby/node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/tinyrainbow": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.1.0.tgz", + "integrity": "sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/ts-api-utils": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.4.3.tgz", + "integrity": "sha512-i3eMG77UTMD0hZhgRS562pv83RC6ukSAC2GMNWc+9dieh/+jDM5u5YG+NHX6VNDRHQcHwmsTHctP9LhbC3WxVw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16" + }, + "peerDependencies": { + "typescript": ">=4.2.0" + } + }, + "node_modules/tsconfck": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/tsconfck/-/tsconfck-3.1.6.tgz", + "integrity": "sha512-ks6Vjr/jEw0P1gmOVwutM3B7fWxoWBL2KRDb1JfqGVawBmO5UsvmWOQFGHBPl5yxYz4eERr19E6L7NMv+Fej4w==", + "dev": true, + "license": "MIT", + "bin": { + "tsconfck": "bin/tsconfck.js" + }, + "engines": { + "node": "^18 || >=20" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/tsyringe": { + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/tsyringe/-/tsyringe-4.10.0.tgz", + "integrity": "sha512-axr3IdNuVIxnaK5XGEUFTu3YmAQ6lllgrvqfEoR16g/HGnYY/6We4oWENtAnzK6/LpJ2ur9PAb80RBt7/U4ugw==", + "license": "MIT", + "dependencies": { + "tslib": "^1.9.3" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/tsyringe/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "license": "0BSD" + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typescript": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.3.tgz", + "integrity": "sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "dev": true, + "license": "MIT" + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/vite": { + "version": "7.3.2", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.2.tgz", + "integrity": "sha512-Bby3NOsna2jsjfLVOHKes8sGwgl4TT0E6vvpYgnAYDIF/tie7MRaFthmKuHx1NSXjiTueXH3do80FMQgvEktRg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "esbuild": "^0.27.0", + "fdir": "^6.5.0", + "picomatch": "^4.0.3", + "postcss": "^8.5.6", + "rollup": "^4.43.0", + "tinyglobby": "^0.2.15" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "lightningcss": "^1.21.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vite-tsconfig-paths": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/vite-tsconfig-paths/-/vite-tsconfig-paths-6.1.1.tgz", + "integrity": "sha512-2cihq7zliibCCZ8P9cKJrQBkfgdvcFkOOc3Y02o3GWUDLgqjWsZudaoiuOwO/gzTzy17cS5F7ZPo4bsnS4DGkg==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.1.1", + "globrex": "^0.1.2", + "tsconfck": "^3.0.3" + }, + "peerDependencies": { + "vite": "*" + } + }, + "node_modules/vite/node_modules/fdir": { + "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" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/vite/node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/vitest": { + "version": "4.0.15", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.0.15.tgz", + "integrity": "sha512-n1RxDp8UJm6N0IbJLQo+yzLZ2sQCDyl1o0LeugbPWf8+8Fttp29GghsQBjYJVmWq3gBFfe9Hs1spR44vovn2wA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/expect": "4.0.15", + "@vitest/mocker": "4.0.15", + "@vitest/pretty-format": "4.0.15", + "@vitest/runner": "4.0.15", + "@vitest/snapshot": "4.0.15", + "@vitest/spy": "4.0.15", + "@vitest/utils": "4.0.15", + "es-module-lexer": "^1.7.0", + "expect-type": "^1.2.2", + "magic-string": "^0.30.21", + "obug": "^2.1.1", + "pathe": "^2.0.3", + "picomatch": "^4.0.3", + "std-env": "^3.10.0", + "tinybench": "^2.9.0", + "tinyexec": "^1.0.2", + "tinyglobby": "^0.2.15", + "tinyrainbow": "^3.0.3", + "vite": "^6.0.0 || ^7.0.0", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@opentelemetry/api": "^1.9.0", + "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", + "@vitest/browser-playwright": "4.0.15", + "@vitest/browser-preview": "4.0.15", + "@vitest/browser-webdriverio": "4.0.15", + "@vitest/ui": "4.0.15", + "happy-dom": "*", + "jsdom": "*" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@opentelemetry/api": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser-playwright": { + "optional": true + }, + "@vitest/browser-preview": { + "optional": true + }, + "@vitest/browser-webdriverio": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + } + } + }, + "node_modules/vitest/node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/webcrypto-core": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/webcrypto-core/-/webcrypto-core-1.8.1.tgz", + "integrity": "sha512-P+x1MvlNCXlKbLSOY4cYrdreqPG5hbzkmawbcXLKN/mf6DZW0SdNNkZ+sjwsqVkI4A4Ko2sPZmkZtCKY58w83A==", + "license": "MIT", + "dependencies": { + "@peculiar/asn1-schema": "^2.3.13", + "@peculiar/json-schema": "^1.1.12", + "asn1js": "^3.0.5", + "pvtsutils": "^1.3.5", + "tslib": "^2.7.0" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..295df88 --- /dev/null +++ b/package.json @@ -0,0 +1,65 @@ +{ + "name": "@super-protocol/certs", + "version": "1.0.0", + "description": "Superprotocol certificate creation and validation library", + "type": "module", + "engines": { + "node": ">=24" + }, + "author": "SuperProtocol", + "publishConfig": { + "access": "public", + "registry": "https://registry.npmjs.org/" + }, + "homepage": "https://github.com/Super-Protocol/sp-certs", + "main": "dist/cjs/index.js", + "module": "dist/mjs/index.js", + "types": "dist/mjs/index.d.ts", + "exports": { + "require": "./dist/cjs/index.js", + "import": "./dist/mjs/index.js", + "types": "./dist/mjs/index.d.ts" + }, + "files": [ + "dist/**/*" + ], + "scripts": { + "lint": "eslint --ext .ts .", + "lint:fix": "eslint --ext .ts . --fix", + "build:cjs": "tsc -p ./tsconfig.cjs.json && echo '{\"type\":\"commonjs\"}' > dist/cjs/package.json", + "build:mjs": "tsc -p ./tsconfig.mjs.json && echo '{\"type\":\"module\"}' > dist/mjs/package.json", + "build": "rm -rf ./dist && npm run build:cjs && npm run build:mjs", + "prepack": "npm run build", + "test": "vitest run --configLoader runner", + "test:watch": "vitest watch --configLoader runner" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/Super-Protocol/sp-certs.git" + }, + "license": "BSL-1.1", + "bugs": { + "url": "https://github.com/Super-Protocol/sp-certs/issues" + }, + "dependencies": { + "@peculiar/asn1-ocsp": "^2.3.15", + "@peculiar/asn1-schema": "^2.3.15", + "@peculiar/asn1-x509": "^2.3.15", + "@peculiar/webcrypto": "^1.5.0", + "@peculiar/x509": "^1.12.4", + "asn1js": "^3.0.5", + "node-forge": "^1.4.0", + "pkijs": "^3.2.4" + }, + "overrides": { + "ajv": "^6.14.0" + }, + "devDependencies": { + "@super-protocol/eslint-config-typescript": "^2.0.1", + "@types/node": "^24.0.0", + "@types/node-forge": "^1.3.1", + "eslint": "^8.57.1", + "vite-tsconfig-paths": "^6.1.1", + "vitest": "4.0.15" + } +} diff --git a/src/constants.ts b/src/constants.ts new file mode 100644 index 0000000..f0c3588 --- /dev/null +++ b/src/constants.ts @@ -0,0 +1,8 @@ +/** Default per-request ceiling for outbound revocation / issuer HTTP fetches (ms). */ +export const HTTP_FETCH_TIMEOUT_MS = 30_000; + +export const OID_AUTHORITY_INFORMATION_ACCESS_EXTENSION = '1.3.6.1.5.5.7.1.1'; +export const OID_CRL_DISTRIBUTION_POINTS = '2.5.29.31'; +export const OID_OCSP_ACCESS_METHOD = '1.3.6.1.5.5.7.48.1'; +export const OID_OCSP_ISSUER_ACCESS_METHOD = '1.3.6.1.5.5.7.48.2'; +export const OID_OCSP_NONCE = '1.3.6.1.5.5.7.48.1.2'; diff --git a/src/crl.ts b/src/crl.ts new file mode 100644 index 0000000..a4698f4 --- /dev/null +++ b/src/crl.ts @@ -0,0 +1,71 @@ +import * as pkijs from 'pkijs'; +import { OID_CRL_DISTRIBUTION_POINTS } from './constants.js'; +import { CertificatesHelper } from './helper.js'; +import { isFulfilled, isRejected, toArrayBuffer } from './utils.js'; + +type CRLRequestData = { crlUrl: string }; + +export class CRLHelper { + static async getCRLFromCerts( + certs: pkijs.Certificate[], + ): Promise { + const crlRequestsData = certs.map(this.getCRLRequestData).filter(Boolean) as CRLRequestData[]; + if (!crlRequestsData.length) { + return []; + } + + const crlResponseResults = await Promise.allSettled( + crlRequestsData.map((reqData) => this.getCRLResponse(reqData)), + ); + + const rejectedCRLResponses = crlResponseResults + .filter(isRejected) + .map((result) => String(result.reason)); + if (rejectedCRLResponses.length) { + throw new Error( + `Can't get CRL responses for some certificates (reasons=${rejectedCRLResponses.join(';\n')})`, + ); + } + + return crlResponseResults.filter(isFulfilled).map((result) => result.value); + } + + private static getCRLRequestData(cert: pkijs.Certificate): CRLRequestData | undefined { + const authorityExtension = CertificatesHelper.getExtensionValue( + cert, + OID_CRL_DISTRIBUTION_POINTS, + ); + if (!authorityExtension) { + return; + } + + const extensionValue = pkijs.ExtensionValueFactory.fromBER( + OID_CRL_DISTRIBUTION_POINTS, + toArrayBuffer(authorityExtension), + ) as pkijs.CRLDistributionPoints; + + const findType6DistributionPointExtension = (entry: pkijs.GeneralName): boolean => + entry.type === 6; + + const crlUrlDistributionPoints = extensionValue.distributionPoints.find((point) => + (point.distributionPoint as pkijs.GeneralName[] | undefined)?.some( + findType6DistributionPointExtension, + ), + ); + const crlUrl = ( + crlUrlDistributionPoints?.distributionPoint as pkijs.GeneralName[] | undefined + )?.find(findType6DistributionPointExtension)?.value; + if (!crlUrl) { + return; + } + + return { crlUrl }; + } + + private static async getCRLResponse( + data: CRLRequestData, + ): Promise { + const response = await CertificatesHelper.downloadBinaryWithCache(data.crlUrl); + return pkijs.CertificateRevocationList.fromBER(response); + } +} diff --git a/src/crypto-key-transformer.ts b/src/crypto-key-transformer.ts new file mode 100644 index 0000000..d58b7cf --- /dev/null +++ b/src/crypto-key-transformer.ts @@ -0,0 +1,109 @@ +import crypto, { KeyObject, createPrivateKey, createPublicKey } from 'crypto'; +import { cryptoProvider } from './setup-crypto.js'; + +export class CryptoKeysTransformer { + static privateKeyObjToDer(key: KeyObject): Buffer { + return key.export({ type: 'pkcs8', format: 'der' }); + } + + static publicKeyObjToDer(key: KeyObject): Buffer { + return key.export({ type: 'spki', format: 'der' }); + } + + static keyObjectsToDer({ + privateKey, + publicKey, + }: { + privateKey: KeyObject; + publicKey: KeyObject; + }): { privateKey: Buffer; publicKey: Buffer } { + return { + privateKey: CryptoKeysTransformer.privateKeyObjToDer(privateKey), + publicKey: CryptoKeysTransformer.publicKeyObjToDer(publicKey), + }; + } + + static privateDerToKeyObj(key: Buffer): KeyObject { + return createPrivateKey({ + key, + type: 'pkcs8', + format: 'der', + }); + } + + static publicDerToKeyObj(key: Buffer): KeyObject { + return createPublicKey({ + key, + type: 'spki', + format: 'der', + }); + } + + static pkcs8PemToCryptoKey(pkcs8Pem: string): Promise { + const key = createPrivateKey(pkcs8Pem).export({ format: 'jwk' }); + const privateKeyAlg = getCryptoKeyAlgorithm(key); + + return cryptoProvider.subtle.importKey('jwk', key, privateKeyAlg, true, ['sign']); + } + + static spkiPemToCryptoKey(spkiPem: string): Promise { + const key = createPublicKey(spkiPem).export({ format: 'jwk' }); + const publicKeyAlg = getCryptoKeyAlgorithm(key); + + return cryptoProvider.subtle.importKey('jwk', key, publicKeyAlg, true, ['verify']); + } + + static async cryptoKeyToPkcs8Pem(privateKey: CryptoKey): Promise { + const jwk = (await cryptoProvider.subtle.exportKey('jwk', privateKey)) as crypto.JsonWebKey; + normalizeCurveForNode(jwk); + + return createPrivateKey({ key: jwk, format: 'jwk' }).export({ + format: 'pem', + type: 'pkcs8', + }) as string; + } + + static async cryptoKeyToSpkiPem(publicKey: CryptoKey): Promise { + const jwk = (await cryptoProvider.subtle.exportKey('jwk', publicKey)) as crypto.JsonWebKey; + normalizeCurveForNode(jwk); + + return createPublicKey({ key: jwk, format: 'jwk' }).export({ + format: 'pem', + type: 'spki', + }) as string; + } + + static async cryptoPublicFromCryptoPrivate(privateKey: CryptoKey): Promise { + const jwk = (await cryptoProvider.subtle.exportKey('jwk', privateKey)) as crypto.JsonWebKey; + normalizeCurveForNode(jwk); + + const publicKey = await createPublicKey({ + key: jwk, + format: 'jwk', + }).export({ + format: 'jwk', + }); + + const publicKeyAlg = getCryptoKeyAlgorithm(publicKey); + return cryptoProvider.subtle.importKey('jwk', publicKey, publicKeyAlg, true, ['verify']); + } +} + +function normalizeCurveForNode(jwk: crypto.JsonWebKey): void { + if (jwk.crv === 'K-256') { + jwk.crv = 'secp256k1'; + } +} + +function getCryptoKeyAlgorithm(key: crypto.JsonWebKey): RsaHashedImportParams | EcKeyImportParams { + return key.kty === 'EC' + ? { + name: 'ECDSA', + namedCurve: key.crv === 'P-256' ? 'P-256' : 'K-256', + hash: 'SHA-256', + } + : { + name: 'RSASSA-PKCS1-v1_5', + hash: 'SHA-256', + }; +} diff --git a/src/generator.ts b/src/generator.ts new file mode 100644 index 0000000..ea73c5e --- /dev/null +++ b/src/generator.ts @@ -0,0 +1,358 @@ +import assert from 'assert'; +import { randomUUID } from 'crypto'; +import forge from 'node-forge'; +import { + AuthorityInfoAccessExtension, + AuthorityKeyIdentifierExtension, + BasicConstraintsExtension, + ExtendedKeyUsage, + ExtendedKeyUsageExtension, + Extension, + GeneralNameType, + JsonGeneralNames, + KeyUsageFlags, + KeyUsagesExtension, + Pkcs10CertificateRequest, + Pkcs10CertificateRequestCreateParams, + Pkcs10CertificateRequestGenerator, + SubjectAlternativeNameExtension, + SubjectKeyIdentifierExtension, + X509Certificate, + X509CertificateCreateParams, + X509CertificateGenerator, +} from '@peculiar/x509'; +import { CryptoKeysTransformer } from './crypto-key-transformer.js'; +import { cryptoProvider } from './setup-crypto.js'; +import { + AlgorithmObj, + CertificatePrincipal, + GenerateCertParams, + GenerateCsrParams, + ParsedCert, + ParsedCsr, + PemOrCryptoKeys, + SignatureAlgorithm, +} from './types.js'; +import { isIpAddress, toUint8Array } from './utils.js'; + +const MAX_X509_SERIAL = BigInt('0x' + 'F'.repeat(40)); +const ONE_HOUR_MS = 60 * 60 * 1000; + +const principalAttributeMap: Record = { + commonName: 'CN', + country: 'C', + localityName: 'L', + stateName: 'ST', + organization: 'O', + organizationalUnit: 'OU', +}; + +const notAllowedCertificateCustomExtensions = [...Object.values(forge.pki.oids)]; + +export class CertificateGenerator { + /** + * Generates an X.509 certificate in PEM format. + * + * The subject public key and signer private key may be provided either as `CryptoKey` + * instances or as PEM strings. + * + * @param params - Certificate subject, issuer, lifetime, key material and optional extensions. + * @returns Generated certificate in PEM format. + */ + static async generateCert(params: GenerateCertParams): Promise { + const ca = Boolean(params.ca); + const { publicKey: subjectPublicKey, privateKey: signerPrivateKey } = + await CertificateGenerator.getCryptoKeys(params); + const signingAlgorithm = subjectPublicKey.algorithm as AlgorithmObj; + + const extensions: Extension[] = [new BasicConstraintsExtension(ca, undefined, true)]; + const extendedKeyUsageItems: ExtendedKeyUsage[] = []; + + if (signingAlgorithm.namedCurve !== 'K-256' && params.dnsNames?.length) { + const generalNames: JsonGeneralNames = params.dnsNames.map((dnsName) => ({ + type: (isIpAddress(dnsName) ? 'ip' : 'dns') as GeneralNameType, + value: dnsName, + })); + extensions.push(new SubjectAlternativeNameExtension(generalNames)); + extendedKeyUsageItems.push(ExtendedKeyUsage.serverAuth, ExtendedKeyUsage.clientAuth); + } + + if (params.ocspSigning) { + extendedKeyUsageItems.push(ExtendedKeyUsage.ocspSigning); + } + + if (params.ocspExtension) { + const { ocspUrl, issuerCertUrl } = params.ocspExtension; + extensions.push( + new AuthorityInfoAccessExtension({ + ocsp: [ocspUrl], + ...(issuerCertUrl ? { caIssuers: [issuerCertUrl] } : {}), + }), + ); + } + + if (extendedKeyUsageItems.length) { + extensions.push(new ExtendedKeyUsageExtension(extendedKeyUsageItems, false)); + } + + let keyUsageFlags = KeyUsageFlags.digitalSignature | KeyUsageFlags.keyEncipherment; + if (params.ca) { + keyUsageFlags |= KeyUsageFlags.keyCertSign; + } + extensions.push(new KeyUsagesExtension(keyUsageFlags, true)); + + const signerPublicKey = + await CryptoKeysTransformer.cryptoPublicFromCryptoPrivate(signerPrivateKey); + extensions.push( + await AuthorityKeyIdentifierExtension.create(signerPublicKey), + await SubjectKeyIdentifierExtension.create(subjectPublicKey), + ); + + if (params.customExtensions?.length) { + const filteredExtensions = params.customExtensions.filter( + (ext) => !notAllowedCertificateCustomExtensions.includes(ext.oid), + ); + for (const customExtension of filteredExtensions) { + if (!customExtension.oid || !customExtension.value) { + throw new Error('Custom extension OID and value are required'); + } + extensions.push( + new Extension(customExtension.oid, false, toUint8Array(customExtension.value)), + ); + } + } + + const createCertificateParams: X509CertificateCreateParams = { + serialNumber: CertificateGenerator.generateSerialNumber(), + issuer: CertificateGenerator.getPrincipalInfo(params.issuer), + subject: CertificateGenerator.getPrincipalInfo(params.subject), + notBefore: new Date(Date.now() - ONE_HOUR_MS), + notAfter: params.notAfter, + publicKey: subjectPublicKey, + signingKey: signerPrivateKey, + signingAlgorithm, + extensions, + }; + + const cert = await X509CertificateGenerator.create(createCertificateParams); + return cert.toString('pem'); + } + + /** + * Generates a new key pair for one of the supported signature algorithms. + * + * @param signatureAlgorithm - Requested signature algorithm. + * @returns Extractable WebCrypto key pair suitable for CSR and certificate generation. + */ + static generateKeys(signatureAlgorithm: SignatureAlgorithm): Promise { + const algorithm = CertificateGenerator.getAlgorithm(signatureAlgorithm); + return cryptoProvider.subtle.generateKey(algorithm, true, ['sign', 'verify']); + } + + /** + * Generates a PKCS#10 certificate signing request in PEM format. + * + * @param params - CSR subject, key material and optional SAN/custom extensions. + * @returns Generated CSR in PEM format. + */ + static async generateCsr(params: GenerateCsrParams): Promise { + const keys = await CertificateGenerator.getCryptoKeys(params); + const signingAlgorithm = keys.publicKey.algorithm as AlgorithmObj; + signingAlgorithm.hash = { name: 'SHA-256' }; + + const extensions: Extension[] = []; + + if (signingAlgorithm.namedCurve !== 'K-256' && params.dnsNames?.length) { + const generalNames: JsonGeneralNames = params.dnsNames.map((dnsName) => ({ + type: (isIpAddress(dnsName) ? 'ip' : 'dns') as GeneralNameType, + value: dnsName, + })); + extensions.push(new SubjectAlternativeNameExtension(generalNames)); + } + + if (params.customExtensions?.length) { + for (const customExtension of params.customExtensions) { + if (!customExtension.oid || !customExtension.value) { + throw new Error('Some custom extension missed OID or value'); + } + extensions.push( + new Extension(customExtension.oid, false, toUint8Array(customExtension.value)), + ); + } + } + + const createCsrParams: Pkcs10CertificateRequestCreateParams = { + name: CertificateGenerator.getPrincipalInfo(params.subject), + keys, + signingAlgorithm, + extensions, + }; + + const csr = await Pkcs10CertificateRequestGenerator.create(createCsrParams); + return csr.toString('pem'); + } + + /** + * Verifies and parses an X.509 certificate. + * + * Self-signed certificates are signature-checked before their metadata is returned. + * + * @param certPem - Certificate in PEM format. + * @returns Parsed certificate metadata and imported public key. + */ + static async checkAndParseCert(certPem: string): Promise { + const cert = new X509Certificate(certPem); + + if (cert.issuer === cert.subject) { + const isValid = await cert.verify(); + if (!isValid) { + throw new Error('Self-signed certificate signature verification failed'); + } + } + + const publicKey = await cryptoProvider.subtle.importKey( + 'spki', + cert.publicKey.rawData, + Object.assign(cert.signatureAlgorithm, cert.publicKey.algorithm), + true, + ['verify'], + ); + + const authorityKeyIdentifierExt = cert.extensions.find( + (ext) => ext instanceof AuthorityKeyIdentifierExtension, + ) as AuthorityKeyIdentifierExtension | undefined; + const subjectKeyIdentifierExt = cert.extensions.find( + (ext) => ext instanceof SubjectKeyIdentifierExtension, + ) as SubjectKeyIdentifierExtension | undefined; + + return { + serialNumberHex: cert.serialNumber, + publicKey, + subject: cert.subject, + issuer: cert.issuer, + notBefore: cert.notBefore, + notAfter: cert.notAfter, + dnsNames: CertificateGenerator.extractDnsNamesFromExtensions(cert.extensions), + authorityKeyIdentifier: authorityKeyIdentifierExt?.keyId, + subjectKeyIdentifier: subjectKeyIdentifierExt?.keyId, + extensions: cert.extensions + .filter((ext) => ext.type !== forge.pki.oids['subjectAltName']) + .map((ext) => ({ + oid: ext.type, + value: Buffer.from(ext.value), + })), + }; + } + + /** + * Verifies and parses a PKCS#10 certificate signing request. + * + * @param csrPem - CSR in PEM format. + * @returns Parsed CSR metadata and imported public key. + */ + static async checkAndParseCsr(csrPem: string): Promise { + const csr = new Pkcs10CertificateRequest(csrPem); + const isValid = await csr.verify(); + if (!isValid) { + throw new Error('CSR signature verification failed'); + } + + const publicKey = await cryptoProvider.subtle.importKey( + 'spki', + csr.publicKey.rawData, + Object.assign(csr.signatureAlgorithm, csr.publicKey.algorithm), + true, + ['verify'], + ); + + return { + subject: csr.subject, + publicKey, + dnsNames: CertificateGenerator.extractDnsNamesFromExtensions(csr.extensions), + extensions: csr.extensions + .filter((ext) => ext.type !== forge.pki.oids['subjectAltName']) + .map((ext) => ({ + oid: ext.type, + value: Buffer.from(ext.value), + })), + }; + } + + private static async getCryptoKeys({ privateKey, publicKey }: PemOrCryptoKeys): Promise<{ + publicKey: CryptoKey; + privateKey: CryptoKey; + }> { + const [pubKey, privKey] = await Promise.all([ + typeof publicKey === 'string' + ? CryptoKeysTransformer.spkiPemToCryptoKey(publicKey) + : publicKey, + typeof privateKey === 'string' + ? CryptoKeysTransformer.pkcs8PemToCryptoKey(privateKey) + : privateKey, + ]); + + assert.deepEqual( + pubKey.algorithm, + privKey.algorithm, + 'Both keys must have same algorithm defined', + ); + + return { publicKey: pubKey, privateKey: privKey }; + } + + private static generateSerialNumber(): string { + const uuid = randomUUID().replace(/-/g, ''); + let serial = BigInt('0x' + uuid) % MAX_X509_SERIAL; + const serialHex = serial.toString(16); + if (serialHex[0] && '89abcdefABCDEF'.includes(serialHex[0])) { + serial = serial >> 1n; + } + + return serial.toString(16); + } + + private static getPrincipalInfo(principal: CertificatePrincipal | string): string { + if (typeof principal === 'string') { + return principal; + } + + if (!principal.commonName) { + throw new Error('Common name is required'); + } + + return Object.entries(principal) + .map(([key, value]) => `${principalAttributeMap[key] || key}=${value}`) + .join(','); + } + + private static getAlgorithm(signatureAlgorithm: string): RsaHashedKeyGenParams | EcKeyGenParams { + switch (signatureAlgorithm) { + case 'RSASSA-PKCS1-SHA256': + return { + name: 'RSASSA-PKCS1-v1_5', + hash: 'SHA-256', + publicExponent: new Uint8Array([1, 0, 1]), + modulusLength: 2048, + }; + case 'ECDSA-P-256-SHA256': + return { + name: 'ECDSA', + namedCurve: 'P-256', + }; + case 'ECDSA-secp256k1-SHA256': + return { + name: 'ECDSA', + namedCurve: 'K-256', + }; + default: + throw new Error(`Unsupported signature algorithm: ${signatureAlgorithm}`); + } + } + + private static extractDnsNamesFromExtensions(extensions: Extension[]): string[] | undefined { + const subjectAltNameExt = extensions.find( + (ext) => ext.type === forge.pki.oids['subjectAltName'], + ) as SubjectAlternativeNameExtension | undefined; + return subjectAltNameExt?.names.items.map((item) => item.value); + } +} diff --git a/src/helper.ts b/src/helper.ts new file mode 100644 index 0000000..d61ec70 --- /dev/null +++ b/src/helper.ts @@ -0,0 +1,212 @@ +import forge from 'node-forge'; +import { OctetString } from 'asn1js'; +import * as pkijs from 'pkijs'; +import { AuthorityKeyIdentifier } from 'pkijs'; +import { Pkcs10CertificateRequest, X509Certificate } from '@peculiar/x509'; +import './setup-crypto.js'; +import { HTTP_FETCH_TIMEOUT_MS } from './constants.js'; +import { AlgorithmObj, CertWithKeyIdentifiers, KeyIdentifier } from './types.js'; +import { toArrayBuffer } from './utils.js'; + +type CacheEntry = { + expiresAt: number; + value: ArrayBuffer; +}; + +export class CertificatesHelper { + private static downloadedBinaryCache = new Map(); + + static derToPem(data: ArrayBuffer, type: string = 'CERTIFICATE'): string { + return forge.pem.encode({ + contentDomain: null, + dekInfo: null, + headers: [], + procType: null, + type, + body: Buffer.from(data).toString('binary'), + }); + } + + static pemToDer(certPem: string): Uint8Array { + return Buffer.from(forge.pki.pemToDer(certPem).bytes(), 'binary'); + } + + static splitPemCerts(certs: string): string[] { + const pemRegex = /(-----BEGIN CERTIFICATE-----[\s\S]*?-----END CERTIFICATE-----)/g; + return certs.match(pemRegex) || []; + } + + static getDomain(certPem: string): string | undefined { + const cert = forge.pki.certificateFromPem(certPem); + return cert.subject.attributes.find((attribute) => attribute.name === 'commonName')?.value as + | string + | undefined; + } + + static getExtensionValue(certParam: string | pkijs.Certificate, oid: string): Buffer | undefined { + const cert = + typeof certParam === 'string' + ? pkijs.Certificate.fromBER(toArrayBuffer(CertificatesHelper.pemToDer(certParam))) + : certParam; + const extension = cert.extensions?.find((ext) => ext.extnID === oid); + return extension && Buffer.from(extension.extnValue.valueBlock.toBER()); + } + + static extractCAFromChain(certsPem: string): { certs: string; ca: string } { + const certs = CertificatesHelper.toPkiCerts(certsPem); + const leafAndIntermediates: pkijs.Certificate[] = []; + const roots: pkijs.Certificate[] = []; + + for (const cert of certs) { + if (cert.issuer.isEqual(cert.subject)) { + roots.push(cert); + } else { + leafAndIntermediates.push(cert); + } + } + + const toPemChain = (items: pkijs.Certificate[]): string => + items.map((cert) => CertificatesHelper.derToPem(cert.toSchema().toBER())).join('\n'); + + return { + certs: toPemChain(leafAndIntermediates), + ca: toPemChain(roots), + }; + } + + static getIssuer( + certWithKeyIdent: CertWithKeyIdentifiers, + potentialIssuersWithKeyIdent: CertWithKeyIdentifiers[], + ): CertWithKeyIdentifiers | undefined { + return potentialIssuersWithKeyIdent.find( + (potentialIssuer) => + (certWithKeyIdent.authorityKeyIdentifier && potentialIssuer.subjectKeyIdentifier + ? certWithKeyIdent.authorityKeyIdentifier.isEqual(potentialIssuer.subjectKeyIdentifier) + : certWithKeyIdent.cert.issuer.isEqual(potentialIssuer.cert.subject)) && + !certWithKeyIdent.cert.subject.isEqual(certWithKeyIdent.cert.issuer), + ); + } + + static pemChainToDer(certsPem: string): Uint8Array[] { + return CertificatesHelper.splitPemCerts(certsPem).map((certPem) => + CertificatesHelper.pemToDer(certPem), + ); + } + + static derChainToPem(certsDer: Uint8Array[]): string { + return certsDer + .map((cert) => CertificatesHelper.derToPem(toArrayBuffer(cert))) + .join('') + .trim(); + } + + static async downloadBinaryWithCache(url: string): Promise { + const now = Date.now(); + const cacheTtlMs = 5 * 60 * 1000; + const cached = CertificatesHelper.downloadedBinaryCache.get(url); + if (cached && cached.expiresAt > now) { + return cached.value; + } + + const response = await fetch(url, { signal: AbortSignal.timeout(HTTP_FETCH_TIMEOUT_MS) }); + if (!response.ok) { + throw new Error(`Failed to download binary from ${url}: ${response.status}`); + } + const value = await response.arrayBuffer(); + CertificatesHelper.downloadedBinaryCache.set(url, { + value, + expiresAt: now + cacheTtlMs, + }); + + return value; + } + + static buildChain( + leaf: pkijs.Certificate | CertWithKeyIdentifiers | { cert: pkijs.Certificate }, + potentialIssuers: pkijs.Certificate[] | CertWithKeyIdentifiers[], + ): CertWithKeyIdentifiers[] { + const normalizedLeaf = 'cert' in leaf ? leaf.cert : leaf; + const chain = CertificatesHelper.addKeyIdentifiersToCerts([normalizedLeaf]); + const potentialIssuersWithKeyIdentifiers = + CertificatesHelper.addKeyIdentifiersToCerts(potentialIssuers); + let currentCert: CertWithKeyIdentifiers | undefined = chain[0]; + + do { + currentCert = currentCert + ? CertificatesHelper.getIssuer(currentCert, potentialIssuersWithKeyIdentifiers) + : undefined; + + if (currentCert) { + chain.push(currentCert); + } + } while (currentCert); + + return chain; + } + + static sortCertsFromLeafToRoot( + certsPem: string | string[] | pkijs.Certificate[] | CertWithKeyIdentifiers[], + ): CertWithKeyIdentifiers[] { + const allCerts: pkijs.Certificate[] = + typeof certsPem === 'string' || certsPem.every((cert) => typeof cert === 'string') + ? CertificatesHelper.toPkiCerts(certsPem as string | string[]) + : (certsPem as Array).map((cert) => + 'cert' in cert ? cert.cert : cert, + ); + + const certsWithKeyIdentifiers = CertificatesHelper.addKeyIdentifiersToCerts(allCerts); + const leafs = certsWithKeyIdentifiers.filter( + (certToCheck) => + !certsWithKeyIdentifiers.some((certsToCheckWith) => + certToCheck.subjectKeyIdentifier && certsToCheckWith.authorityKeyIdentifier + ? certToCheck.subjectKeyIdentifier.isEqual(certsToCheckWith.authorityKeyIdentifier) + : certToCheck.cert.subject.isEqual(certsToCheckWith.cert.issuer), + ), + ); + + const chains = leafs + .map((leaf) => CertificatesHelper.buildChain(leaf.cert, allCerts)) + .sort((one, two) => two.length - one.length); + return chains.flat(); + } + + static getCertPublicKeyAlgorithm(certPem: string): AlgorithmObj { + return new X509Certificate(certPem).publicKey.algorithm as AlgorithmObj; + } + + static getCsrPublicKeyAlgorithm(csrPem: string): AlgorithmObj { + return new Pkcs10CertificateRequest(csrPem).publicKey.algorithm as AlgorithmObj; + } + + static toPkiCerts(certs: string | string[]): pkijs.Certificate[] { + const certsArray = Array.isArray(certs) ? certs : CertificatesHelper.splitPemCerts(certs); + return certsArray.map((certPem) => + pkijs.Certificate.fromBER(toArrayBuffer(CertificatesHelper.pemToDer(certPem))), + ); + } + + static addKeyIdentifiersToCerts( + certs: Array, + ): CertWithKeyIdentifiers[] { + return certs.map((cert) => { + if ('cert' in cert) { + return cert; + } + + const authorityKeyIdentifierExt = cert.extensions?.find( + (ext) => ext.extnID === forge.pki.oids['authorityKeyIdentifier'], + )?.parsedValue as AuthorityKeyIdentifier | undefined; + const subjectKeyIdentifierExt = cert.extensions?.find( + (ext) => ext.extnID === forge.pki.oids['subjectKeyIdentifier'], + )?.parsedValue as OctetString | undefined; + + return { + cert, + authorityKeyIdentifier: authorityKeyIdentifierExt?.keyIdentifier as + | KeyIdentifier + | undefined, + subjectKeyIdentifier: subjectKeyIdentifierExt as KeyIdentifier | undefined, + }; + }); + } +} diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..46320d5 --- /dev/null +++ b/src/index.ts @@ -0,0 +1,9 @@ +export * from './constants.js'; +export * from './crypto-key-transformer.js'; +export * from './generator.js'; +export * from './helper.js'; +export * from './ocsp.js'; +export * from './crl.js'; +export * from './setup-crypto.js'; +export * from './types.js'; +export * from './validator.js'; diff --git a/src/ocsp.ts b/src/ocsp.ts new file mode 100644 index 0000000..4c153dc --- /dev/null +++ b/src/ocsp.ts @@ -0,0 +1,461 @@ +import * as asn1js from 'asn1js'; +import forge from 'node-forge'; +import * as pkijs from 'pkijs'; +import { CertID, OCSPRequest, Request, TBSRequest } from '@peculiar/asn1-ocsp'; +import { OctetString, AsnParser, AsnSerializer } from '@peculiar/asn1-schema'; +import { Extension, Extensions, AlgorithmIdentifier } from '@peculiar/asn1-x509'; +import { ExtendedKeyUsage } from '@peculiar/x509'; +import { + HTTP_FETCH_TIMEOUT_MS, + OID_AUTHORITY_INFORMATION_ACCESS_EXTENSION, + OID_OCSP_ACCESS_METHOD, + OID_OCSP_ISSUER_ACCESS_METHOD, + OID_OCSP_NONCE, +} from './constants.js'; +import { CryptoKeysTransformer } from './crypto-key-transformer.js'; +import { CertificatesHelper } from './helper.js'; +import { + CertWithKeyIdentifiers, + CustomExtension, + GenerateOcspResponseParams, + OcspCertStatus, + ParsedOcspRequest, +} from './types.js'; +import { isFulfilled, isRejected, toArrayBuffer, toUint8Array, tryWithInterval } from './utils.js'; + +type OcspRequestData = { + ocspUrl: string; + issuerCertUrl?: string; + certWithKeyIdent: CertWithKeyIdentifiers; +}; + +type GetOCSPResponseParams = { + ocspUrl: string; + certsWithIssuer: Array<{ + issuerCertUrl?: string; + cert: pkijs.Certificate; + issuerCert?: pkijs.Certificate; + }>; + ca: pkijs.Certificate[]; + oidsToCheck: string[]; +}; + +const DEFAULT_REVOCATION_DATE = new Date('1970-01-01T00:00:00Z'); + +export class OCSPHelper { + static async getOCSPResponseFromCerts( + certs: CertWithKeyIdentifiers[], + ca: CertWithKeyIdentifiers[], + oidsToCheck: string[] = [], + ): Promise { + const ocspRequestsData = certs + .map(OCSPHelper.getOCSPRequestData) + .filter(Boolean) as OcspRequestData[]; + if (!ocspRequestsData.length) { + return []; + } + + const groups = new Map(); + for (const requestData of ocspRequestsData) { + const group = groups.get(requestData.ocspUrl) ?? []; + group.push(requestData); + groups.set(requestData.ocspUrl, group); + } + + const getOcspResponseParams: GetOCSPResponseParams[] = [...groups.entries()].map( + ([ocspUrl, certParams]) => ({ + ocspUrl, + certsWithIssuer: certParams.map(({ certWithKeyIdent, issuerCertUrl }) => ({ + cert: certWithKeyIdent.cert, + issuerCertUrl, + issuerCert: CertificatesHelper.getIssuer(certWithKeyIdent, [...certs, ...ca])?.cert, + })), + ca: ca.map((certWithKeyIdent) => certWithKeyIdent.cert), + oidsToCheck, + }), + ); + + const ocspResponseResults = await Promise.allSettled( + getOcspResponseParams.map((params) => OCSPHelper.getOCSPResponse(params)), + ); + + const rejectedOCSPResponses = ocspResponseResults + .filter(isRejected) + .map((result) => String(result.reason)); + if (rejectedOCSPResponses.length) { + throw new Error( + `Can't get valid OCSP responses for some certificates (reasons=${rejectedOCSPResponses.join(';\n')})`, + ); + } + + return ocspResponseResults.filter(isFulfilled).map((result) => result.value); + } + + static async generateOCSPResponse(params: GenerateOcspResponseParams): Promise { + const ocspBasicResp = new pkijs.BasicOCSPResponse(); + const { issuerPem: issuerCertPem, caCertsPem, certs, privateKey, nonce } = params; + const { certs: issuerCertsPem } = CertificatesHelper.extractCAFromChain( + `${issuerCertPem}\n${caCertsPem || ''}`, + ); + const issuerCert = CertificatesHelper.toPkiCerts(issuerCertPem)[0]; + if (!issuerCert) { + throw new Error('Issuer certificate is required'); + } + + ocspBasicResp.tbsResponseData.responderID = issuerCert.subject; + ocspBasicResp.tbsResponseData.producedAt = new Date(); + ocspBasicResp.certs = CertificatesHelper.toPkiCerts(issuerCertsPem); + + for (const certData of certs) { + const { serialNumber, status, issuerKeyHash, issuerNameHash, hashAlgorithm, revocationDate } = + certData; + const certID = new pkijs.CertID({ + hashAlgorithm: new pkijs.AlgorithmIdentifier({ + algorithmId: hashAlgorithm, + algorithmParams: new asn1js.Null(), + }), + issuerNameHash: new asn1js.OctetString({ valueHex: issuerNameHash }), + issuerKeyHash: new asn1js.OctetString({ valueHex: issuerKeyHash }), + serialNumber: new asn1js.Integer({ valueHex: serialNumber }), + }); + + const response = new pkijs.SingleResponse({ certID }); + + switch (status) { + case OcspCertStatus.OK: + case OcspCertStatus.Unknown: + response.certStatus = new asn1js.Primitive({ + idBlock: { + tagClass: 3, + tagNumber: status, + }, + }); + break; + case OcspCertStatus.Revoked: + response.certStatus = new asn1js.Constructed({ + idBlock: { + tagClass: 3, + tagNumber: status, + isConstructed: true, + }, + value: [ + new asn1js.GeneralizedTime({ + valueDate: revocationDate || DEFAULT_REVOCATION_DATE, + }), + ], + }); + break; + default: + throw new Error(`Unknown OCSP certificate status: ${status}`); + } + + response.thisUpdate = new Date(); + ocspBasicResp.tbsResponseData.responses.push(response); + } + + if (nonce) { + ocspBasicResp.tbsResponseData.responseExtensions = [ + new pkijs.Extension({ + extnID: OID_OCSP_NONCE, + extnValue: nonce, + }), + ]; + } + + const privateCryptoKey = await CryptoKeysTransformer.pkcs8PemToCryptoKey(privateKey); + await ocspBasicResp.sign(privateCryptoKey, 'SHA-256'); + + const ocspBasicRespRaw = ocspBasicResp.toSchema().toBER(false); + const ocspResp = new pkijs.OCSPResponse({ + responseStatus: new asn1js.Enumerated({ value: 0 }), + responseBytes: new pkijs.ResponseBytes({ + responseType: pkijs.id_PKIX_OCSP_Basic, + response: new asn1js.OctetString({ valueHex: toArrayBuffer(ocspBasicRespRaw) }), + }), + }); + + return ocspResp.toSchema().toBER(); + } + + static parseOCSPRequest(ocspRequestBinary: ArrayBuffer): ParsedOcspRequest { + const ocspRequest = AsnParser.parse(ocspRequestBinary, OCSPRequest); + const certRequests = ocspRequest.tbsRequest.requestList.map((request) => { + const reqCert = { + hashAlgorithm: request.reqCert.hashAlgorithm.algorithm, + issuerNameHash: toArrayBuffer(request.reqCert.issuerNameHash.buffer), + issuerKeyHash: toArrayBuffer(request.reqCert.issuerKeyHash.buffer), + serialNumber: toArrayBuffer(request.reqCert.serialNumber), + }; + + const extensionsToCheck = + request.singleRequestExtensions?.map((ext) => ({ + oid: ext.extnID, + value: Buffer.from(ext.extnValue.buffer), + })) || []; + + return { ...reqCert, extensionsToCheck }; + }); + + const nonceExtension = ocspRequest.tbsRequest.requestExtensions?.find( + (ext) => ext.extnID === OID_OCSP_NONCE, + ); + const nonce = nonceExtension?.extnValue.buffer; + + return { certRequests, nonce }; + } + + private static canCertSignOCSPResponse( + cert: pkijs.Certificate, + certsWithIssuer: GetOCSPResponseParams['certsWithIssuer'], + ): boolean { + if ( + certsWithIssuer.length && + certsWithIssuer.every( + (certWithIssuer) => cert.toString() === certWithIssuer.issuerCert?.toString(), + ) + ) { + return true; + } + + const extKeysUsage = cert.extensions?.find( + (ext) => ext.extnID === forge.pki.oids['extKeyUsage'], + ); + if (!extKeysUsage) { + return false; + } + + return Boolean( + extKeysUsage.parsedValue.keyPurposes.find( + (usage: string) => usage === ExtendedKeyUsage.ocspSigning, + ), + ); + } + + private static getOCSPRequestData( + certWithKeyIdent: CertWithKeyIdentifiers, + ): OcspRequestData | undefined { + const authorityExtension = CertificatesHelper.getExtensionValue( + certWithKeyIdent.cert, + OID_AUTHORITY_INFORMATION_ACCESS_EXTENSION, + ); + if (!authorityExtension) { + return; + } + + const extensionValue = pkijs.ExtensionValueFactory.fromBER( + OID_AUTHORITY_INFORMATION_ACCESS_EXTENSION, + toArrayBuffer(authorityExtension), + ) as pkijs.IInfoAccess; + + const ocspUrl = extensionValue.accessDescriptions.find( + (desc) => desc.accessMethod === OID_OCSP_ACCESS_METHOD, + )?.accessLocation.value; + const issuerCertUrl = extensionValue.accessDescriptions.find( + (desc) => desc.accessMethod === OID_OCSP_ISSUER_ACCESS_METHOD, + )?.accessLocation.value; + + if (!ocspUrl) { + return; + } + + return { ocspUrl, issuerCertUrl, certWithKeyIdent }; + } + + private static async getOCSPResponse( + params: GetOCSPResponseParams, + ): Promise { + const { ocspUrl, certsWithIssuer, ca, oidsToCheck } = params; + const requestList: Request[] = []; + const issuerCertificates: pkijs.Certificate[] = []; + + const addIssuerCertIfNotExists = (cert: pkijs.Certificate): void => { + if (!issuerCertificates.some((item) => item.subject.isEqual(cert.subject))) { + issuerCertificates.push(cert); + } + }; + + for (const certWithIssuer of certsWithIssuer) { + if (!certWithIssuer.issuerCert && certWithIssuer.issuerCertUrl) { + const issuerCertRaw = await CertificatesHelper.downloadBinaryWithCache( + certWithIssuer.issuerCertUrl, + ); + certWithIssuer.issuerCert = pkijs.Certificate.fromBER(issuerCertRaw); + } + if (!certWithIssuer.issuerCert) { + throw new Error( + `No issuer certificate found for OCSP request for ${certWithIssuer.cert.subject}`, + ); + } + + addIssuerCertIfNotExists(certWithIssuer.issuerCert); + addIssuerCertIfNotExists(certWithIssuer.cert); + + const certID = new pkijs.CertID(); + await certID.createForCertificate(certWithIssuer.cert, { + hashAlgorithm: 'SHA-1', + issuerCertificate: certWithIssuer.issuerCert, + }); + + const request = new Request({ + reqCert: new CertID({ + hashAlgorithm: new AlgorithmIdentifier({ + algorithm: certID.hashAlgorithm.algorithmId, + }), + issuerNameHash: new OctetString().fromASN(certID.issuerNameHash), + issuerKeyHash: new OctetString().fromASN(certID.issuerKeyHash), + serialNumber: certID.serialNumber.valueBlock.valueHex, + }), + }); + + const extensionsToCheck = OCSPHelper.getCertExtensionsToCheck( + certWithIssuer.cert, + oidsToCheck, + ); + if (extensionsToCheck.length) { + request.singleRequestExtensions = new Extensions( + extensionsToCheck.map( + (ext) => + new Extension({ + extnID: ext.oid, + extnValue: new OctetString(toUint8Array(ext.value)), + }), + ), + ); + } + + requestList.push(request); + } + + const reqNonce = OCSPHelper.getNonceForRequest(); + const ocspReq = new OCSPRequest({ + tbsRequest: new TBSRequest({ + requestList, + requestExtensions: new Extensions([ + new Extension({ + extnID: OID_OCSP_NONCE, + extnValue: new OctetString(toUint8Array(reqNonce)), + }), + ]), + }), + }); + + const ocspBasicResp = await OCSPHelper.sendOCSPRequest(ocspUrl, ocspReq); + const respNonce = OCSPHelper.getNonceFromResponse(ocspBasicResp); + if (respNonce && Buffer.compare(reqNonce, respNonce) !== 0) { + throw new Error('OCSP nonces from request and response do not match'); + } + + if (!ocspBasicResp.certs?.length) { + ocspBasicResp.certs = issuerCertificates; + } + + const certsWithKeyIdentifier = await Promise.all( + ocspBasicResp.certs.map(async (cert) => { + let keyIdentifier = cert.extensions + ?.find((ext) => ext.extnID === forge.pki.oids['subjectKeyIdentifier']) + ?.parsedValue.valueBlock.toBER() as ArrayBuffer | undefined; + if (!keyIdentifier) { + keyIdentifier = await cert.getKeyHash(); + } + + return { + cert, + keyIdentifier: Buffer.from(keyIdentifier), + }; + }), + ); + + const signers = certsWithKeyIdentifier.filter( + ({ cert, keyIdentifier }) => + cert.subject.isEqual(ocspBasicResp.tbsResponseData.responderID) || + (ocspBasicResp.tbsResponseData.responderID?.valueBlock && + Buffer.compare( + keyIdentifier, + Buffer.from(ocspBasicResp.tbsResponseData.responderID.valueBlock.toBER()), + ) === 0), + ); + if (!signers.length) { + throw new Error('No OCSP signer certificate found'); + } + if (signers.length > 1) { + throw new Error('Prohibited attempt to replace OCSP signer'); + } + + const signerChain = CertificatesHelper.buildChain(signers[0], [ + ...ocspBasicResp.certs, + ...issuerCertificates, + ]); + ocspBasicResp.certs = signerChain.map((certWithKeyIdentifiers) => certWithKeyIdentifiers.cert); + + const isSignerValid = OCSPHelper.canCertSignOCSPResponse( + signers[0].cert, + params.certsWithIssuer, + ); + if (!isSignerValid) { + throw new Error('OCSP signer certificate does not have the OCSP signing extended key usage'); + } + + const isValid = await ocspBasicResp.verify({ trustedCerts: ca }); + if (!isValid) { + throw new Error('OCSP response verification failed'); + } + + return ocspBasicResp; + } + + private static async sendOCSPRequest( + ocspUrl: string, + ocspReq: OCSPRequest, + ): Promise { + const ocspResponse = await tryWithInterval({ + handler: (controller) => + fetch(ocspUrl, { + method: 'POST', + headers: { + 'Content-Type': 'application/ocsp-request', + }, + body: AsnSerializer.serialize(ocspReq), + signal: controller.signal, + }), + retryInterval: 1000, + retryMax: 3, + timeout: HTTP_FETCH_TIMEOUT_MS, + }); + + if (!ocspResponse.ok) { + throw new Error(`OCSP request failed with status ${ocspResponse.status}`); + } + + const ocspRespSimpl = pkijs.OCSPResponse.fromBER(await ocspResponse.arrayBuffer()); + if (!ocspRespSimpl.responseBytes) { + throw new Error('No ResponseBytes in the OCSP Response'); + } + + return pkijs.BasicOCSPResponse.fromBER( + toArrayBuffer(ocspRespSimpl.responseBytes.response.valueBlock.valueHexView), + ); + } + + private static getNonceForRequest(): Uint8Array { + return pkijs.getRandomValues(new Uint8Array(32)); + } + + private static getNonceFromResponse(ocspBasicResp: pkijs.BasicOCSPResponse): Buffer | undefined { + const nonceExtension = ocspBasicResp.tbsResponseData.responseExtensions?.find( + (extension) => extension.extnID === OID_OCSP_NONCE, + ); + return nonceExtension && Buffer.from(nonceExtension.extnValue.valueBlock.valueHex); + } + + private static getCertExtensionsToCheck( + cert: pkijs.Certificate, + oidsToCheck: string[], + ): CustomExtension[] { + return oidsToCheck + .map((oid) => ({ + oid, + value: CertificatesHelper.getExtensionValue(cert, oid), + })) + .filter((ext): ext is CustomExtension => Boolean(ext.value)); + } +} diff --git a/src/setup-crypto.ts b/src/setup-crypto.ts new file mode 100644 index 0000000..ae28ef0 --- /dev/null +++ b/src/setup-crypto.ts @@ -0,0 +1,29 @@ +import * as x509 from '@peculiar/x509'; +import * as webcrypto from '@peculiar/webcrypto'; +import * as pkijs from 'pkijs'; + +const cryptoProvider = new webcrypto.Crypto(); + +x509.cryptoProvider.set(cryptoProvider); + +pkijs.setEngine('Node', new pkijs.CryptoEngine({ name: 'Node', crypto: cryptoProvider })); + +pkijs.ECNamedCurves.register('K-256', '1.3.132.0.10', 32); + +const originGetAlgorithmByOIDFn = pkijs.CryptoEngine.prototype.getAlgorithmByOID; +function getAlgorithmByOID(oid: string, safety?: boolean, target?: string): object | Algorithm { + if (oid === '1.3.132.0.10') { + return { + name: 'K-256', + }; + } + + return originGetAlgorithmByOIDFn(oid, safety, target); +} +pkijs.CryptoEngine.prototype.getAlgorithmByOID = getAlgorithmByOID; + +x509.PemConverter.isPem = (data: string): data is string => { + return typeof data === 'string' && data.startsWith('-----BEGIN'); +}; + +export { cryptoProvider }; diff --git a/src/types.ts b/src/types.ts new file mode 100644 index 0000000..3ca7074 --- /dev/null +++ b/src/types.ts @@ -0,0 +1,133 @@ +import type { Certificate } from 'pkijs'; + +export type ValidateCertChainResult = { + isValid: boolean; + errorMessage?: string; +}; + +export type SignatureAlgorithm = + | 'RSASSA-PKCS1-SHA256' + | 'ECDSA-secp256k1-SHA256' + | 'ECDSA-P-256-SHA256'; + +export type AlgorithmObj = { + name: string; + namedCurve?: string; + hash?: { + name: string; + }; +}; + +export type CustomExtension = { + oid: string; + value: Buffer; +}; + +export type CertificatePrincipal = { + country?: string; + stateName?: string; + localityName?: string; + organization?: string; + organizationalUnit?: string; + commonName: string; +}; + +export type PemOrCryptoKeys = { + /** + * spki format for PEM. + */ + publicKey: string | CryptoKey; + /** + * pkcs8 format for PEM. + */ + privateKey: string | CryptoKey; +}; + +export type GenerateCertParams = PemOrCryptoKeys & { + subject: CertificatePrincipal | string; + issuer: CertificatePrincipal | string; + notAfter: Date; + dnsNames?: string[]; + ca?: boolean; + ocspSigning?: boolean; + ocspExtension?: { + ocspUrl: string; + issuerCertUrl?: string; + }; + customExtensions?: CustomExtension[]; +}; + +export type GenerateCsrParams = PemOrCryptoKeys & { + subject: CertificatePrincipal | string; + dnsNames?: string[]; + customExtensions?: CustomExtension[]; +}; + +export type ParsedCsr = { + publicKey: CryptoKey; + subject: string; + extensions: CustomExtension[]; + dnsNames?: string[]; +}; + +export type ParsedCert = { + serialNumberHex: string; + publicKey: CryptoKey; + subject: string; + issuer: string; + notBefore: Date; + notAfter: Date; + extensions: CustomExtension[]; + dnsNames?: string[]; + authorityKeyIdentifier?: string; + subjectKeyIdentifier?: string; +}; + +export type OcspCertData = { + issuerNameHash: ArrayBuffer; + issuerKeyHash: ArrayBuffer; + serialNumber: ArrayBuffer; + extensionsToCheck: CustomExtension[]; + hashAlgorithm: string; +}; + +export type ParsedOcspRequest = { + certRequests: OcspCertData[]; + nonce?: ArrayBuffer; +}; + +export enum OcspCertStatus { + OK = 0, + Revoked = 1, + Unknown = 2, +} + +export type GenerateOcspResponseParams = { + issuerPem: string; + caCertsPem?: string; + certs: Array< + Omit & { + status: OcspCertStatus; + revocationDate?: Date; + } + >; + privateKey: string; + nonce?: ArrayBuffer; +}; + +export type KeyIdentifier = { + isEqual(other: KeyIdentifier): boolean; +}; + +export type CertWithKeyIdentifiers = { + cert: Certificate; + authorityKeyIdentifier?: KeyIdentifier; + subjectKeyIdentifier?: KeyIdentifier; +}; + +export type ValidateCertChainOptions = { + offline?: boolean; + checkCrl?: boolean; + checkOcsp?: boolean; + ocspExtensionOids?: string[]; +}; diff --git a/src/utils.ts b/src/utils.ts new file mode 100644 index 0000000..c35c9fd --- /dev/null +++ b/src/utils.ts @@ -0,0 +1,95 @@ +export function isIpAddress(value: string): boolean { + return netIsIPv4(value) || netIsIPv6(value); +} + +export function isFulfilled( + result: PromiseSettledResult, +): result is PromiseFulfilledResult { + return result.status === 'fulfilled'; +} + +export function isRejected(result: PromiseSettledResult): result is PromiseRejectedResult { + return result.status === 'rejected'; +} + +export async function tryWithInterval({ + handler, + retryInterval, + retryMax, + timeout, +}: { + handler: (controller: AbortController) => Promise; + retryInterval: number; + retryMax: number; + timeout?: number; +}): Promise { + let lastError: unknown; + + for (let attempt = 0; attempt <= retryMax; attempt++) { + const controller = new AbortController(); + const timeoutId = + timeout !== undefined ? setTimeout(() => controller.abort(), timeout) : undefined; + try { + return await handler(controller); + } catch (err) { + lastError = err; + if (attempt === retryMax) { + break; + } + await new Promise((resolve) => setTimeout(resolve, retryInterval)); + } finally { + clearTimeout(timeoutId); + } + } + + throw lastError; +} + +function isArrayBufferView(data: ArrayBufferLike | ArrayBufferView): data is ArrayBufferView { + return ( + typeof data === 'object' && + data !== null && + 'buffer' in data && + 'byteOffset' in data && + 'byteLength' in data + ); +} + +export function toArrayBuffer(data: ArrayBufferLike | ArrayBufferView): ArrayBuffer { + if (isArrayBufferView(data)) { + const { buffer, byteOffset, byteLength } = data; + return buffer.slice(byteOffset, byteOffset + byteLength) as ArrayBuffer; + } + + return data.slice(0) as ArrayBuffer; +} + +export function toUint8Array(data: ArrayBufferLike | ArrayBufferView): Uint8Array { + return new Uint8Array(toArrayBuffer(data)); +} + +function netIsIPv4(value: string): boolean { + const parts = value.split('.'); + return ( + parts.length === 4 && + parts.every((part) => { + if (!/^\d+$/.test(part)) { + return false; + } + const number = Number(part); + return number >= 0 && number <= 255 && String(number) === part; + }) + ); +} + +function netIsIPv6(value: string): boolean { + if (!value.includes(':')) { + return false; + } + + try { + return new URL(`http://[${value}]`).hostname === `[${value.toLowerCase()}]`; + } catch { + return false; + } +} diff --git a/src/validator.ts b/src/validator.ts new file mode 100644 index 0000000..34d3694 --- /dev/null +++ b/src/validator.ts @@ -0,0 +1,79 @@ +import * as pkijs from 'pkijs'; +import { CRLHelper } from './crl.js'; +import { CertificatesHelper } from './helper.js'; +import { OCSPHelper } from './ocsp.js'; +import { ValidateCertChainOptions, ValidateCertChainResult } from './types.js'; + +export class Validator { + /** + * Validates a certificate chain against the provided trusted CA certificate set. + * + * The input chain may be provided in any order. The validator will sort it, verify the path, + * and optionally perform CRL and OCSP checks. When `ocspExtensionOids` is provided, matching + * certificate extensions are additionally propagated into the generated OCSP request. + * + * @param certsPem - Leaf and intermediate certificates in PEM format. + * @param caPem - Trusted root CA certificate or certificates in PEM format. + * @param options - Validation options: + * - `offline`: disables both CRL and OCSP checks. Defaults to `false`, which means both + * revocation checks are enabled by default. + * - `checkCrl`: explicitly enables or disables CRL validation when custom behavior is needed. + * - `checkOcsp`: explicitly enables or disables OCSP validation when custom behavior is needed. + * - `ocspExtensionOids`: certificate extension OIDs that will additionally be copied into + * generated OCSP requests as request extensions. + * @returns Validation status and an error message when verification fails. + */ + static async validateCertChain( + certsPem: string | string[], + caPem: string | string[], + options: ValidateCertChainOptions = {}, + ): Promise { + const { offline, checkCrl = !offline, checkOcsp = !offline, ocspExtensionOids = [] } = options; + + const sortedCertsWithKeyIdent = CertificatesHelper.sortCertsFromLeafToRoot(certsPem).reverse(); + const sortedCerts = sortedCertsWithKeyIdent.map((certWithKeyIdent) => certWithKeyIdent.cert); + const ca = CertificatesHelper.toPkiCerts(caPem); + + try { + const crls = checkCrl ? await CRLHelper.getCRLFromCerts(sortedCerts) : []; + const ocspBaseResponses = checkOcsp + ? await OCSPHelper.getOCSPResponseFromCerts( + sortedCertsWithKeyIdent, + CertificatesHelper.addKeyIdentifiersToCerts(ca), + ocspExtensionOids, + ) + : []; + + const chainEngine = new pkijs.CertificateChainValidationEngine({ + certs: sortedCerts, + trustedCerts: ca, + ocsps: ocspBaseResponses, + crls, + }); + + const verifyResult = await chainEngine.verify(); + if (!verifyResult.result) { + return { + isValid: false, + errorMessage: verifyResult.resultMessage, + }; + } + + const isEachCertVerified = sortedCerts.every((cert) => + verifyResult.certificatePath?.find((verifiedCert) => + verifiedCert.serialNumber.isEqual(cert.serialNumber), + ), + ); + if (!isEachCertVerified) { + throw new Error('Some of certificates do not belong to chain'); + } + + return { isValid: true }; + } catch (err) { + return { + isValid: false, + errorMessage: (err as Error).message, + }; + } + } +} diff --git a/test/generator.test.ts b/test/generator.test.ts new file mode 100644 index 0000000..4b1ae48 --- /dev/null +++ b/test/generator.test.ts @@ -0,0 +1,181 @@ +import * as crypto from 'node:crypto'; +import { + CertificateGenerator, + CertificatesHelper, + CryptoKeysTransformer, + GenerateCsrParams, + SignatureAlgorithm, + Validator, +} from '../src/index.js'; +import { describe, expect, it } from 'vitest'; +import { rootCertBaseParams, rootSubject, subroot1, subroot2 } from './test-helpers.js'; + +const signatureAlgorithms: SignatureAlgorithm[] = [ + 'ECDSA-P-256-SHA256', + 'ECDSA-secp256k1-SHA256', + 'RSASSA-PKCS1-SHA256', +]; + +describe('CertificateGenerator', () => { + it('generateCert with PEM-converted keys and custom extensions', async () => { + const keys = await CertificateGenerator.generateKeys('ECDSA-P-256-SHA256'); + const rootPrivatePem = await CryptoKeysTransformer.cryptoKeyToPkcs8Pem(keys.privateKey); + const rootPublicPem = await CryptoKeysTransformer.cryptoKeyToSpkiPem(keys.publicKey); + + const cert = await CertificateGenerator.generateCert({ + ...rootCertBaseParams, + publicKey: rootPublicPem, + privateKey: rootPrivatePem, + customExtensions: [ + { + oid: '1.2.3.4.5.6', + value: Buffer.from('1122334455', 'hex'), + }, + ], + }); + + const parsedCert = new crypto.X509Certificate(cert); + expect(parsedCert.subject.replace(/\n/g, ',')).toBe( + 'C=US,ST=California,L=San Francisco,O=Super Protocol,OU=Development,CN=Root CA', + ); + expect(parsedCert.verify(parsedCert.publicKey)).toBe(true); + expect(parsedCert.subjectAltName ?? '').toMatch(/DNS:sp\.superprotocol\.io/); + expect(CertificatesHelper.getExtensionValue(cert, '1.2.3.4.5.6')?.toString('hex')).toBe( + '1122334455', + ); + }); + + it('secp256k1 certificates omit subjectAltName', async () => { + const keys = await CertificateGenerator.generateKeys('ECDSA-secp256k1-SHA256'); + const cert = await CertificateGenerator.generateCert({ + ...rootCertBaseParams, + publicKey: keys.publicKey, + privateKey: keys.privateKey, + }); + + const parsedCert = new crypto.X509Certificate(cert); + expect(parsedCert.subjectAltName).toBeUndefined(); + }); + + it('generateCert rejects mismatched key algorithms', async () => { + const p256Keys = await CertificateGenerator.generateKeys('ECDSA-P-256-SHA256'); + const secpKeys = await CertificateGenerator.generateKeys('ECDSA-secp256k1-SHA256'); + + await expect( + CertificateGenerator.generateCert({ + ...rootCertBaseParams, + publicKey: p256Keys.publicKey, + privateKey: secpKeys.privateKey, + }), + ).rejects.toThrow(/Both keys must have same algorithm defined/); + }); + + for (const signatureAlgorithm of signatureAlgorithms) { + it(`generated chain validates for ${signatureAlgorithm}`, async () => { + const rootKeys = await CertificateGenerator.generateKeys(signatureAlgorithm); + const rootCert = await CertificateGenerator.generateCert({ + subject: rootSubject, + issuer: rootSubject, + notAfter: rootCertBaseParams.notAfter, + ca: true, + publicKey: rootKeys.publicKey, + privateKey: rootKeys.privateKey, + }); + + const subroot1Keys = await CertificateGenerator.generateKeys(signatureAlgorithm); + const subroot1Cert = await CertificateGenerator.generateCert({ + subject: subroot1, + issuer: rootSubject, + notAfter: rootCertBaseParams.notAfter, + ca: true, + publicKey: subroot1Keys.publicKey, + privateKey: rootKeys.privateKey, + }); + + const subroot2Keys = await CertificateGenerator.generateKeys(signatureAlgorithm); + const subroot2Cert = await CertificateGenerator.generateCert({ + subject: subroot2, + issuer: subroot1, + notAfter: rootCertBaseParams.notAfter, + ca: true, + publicKey: subroot2Keys.publicKey, + privateKey: subroot1Keys.privateKey, + }); + + const rootParsed = await CertificateGenerator.checkAndParseCert(rootCert); + const subroot1Parsed = await CertificateGenerator.checkAndParseCert(subroot1Cert); + const subroot2Parsed = await CertificateGenerator.checkAndParseCert(subroot2Cert); + + expect(rootParsed.subjectKeyIdentifier).toBe(subroot1Parsed.authorityKeyIdentifier); + expect(subroot1Parsed.subjectKeyIdentifier).toBe(subroot2Parsed.authorityKeyIdentifier); + + const result = await Validator.validateCertChain([subroot2Cert, subroot1Cert], rootCert); + expect(result.isValid, result.errorMessage).toBe(true); + }); + } + + it('checkAndParseCert returns parsed metadata', async () => { + const keys = await CertificateGenerator.generateKeys('ECDSA-P-256-SHA256'); + const dnsNames = ['sp.superprotocol.io', 'superprotocol.io', '127.0.0.1']; + const principal = + 'C=US, ST=California, L=San Francisco, O=Super Protocol, OU=Development, CN=Root CA'; + + const customExtensions = [ + { + oid: '1.2.3.4.5.6', + value: Buffer.from('112233445566', 'hex'), + }, + ]; + + const cert = await CertificateGenerator.generateCert({ + ...rootCertBaseParams, + subject: principal, + issuer: principal, + ca: false, + publicKey: keys.publicKey, + privateKey: keys.privateKey, + dnsNames, + customExtensions, + }); + + const parsedCert = await CertificateGenerator.checkAndParseCert(cert); + expect(parsedCert.subject).toBe(principal); + expect(parsedCert.issuer).toBe(principal); + expect(parsedCert.notBefore instanceof Date).toBe(true); + expect(parsedCert.notAfter instanceof Date).toBe(true); + expect(parsedCert.publicKey.constructor.name).toBe('CryptoKey'); + expect(parsedCert.dnsNames).toEqual(dnsNames); + expect( + parsedCert.extensions.some((extension) => extension.oid === customExtensions[0]?.oid), + ).toBe(true); + expect(parsedCert.authorityKeyIdentifier).toBe(parsedCert.subjectKeyIdentifier); + }); + + for (const signatureAlgorithm of signatureAlgorithms) { + it(`generateCsr and parse CSR for ${signatureAlgorithm}`, async () => { + const keys = await CertificateGenerator.generateKeys(signatureAlgorithm); + const dnsNames = ['sp.superprotocol.io', 'superprotocol.io', '127.0.0.1']; + const customExtension = { + oid: '1.2.3.4.5.6.7.8.9.10', + value: Buffer.from('custom value', 'utf8'), + }; + + const csrPem = await CertificateGenerator.generateCsr({ + subject: rootSubject, + publicKey: keys.publicKey, + privateKey: keys.privateKey, + dnsNames, + customExtensions: [customExtension], + } satisfies GenerateCsrParams); + + const parsedCsr = await CertificateGenerator.checkAndParseCsr(csrPem); + expect(parsedCsr.subject.includes('CN=Root CA')).toBe(true); + expect(parsedCsr.publicKey.constructor.name).toBe('CryptoKey'); + expect(parsedCsr.dnsNames).toEqual( + signatureAlgorithm === 'ECDSA-secp256k1-SHA256' ? undefined : dnsNames, + ); + expect(parsedCsr.extensions[0]?.oid).toBe(customExtension.oid); + expect(parsedCsr.extensions[0]?.value.toString('utf8')).toBe('custom value'); + }); + } +}); diff --git a/test/helper.spec.ts b/test/helper.spec.ts new file mode 100644 index 0000000..84bf7d7 --- /dev/null +++ b/test/helper.spec.ts @@ -0,0 +1,23 @@ +import { CertificatesHelper } from '../src/index.js'; +import { afterEach, describe, expect, it, vi } from 'vitest'; +import { createCertificateChain } from './test-helpers.js'; + +describe('CertificatesHelper', () => { + afterEach(() => { + vi.unstubAllGlobals(); + vi.restoreAllMocks(); + }); + + it('sortCertsFromLeafToRoot orders a generated chain', async () => { + const { rootCertPem, subroot1CertPem, leafCertPem } = await createCertificateChain(); + const sorted = CertificatesHelper.sortCertsFromLeafToRoot([ + rootCertPem, + leafCertPem, + subroot1CertPem, + ]); + const subjects = sorted.map( + (item) => item.cert.subject.typesAndValues.at(-1)?.value.valueBlock.value, + ); + expect(subjects).toEqual(['leaf.example.test', 'Subroot1 CA', 'Root CA']); + }); +}); diff --git a/test/mocks.ts b/test/mocks.ts new file mode 100644 index 0000000..7dea3c0 --- /dev/null +++ b/test/mocks.ts @@ -0,0 +1,10 @@ +import { vi } from 'vitest'; + +export function installFetchMock( + handler: (url: string | URL | Request, init?: RequestInit) => Promise, +): () => void { + vi.stubGlobal('fetch', vi.fn(handler)); + return () => { + vi.unstubAllGlobals(); + }; +} diff --git a/test/ocsp.spec.ts b/test/ocsp.spec.ts new file mode 100644 index 0000000..0fcb05c --- /dev/null +++ b/test/ocsp.spec.ts @@ -0,0 +1,148 @@ +import { + CertificateGenerator, + CertificatesHelper, + CryptoKeysTransformer, + OCSPHelper, + OcspCertStatus, + Validator, +} from '../src/index.js'; +import { afterEach, describe, expect, it, vi } from 'vitest'; +import { toArrayBuffer } from '../src/utils.js'; +import { installFetchMock } from './mocks.js'; +import { createCertificateChain, rootSubject } from './test-helpers.js'; + +describe('OCSPHelper', () => { + let restoreFetch: (() => void) | undefined; + + afterEach(() => { + restoreFetch?.(); + restoreFetch = undefined; + vi.unstubAllGlobals(); + vi.restoreAllMocks(); + }); + + it('generates valid request/response flow for a chain', async () => { + const chain = await createCertificateChain({ + includeOcsp: true, + leafCustomExtensions: [ + { + oid: '1.2.3.4.5', + value: Buffer.from('some custom data to check', 'utf-8'), + }, + { + oid: '6.7.8.9.10', + value: Buffer.from('some custom data NOT to check', 'utf-8'), + }, + ], + }); + const ocspSignerKeys = await CertificateGenerator.generateKeys('ECDSA-secp256k1-SHA256'); + const ocspSignerCertPem = await CertificateGenerator.generateCert({ + subject: { + ...rootSubject, + commonName: 'OCSP Signing CA', + }, + issuer: rootSubject, + notAfter: new Date(Date.now() + 365 * 24 * 60 * 60 * 1000), + publicKey: ocspSignerKeys.publicKey, + privateKey: chain.rootKeys.privateKey, + ocspSigning: true, + }); + + let fetchCalls = 0; + restoreFetch = installFetchMock(async (url, init) => { + fetchCalls += 1; + if (String(url).includes('/ocsp-issuer/subroot1')) { + return new Response(Buffer.from(CertificatesHelper.pemToDer(chain.subroot1CertPem))); + } + if (String(url).includes('/ocsp-issuer/root')) { + return new Response(Buffer.from(CertificatesHelper.pemToDer(chain.rootCertPem))); + } + if (String(url).includes('/ocsp')) { + const parsedRequest = OCSPHelper.parseOCSPRequest( + toArrayBuffer(init?.body as ArrayBufferView), + ); + + const extensionToCheck = parsedRequest.certRequests.flatMap( + (certRequest) => certRequest.extensionsToCheck ?? [], + ); + expect(extensionToCheck).toEqual([ + { + oid: '1.2.3.4.5', + value: Buffer.from('some custom data to check', 'utf-8'), + }, + ]); + + const ocspResponse = await OCSPHelper.generateOCSPResponse({ + issuerPem: ocspSignerCertPem, + privateKey: await CryptoKeysTransformer.cryptoKeyToPkcs8Pem(ocspSignerKeys.privateKey), + nonce: parsedRequest.nonce, + certs: parsedRequest.certRequests.map((certRequest) => ({ + ...certRequest, + status: OcspCertStatus.OK, + })), + }); + return new Response(Buffer.from(ocspResponse)); + } + + throw new Error(`Unexpected fetch url: ${url}`); + }); + + const result = await Validator.validateCertChain( + [chain.leafCertPem, chain.subroot1CertPem], + chain.rootCertPem, + { checkCrl: false, checkOcsp: true, ocspExtensionOids: ['1.2.3.4.5'] }, + ); + expect(result.isValid, result.errorMessage).toBe(true); + expect(fetchCalls >= 1).toBe(true); + }); + + it('fails on wrong nonce', async () => { + const chain = await createCertificateChain({ includeOcsp: true }); + const ocspSignerKeys = await CertificateGenerator.generateKeys('ECDSA-secp256k1-SHA256'); + const ocspSignerCertPem = await CertificateGenerator.generateCert({ + subject: { + ...rootSubject, + commonName: 'OCSP Signing CA', + }, + issuer: rootSubject, + notAfter: new Date(Date.now() + 365 * 24 * 60 * 60 * 1000), + publicKey: ocspSignerKeys.publicKey, + privateKey: chain.rootKeys.privateKey, + ocspSigning: true, + }); + + restoreFetch = installFetchMock(async (url, init) => { + if (String(url).includes('/ocsp-issuer/subroot1')) { + return new Response(Buffer.from(CertificatesHelper.pemToDer(chain.subroot1CertPem))); + } + if (String(url).includes('/ocsp-issuer/root')) { + return new Response(Buffer.from(CertificatesHelper.pemToDer(chain.rootCertPem))); + } + if (String(url).includes('/ocsp')) { + const parsedRequest = OCSPHelper.parseOCSPRequest( + toArrayBuffer(init?.body as ArrayBufferView), + ); + const ocspResponse = await OCSPHelper.generateOCSPResponse({ + issuerPem: ocspSignerCertPem, + privateKey: await CryptoKeysTransformer.cryptoKeyToPkcs8Pem(ocspSignerKeys.privateKey), + nonce: new Uint8Array(Buffer.from('wrong nonce')).buffer, + certs: parsedRequest.certRequests.map((certRequest) => ({ + ...certRequest, + status: OcspCertStatus.OK, + })), + }); + return new Response(Buffer.from(ocspResponse)); + } + + throw new Error(`Unexpected fetch url: ${url}`); + }); + + const result = await Validator.validateCertChain( + [chain.leafCertPem, chain.subroot1CertPem], + chain.rootCertPem, + { checkCrl: false, checkOcsp: true }, + ); + expect(result.isValid).toBe(false); + expect(result.errorMessage ?? '').toMatch(/nonces from request and response do not match/); + }); +}); diff --git a/test/test-helpers.ts b/test/test-helpers.ts new file mode 100644 index 0000000..782aaeb --- /dev/null +++ b/test/test-helpers.ts @@ -0,0 +1,113 @@ +import { expect } from 'vitest'; +import { CertificateGenerator, GenerateCertParams, SignatureAlgorithm } from '../src/index.js'; + +export const basePrincipalInfo = { + country: 'US', + stateName: 'California', + localityName: 'San Francisco', + organization: 'Super Protocol', + organizationalUnit: 'Development', +}; + +export const rootSubject = { + ...basePrincipalInfo, + commonName: 'Root CA', +}; + +export const subroot1 = { + ...basePrincipalInfo, + commonName: 'Subroot1 CA', +}; + +export const subroot2 = { + ...basePrincipalInfo, + commonName: 'Subroot2 CA', +}; + +export const rootCertBaseParams = { + subject: rootSubject, + issuer: rootSubject, + notAfter: new Date(Date.now() + 365 * 24 * 60 * 60 * 1000), + dnsNames: ['sp.superprotocol.io', 'superprotocol.io', '127.0.0.1'], + ca: true, +}; + +export async function createCertificateChain({ + signatureAlgorithm = 'ECDSA-secp256k1-SHA256', + includeOcsp = false, + leafCustomExtensions = [], +}: { + signatureAlgorithm?: SignatureAlgorithm; + includeOcsp?: boolean; + leafCustomExtensions?: GenerateCertParams['customExtensions']; +} = {}) { + const rootKeys = await CertificateGenerator.generateKeys(signatureAlgorithm); + const rootCertPem = await CertificateGenerator.generateCert({ + ...rootCertBaseParams, + publicKey: rootKeys.publicKey, + privateKey: rootKeys.privateKey, + }); + + const subroot1Keys = await CertificateGenerator.generateKeys(signatureAlgorithm); + const subroot1CertPem = await CertificateGenerator.generateCert({ + ...rootCertBaseParams, + issuer: rootCertBaseParams.subject, + subject: subroot1, + publicKey: subroot1Keys.publicKey, + privateKey: rootKeys.privateKey, + ...(includeOcsp + ? { + ocspExtension: { + ocspUrl: 'https://example.test/ocsp', + issuerCertUrl: 'https://example.test/ocsp-issuer/subroot1', + }, + } + : {}), + }); + + const leafKeys = await CertificateGenerator.generateKeys(signatureAlgorithm); + const leafCertPem = await CertificateGenerator.generateCert({ + subject: { + ...basePrincipalInfo, + commonName: 'leaf.example.test', + }, + issuer: subroot1, + notAfter: rootCertBaseParams.notAfter, + publicKey: leafKeys.publicKey, + privateKey: subroot1Keys.privateKey, + ...(signatureAlgorithm === 'ECDSA-secp256k1-SHA256' + ? {} + : { dnsNames: ['leaf.example.test', '127.0.0.1'] }), + ...(includeOcsp + ? { + ocspExtension: { + ocspUrl: 'https://example.test/ocsp', + issuerCertUrl: 'https://example.test/ocsp-issuer/root', + }, + } + : {}), + customExtensions: leafCustomExtensions, + }); + + return { + rootKeys, + rootCertPem, + subroot1Keys, + subroot1CertPem, + leafKeys, + leafCertPem, + }; +} + +export function serialHexToArrayBuffer(serialHex: string): ArrayBuffer { + const normalized = serialHex.length % 2 === 0 ? serialHex : `0${serialHex}`; + const bytes = Uint8Array.from(Buffer.from(normalized, 'hex')); + return bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength); +} + +export async function expectValidChain( + resultPromise: Promise<{ isValid: boolean; errorMessage?: string }>, +): Promise { + const result = await resultPromise; + expect(result.isValid, result.errorMessage).toBe(true); +} diff --git a/test/validator.spec.ts b/test/validator.spec.ts new file mode 100644 index 0000000..d74d506 --- /dev/null +++ b/test/validator.spec.ts @@ -0,0 +1,126 @@ +import { + CertificateGenerator, + CertificatesHelper, + CryptoKeysTransformer, + OCSPHelper, + OcspCertStatus, + Validator, +} from '../src/index.js'; +import { afterEach, describe, expect, it, vi } from 'vitest'; +import { installFetchMock } from './mocks.js'; +import { toArrayBuffer } from '../src/utils.js'; +import { createCertificateChain, rootSubject, serialHexToArrayBuffer } from './test-helpers.js'; + +describe('Validator', () => { + let restoreFetch: (() => void) | undefined; + + afterEach(() => { + restoreFetch?.(); + restoreFetch = undefined; + vi.unstubAllGlobals(); + vi.restoreAllMocks(); + }); + + it('validateCertChain succeeds offline in any order', async () => { + const { rootCertPem, subroot1CertPem, leafCertPem } = await createCertificateChain(); + const direct = await Validator.validateCertChain([leafCertPem, subroot1CertPem], rootCertPem, { + offline: true, + }); + const reversed = await Validator.validateCertChain( + [subroot1CertPem, leafCertPem], + rootCertPem, + { + offline: true, + }, + ); + + expect(direct.isValid, direct.errorMessage).toBe(true); + expect(reversed.isValid, reversed.errorMessage).toBe(true); + }); + + it('validateCertChain fails on expired leaf offline', async () => { + const { rootCertPem, subroot1Keys, subroot1CertPem } = await createCertificateChain(); + const expiredLeafKeys = await CertificateGenerator.generateKeys('ECDSA-secp256k1-SHA256'); + const expiredLeaf = await CertificateGenerator.generateCert({ + subject: 'CN=expired.example.test,O=Super Protocol', + issuer: 'CN=Subroot1 CA,OU=Development,O=Super Protocol,L=San Francisco,ST=California,C=US', + notAfter: new Date(Date.now() - 60_000), + publicKey: expiredLeafKeys.publicKey, + privateKey: subroot1Keys.privateKey, + }); + + const result = await Validator.validateCertChain([expiredLeaf, subroot1CertPem], rootCertPem, { + offline: true, + }); + expect(result.isValid).toBe(false); + expect(result.errorMessage ?? '').toMatch(/not yet valid or expired/); + }); + + it('validateCertChain verifies OCSP online and catches revocation', async () => { + const oid = '1.2.3.4.5.6.7'; + const value = Buffer.from('challenge'); + const chain = await createCertificateChain({ + includeOcsp: true, + leafCustomExtensions: [{ oid, value }], + }); + const { rootCertPem, subroot1CertPem, leafCertPem } = chain; + const leafParsed = await CertificateGenerator.checkAndParseCert(leafCertPem); + const subrootParsed = await CertificateGenerator.checkAndParseCert(subroot1CertPem); + const ocspSignerKeys = await CertificateGenerator.generateKeys('ECDSA-secp256k1-SHA256'); + const ocspSignerCertPem = await CertificateGenerator.generateCert({ + subject: { + ...rootSubject, + commonName: 'OCSP Signing CA', + }, + issuer: rootSubject, + notAfter: new Date(Date.now() + 365 * 24 * 60 * 60 * 1000), + publicKey: ocspSignerKeys.publicKey, + privateKey: chain.rootKeys.privateKey, + ocspSigning: true, + }); + + restoreFetch = installFetchMock(async (url, init) => { + if (String(url).includes('/ocsp-issuer/subroot1')) { + return new Response(Buffer.from(CertificatesHelper.pemToDer(subroot1CertPem))); + } + if (String(url).includes('/ocsp-issuer/root')) { + return new Response(Buffer.from(CertificatesHelper.pemToDer(rootCertPem))); + } + if (String(url).includes('/ocsp')) { + const parsedRequest = OCSPHelper.parseOCSPRequest( + toArrayBuffer(init?.body as ArrayBufferView), + ); + expect(parsedRequest.certRequests.length).toBe(2); + expect(parsedRequest.certRequests[0]?.serialNumber).toEqual( + serialHexToArrayBuffer(subrootParsed.serialNumberHex), + ); + expect(parsedRequest.certRequests[1]?.serialNumber).toEqual( + serialHexToArrayBuffer(leafParsed.serialNumberHex), + ); + expect(parsedRequest.certRequests[1]?.extensionsToCheck[0]?.oid).toBe(oid); + + const ocspResponse = await OCSPHelper.generateOCSPResponse({ + issuerPem: ocspSignerCertPem, + privateKey: await CryptoKeysTransformer.cryptoKeyToPkcs8Pem(ocspSignerKeys.privateKey), + nonce: parsedRequest.nonce, + certs: parsedRequest.certRequests.map((certRequest, index) => ({ + ...certRequest, + status: index === 1 ? OcspCertStatus.Revoked : OcspCertStatus.OK, + })), + }); + + return new Response(Buffer.from(ocspResponse)); + } + + throw new Error(`Unexpected fetch url: ${url}`); + }); + + const result = await Validator.validateCertChain([leafCertPem, subroot1CertPem], rootCertPem, { + checkCrl: false, + checkOcsp: true, + ocspExtensionOids: [oid], + }); + expect(result.isValid).toBe(false); + expect(result.errorMessage ?? '').toMatch(/revoked via OCSP response/); + }); +}); diff --git a/tsconfig.cjs.json b/tsconfig.cjs.json new file mode 100644 index 0000000..59d7fa5 --- /dev/null +++ b/tsconfig.cjs.json @@ -0,0 +1,11 @@ +{ + "extends": [ + "./tsconfig.json", + "@super-protocol/eslint-config-typescript/tsconfig/node-cjs" + ], + "compilerOptions": { + "rootDir": "src", + "outDir": "dist/cjs" + }, + "include": ["src/**/*"] +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..0380848 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,12 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "extends": "@super-protocol/eslint-config-typescript/tsconfig/base", + "compilerOptions": { + "target": "es2022", + "lib": ["es2022", "dom"], + "allowJs": true, + "declaration": true, + "skipLibCheck": true + }, + "include": ["src/**/*", "test/**/*", "vitest.config.ts"] +} diff --git a/tsconfig.mjs.json b/tsconfig.mjs.json new file mode 100644 index 0000000..949019b --- /dev/null +++ b/tsconfig.mjs.json @@ -0,0 +1,12 @@ +{ + "extends": [ + "./tsconfig.json", + "@super-protocol/eslint-config-typescript/tsconfig/node-esm" + ], + "compilerOptions": { + "rootDir": "src", + "outDir": "dist/mjs", + + }, + "include": ["src/**/*"] +} diff --git a/vitest.config.ts b/vitest.config.ts new file mode 100644 index 0000000..4f47b67 --- /dev/null +++ b/vitest.config.ts @@ -0,0 +1,27 @@ +import { defineConfig } from 'vitest/config'; +import tsconfigPaths from 'vite-tsconfig-paths'; + +export default defineConfig({ + cacheDir: '/tmp/sp-certs-vite-cache', + plugins: [tsconfigPaths()], + test: { + include: ['test/**/*.{spec,test}.{ts,js}'], + exclude: ['**/node_modules/**', '**/dist/**'], + environment: 'node', + reporters: process.env.GITHUB_ACTIONS ? ['default', 'junit', 'github-actions'] : ['default'], + outputFile: process.env.GITHUB_ACTIONS + ? { + junit: './tests/reports/junit-tests.xml', + } + : undefined, + }, + esbuild: { + target: 'node24', + format: 'esm', + tsconfigRaw: { + compilerOptions: { + experimentalDecorators: true, + }, + }, + }, +});