S00-019: Validate browser SCRAM-SHA-512 through public mechanism composition
Changes: Add an isolated pinned mechanism wrapper using Stanza's public constructor and factory APIs, preserve the SCRAM-SHA-512 wire name and fail-closed final guard, and document the proposed ADR, browser bundle evidence, independent Scramp vectors, notices and limits. Leave parent selection, dependency experiments, specs and shared contracts unchanged. Validation: Fresh-cache npm ci and Vite browser build pass; 87 focused tests pass, including RFC HMAC vectors, original defect reproduction, independent full SHA-512 exchanges and rejected invalid finals/order/reset paths. Hashed Scramp oracle fixture comparison passes. Dependency check verifies 49 locked entries, 25 installed pins/licenses and 340 unmodified Stanza files. make check passes 72 tooling tests. Native browsers, WSS/live server interoperability and production readiness are untested; independent security review remains pending. Task: S00-019 Agent: auto-crypto-c68801170fa4 Role: crypto Action: claim Status: review Task title: Validate browser-compatible SCRAM-SHA-512 through a public mechanism API
This commit is contained in:
1 parent
e69372c6db
commit
07c9967d02
26 files changed
+6399
-8
No files matched your search
@@ -0,0 +1,129 @@
|
|||||||
|
# ADR 0002 SHA-512 remediation: public mechanism composition
|
||||||
|
|
||||||
|
- Status: proposed; independent security review required.
|
||||||
|
- Task: S00-019, remediation of S00-003.
|
||||||
|
- Date: 2026-10-15.
|
||||||
|
- Owner: auto-crypto-c68801170fa4 (crypto).
|
||||||
|
- Reviewer: pending, security role.
|
||||||
|
- Requirements: ACC-004, NET-003, NET-004.
|
||||||
|
|
||||||
|
Input: original SHA-512 blocker, accepted scope/stack/SASL/parser decisions,
|
||||||
|
exact pinned Stanza browser source/public declarations and primary vectors.
|
||||||
|
Output is one isolated runnable experiment in
|
||||||
|
experiments/0002-xmpp-library/sha512-validation. No production library selection,
|
||||||
|
parent WSS work, shared contract, stack change or original spec change.
|
||||||
|
|
||||||
|
## Defect and options
|
||||||
|
|
||||||
|
The built Stanza 12.22.1 browser backend reproduces the RFC 4231 case 1 failure
|
||||||
|
for createHmac('sha-512'). Source chooses 128-byte blocks only for sha512;
|
||||||
|
SCRAM's normal wire name derives sha-512. The browser digest implementation
|
||||||
|
already accepts both spellings; HMAC does not normalize the hyphen. Direct
|
||||||
|
public SCRAM('SCRAM-SHA-512') produces the wrong full client proof as well.
|
||||||
|
|
||||||
|
1. Compose the existing exported SCRAM through its public constructor with the
|
||||||
|
working algorithm alias, expose the correct independent wire name, and
|
||||||
|
register the wrapper through public Factory APIs. Proposed for this exact
|
||||||
|
version, with complete browser-backend vectors guarding the alias behavior.
|
||||||
|
2. Replace crypto or the complete mechanism with another library. Not needed
|
||||||
|
for this outcome: introduces new runtime crypto/dependency/API assumptions.
|
||||||
|
Scramp is used only as an independent test oracle, not a browser candidate.
|
||||||
|
3. Patch private algorithm/vendor HMAC, mutate vendor prototypes, rewrite imports
|
||||||
|
to a replacement crypto backend, rename the wire mechanism or downgrade.
|
||||||
|
Rejected; no such patch/workaround is used.
|
||||||
|
|
||||||
|
## Proposed integration
|
||||||
|
|
||||||
|
Sha512Mechanism implements the shipped public Mechanism interface and delegates
|
||||||
|
all crypto to a new public SCRAM('SCRAM-SHA512'). Constructor typing accepts a
|
||||||
|
string; exact shipped source derives sha512, supported by createHash/Hmac. The
|
||||||
|
wrapper exposes SCRAM-SHA-512 on the wire and never accesses/changes a Stanza
|
||||||
|
private field. The delegate retains its internal alias name throughout. This
|
||||||
|
uses documented public constructor/method signatures and version-inspected alias
|
||||||
|
behavior, not an upstream promise of a named SCRAM-SHA512 wire mechanism.
|
||||||
|
Future upgrades must revalidate this composition; an incompatible API is a new
|
||||||
|
blocker, not permission for a private patch.
|
||||||
|
|
||||||
|
Public Factory.disable/register replaces SHA-512 before negotiation; fresh
|
||||||
|
Client.use installs the isolated S00-017 guard copy. Guard is narrowed to SHA-512
|
||||||
|
and its 88-character verifier, while preserving strict validation, ordering,
|
||||||
|
mutual authentication, cancellation and no-success-on-error behavior. Delegate
|
||||||
|
processSuccess/finalize must confirm both flags before auth event/flags/restart.
|
||||||
|
Library-derived keys remain attempt-local, getCacheableCredentials returns null;
|
||||||
|
no storage/cache update path is added. Crypto primitives, SCRAM composition,
|
||||||
|
SASLprep and verifier remain entirely existing library code.
|
||||||
|
|
||||||
|
Only non-PLUS SHA-512 is permitted by this fixture profile. PLUS-only/plaintext/
|
||||||
|
unsupported offers fail; compatible mixed offers select SHA-512. tlsUnique input
|
||||||
|
fails. GS2 n,, and c=biws reflect no channel binding. Full NET-003 fallback policy
|
||||||
|
and real WSS ACC-004 stay with S00-003; this experiment makes no complete product
|
||||||
|
requirement or library-selection claim.
|
||||||
|
|
||||||
|
## Pins, public API and sources
|
||||||
|
|
||||||
|
Inspected primary sources on 2026-10-15:
|
||||||
|
|
||||||
|
- [RFC 4231 sections 4.2–4.8](https://www.rfc-editor.org/rfc/rfc4231.html#section-4.2)
|
||||||
|
supplies seven fixed HMAC-SHA-512 cases, including 131-byte keys and truncation.
|
||||||
|
- [RFC 6234 test driver](https://www.rfc-editor.org/rfc/rfc6234.html#section-8.5)
|
||||||
|
supplies SHA-512 abc digest. No primitive code is copied from the RFC.
|
||||||
|
- [Stanza source](https://github.com/legastero/stanza/blob/master/src/lib/sasl/index.ts)
|
||||||
|
supplies public API context; exact shipped 12.22.1 lib/sasl/index.d.ts/.js,
|
||||||
|
Client.d.ts/.js, plugins/features.d.ts/.js and platform/browser/crypto files
|
||||||
|
are authoritative version-specific evidence. Factory accepts a constructor;
|
||||||
|
wrapper calls only declared public methods. Stanza license is MIT with bundled
|
||||||
|
browser crypto ancestry notices preserved.
|
||||||
|
- [events upstream](https://github.com/browserify/events) documents browser
|
||||||
|
EventEmitter API, ES5 support, MIT license and limited maintainer resources.
|
||||||
|
events 3.3.0 supplies the explicit polyfill required by Stanza's browser graph.
|
||||||
|
- [Vite build](https://vite.dev/guide/build.html) supports library output/browser
|
||||||
|
targets; accepted Vite 8.3.4 is reused, not a new stack selection.
|
||||||
|
- [Scramp 1.4.6 API](https://pypi.org/project/scramp/1.4.6/) documents SHA-512
|
||||||
|
and public client/server methods. Test oracle pins Scramp 1.4.6 (MIT-0) and
|
||||||
|
asn1crypto 1.5.1 (MIT), exact wheel SHA256 hashes, Python >=3.8. Version selected
|
||||||
|
for available Python 3.9 host compatibility; not a latest-version/audit claim.
|
||||||
|
|
||||||
|
Direct npm pins: stanza 12.22.1, events 3.3.0 (MIT), Vite 8.3.4 (MIT, build only).
|
||||||
|
49 exact locked entries, 25 installed on this macOS ARM64 host. All lock entries
|
||||||
|
have HTTPS registry URL, SHA512 integrity and license inventory; installed
|
||||||
|
notices include Stanza NOTICE browser code lineage and build third-party texts.
|
||||||
|
Native rolldown binding omits a license file; owning rolldown MIT/third-party
|
||||||
|
licenses preserved. Optional platform entries are inventory-only. Build graph
|
||||||
|
includes MIT/ISC/BSD/Apache/MPL packages; no modified covered vendor source.
|
||||||
|
Neither pins nor these checks certify advisory/provenance/security clearance.
|
||||||
|
|
||||||
|
Runtime remains accepted Node 24.21.0/npm 11.19.0. Normal HTTPS, strict engines,
|
||||||
|
disabled install scripts; no global configuration change. Browser bundle uses
|
||||||
|
package browser mapping and explicit events alias, with no Node crypto/backend
|
||||||
|
or external builtin. Build graph assertions and executed artifact supply backend
|
||||||
|
evidence; Node crypto appears only as the separate primitive oracle in tests.
|
||||||
|
|
||||||
|
## Actual validation and limits
|
||||||
|
|
||||||
|
Fresh-cache npm ci, browser library build and 87 tests passed. All seven fixed
|
||||||
|
RFC HMAC cases match sha512/SHA512; SHA digest aliases match abc, varied HMAC keys
|
||||||
|
0..256 bytes match independent Node crypto. Stock hyphenated HMAC/full SCRAM
|
||||||
|
failures reproduce. Three full fixed SHA-512 exchanges match independent Scramp
|
||||||
|
client/server output (ordinary/131-byte/Unicode passwords). Scramp regeneration
|
||||||
|
verifies client proof and server-final independently before comparing fixtures.
|
||||||
|
|
||||||
|
Actual browser-built Client, XML registry, feature dispatcher and mechanism run
|
||||||
|
positive and missing/forged/premature/malformed/duplicate/racing-final checks.
|
||||||
|
Fixture send/transport effects are captured explicitly; crypto is never stubbed.
|
||||||
|
Fail never authenticates/restarts; reset/error/replay cannot revive it. Server
|
||||||
|
silence after proof remains unauthenticated until external reset. All 340 Stanza
|
||||||
|
files byte-match original pinned candidate. Detailed commands, checks and hashes
|
||||||
|
are in README and executor evidence; independent review remains mandatory.
|
||||||
|
|
||||||
|
Bundle execution host is Node, using browser backend and host native web APIs.
|
||||||
|
Actual Chrome/Firefox/Safari runtime, WSS/TLS, live independent server/client,
|
||||||
|
parent parser integration, workers/performance and production readiness are not
|
||||||
|
claimed. Targets Chrome/Edge 111, Firefox 114, Safari 16.4 are accepted build
|
||||||
|
floors, not a certified product matrix; native crypto RNG/fetch/WebSocket needed.
|
||||||
|
Synchronous SCRAM derivation can block UI; no constant-time verifier guarantee
|
||||||
|
(Stanza compares strings), timeout or exhaustive SASLprep claim. Only success
|
||||||
|
additional-data final, extension-free syntax, 4096 decoded bytes/100000 iteration
|
||||||
|
fixture limits; separate final challenge conservatively rejects. Accepted
|
||||||
|
S00-017/S00-018 source is untouched. Independent security reviewer must assess
|
||||||
|
public alias composition and all limits, repeat commands and record acceptance
|
||||||
|
before the coordinator resumes the parent in another fresh process.
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
engine-strict=true
|
||||||
|
strict-ssl=true
|
||||||
|
registry=https://registry.npmjs.org/
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
# Locked dependency license inventory
|
||||||
|
|
||||||
|
All optional build-host entries are included; installed graph checked separately.
|
||||||
|
|
||||||
|
| Package | Version | License |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| @oxc-project/types | 0.154.0 | MIT |
|
||||||
|
| @rolldown/binding-android-arm-eabi | 1.2.14 | MIT |
|
||||||
|
| @rolldown/binding-android-arm64 | 1.2.14 | MIT |
|
||||||
|
| @rolldown/binding-darwin-arm64 | 1.2.14 | MIT |
|
||||||
|
| @rolldown/binding-darwin-x64 | 1.2.14 | MIT |
|
||||||
|
| @rolldown/binding-freebsd-x64 | 1.2.14 | MIT |
|
||||||
|
| @rolldown/binding-linux-arm-gnueabihf | 1.2.14 | MIT |
|
||||||
|
| @rolldown/binding-linux-arm64-gnu | 1.2.14 | MIT |
|
||||||
|
| @rolldown/binding-linux-arm64-musl | 1.2.14 | MIT |
|
||||||
|
| @rolldown/binding-linux-ppc64-gnu | 1.2.14 | MIT |
|
||||||
|
| @rolldown/binding-linux-s390x-gnu | 1.2.14 | MIT |
|
||||||
|
| @rolldown/binding-linux-x64-gnu | 1.2.14 | MIT |
|
||||||
|
| @rolldown/binding-linux-x64-musl | 1.2.14 | MIT |
|
||||||
|
| @rolldown/binding-openharmony-arm64 | 1.2.14 | MIT |
|
||||||
|
| @rolldown/binding-win32-arm64-msvc | 1.2.14 | MIT |
|
||||||
|
| @rolldown/binding-win32-x64-msvc | 1.2.14 | MIT |
|
||||||
|
| @rolldown/pluginutils | 1.0.1 | MIT |
|
||||||
|
| @types/node | 22.20.5 | MIT |
|
||||||
|
| @types/punycode | 2.1.4 | MIT |
|
||||||
|
| @types/ws | 8.18.2 | MIT |
|
||||||
|
| detect-libc | 2.1.2 | Apache-2.0 |
|
||||||
|
| events | 3.3.0 | MIT |
|
||||||
|
| fdir | 6.5.0 | MIT |
|
||||||
|
| fsevents | 2.3.3 | MIT |
|
||||||
|
| lightningcss | 1.33.0 | MPL-2.0 |
|
||||||
|
| lightningcss-android-arm64 | 1.33.0 | MPL-2.0 |
|
||||||
|
| lightningcss-darwin-arm64 | 1.33.0 | MPL-2.0 |
|
||||||
|
| lightningcss-darwin-x64 | 1.33.0 | MPL-2.0 |
|
||||||
|
| lightningcss-freebsd-x64 | 1.33.0 | MPL-2.0 |
|
||||||
|
| lightningcss-linux-arm-gnueabihf | 1.33.0 | MPL-2.0 |
|
||||||
|
| lightningcss-linux-arm64-gnu | 1.33.0 | MPL-2.0 |
|
||||||
|
| lightningcss-linux-arm64-musl | 1.33.0 | MPL-2.0 |
|
||||||
|
| lightningcss-linux-x64-gnu | 1.33.0 | MPL-2.0 |
|
||||||
|
| lightningcss-linux-x64-musl | 1.33.0 | MPL-2.0 |
|
||||||
|
| lightningcss-win32-arm64-msvc | 1.33.0 | MPL-2.0 |
|
||||||
|
| lightningcss-win32-x64-msvc | 1.33.0 | MPL-2.0 |
|
||||||
|
| nanoid | 3.3.20 | MIT |
|
||||||
|
| picocolors | 1.1.1 | ISC |
|
||||||
|
| picomatch | 4.0.7 | MIT |
|
||||||
|
| postcss | 8.5.30 | MIT |
|
||||||
|
| punycode | 2.3.1 | MIT |
|
||||||
|
| rolldown | 1.2.14 | MIT |
|
||||||
|
| sdp | 3.2.2 | MIT |
|
||||||
|
| source-map-js | 1.2.2 | BSD-3-Clause |
|
||||||
|
| stanza | 12.22.1 | MIT |
|
||||||
|
| tinyglobby | 0.2.17 | MIT |
|
||||||
|
| undici-types | 6.21.0 | MIT |
|
||||||
|
| vite | 8.3.4 | MIT |
|
||||||
|
| ws | 8.22.0 | MIT |
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
# Independent oracle licenses
|
||||||
|
|
||||||
|
Test-only Python dependencies; not browser/app/runtime dependencies.
|
||||||
|
|
||||||
|
## scramp 1.4.6
|
||||||
|
|
||||||
|
MIT No Attribution
|
||||||
|
|
||||||
|
Copyright The Contributors
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
|
|
||||||
|
## asn1crypto 1.5.1
|
||||||
|
|
||||||
|
Copyright (c) 2015-2022 Will Bond <will@wbond.net>
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
this software and associated documentation files (the "Software"), to deal in
|
||||||
|
the Software without restriction, including without limitation the rights to
|
||||||
|
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||||
|
of the Software, and to permit persons to whom the Software is furnished to do
|
||||||
|
so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
|
|
||||||
@@ -0,0 +1,103 @@
|
|||||||
|
# Isolated browser SCRAM-SHA-512 validation
|
||||||
|
|
||||||
|
S00-019 fixture experiment, proposed for independent security review. No live
|
||||||
|
account/transport or parent library selection. Node executes the **browser-built
|
||||||
|
backend**, not Stanza's Node crypto. This is browser bundle/backend evidence,
|
||||||
|
not an actual Chrome/Firefox/Safari execution or WSS interoperability result.
|
||||||
|
|
||||||
|
## Reproduce
|
||||||
|
|
||||||
|
Use accepted Node 24.21.0 / npm 11.19.0. Existing reviewed macOS ARM64 runtime:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
export PATH=/private/tmp/luma-stack-runtime/node-v24.21.0-darwin-arm64/bin:$PATH
|
||||||
|
npm ci --prefix experiments/0002-xmpp-library/sha512-validation --ignore-scripts --no-audit --no-fund --cache /private/tmp/luma-sha512-review-cache --fetch-retries=0 --fetch-timeout=15000
|
||||||
|
npm test --prefix experiments/0002-xmpp-library/sha512-validation
|
||||||
|
python3 experiments/0002-xmpp-library/sha512-validation/check-dependencies.py
|
||||||
|
```
|
||||||
|
|
||||||
|
Use a new empty cache for independent clean-install evidence. The dependency
|
||||||
|
check also reads the original installed candidate at the parent node_modules
|
||||||
|
for byte comparison; it does not modify it. If unavailable, reproduce its pinned
|
||||||
|
install under the parent task's authority or compare Stanza against its locked
|
||||||
|
tarball separately; do not change the parent from this task.
|
||||||
|
|
||||||
|
`npm test` builds before running 87 checks. Vite's default browser resolution
|
||||||
|
selects stanza/platform/browser. events 3.3.0 is the explicit browser EventEmitter
|
||||||
|
polyfill. Build fails if module IDs show Node backend/builtins/browser externals
|
||||||
|
or no browser HMAC. Output `dist/modules.json` lists resolved modules;
|
||||||
|
`dist/browser.mjs` is a self-contained ESM bundle. Backend tests inspect graph,
|
||||||
|
no runtime imports/require, and exported platform name `browser`. All integrated
|
||||||
|
exchanges import this artifact, never Node source. Node crypto is an independent
|
||||||
|
primitive oracle in tests only; the integration suite has no Node crypto import.
|
||||||
|
The native RNG path uses host getRandomValues, not a fabricated success value.
|
||||||
|
|
||||||
|
## Independent full-exchange oracle
|
||||||
|
|
||||||
|
The checked-in `test/scramp-vectors.json` is fixed output from **Scramp 1.4.6**
|
||||||
|
client and server (Python test-only MIT-0 dependency, asn1crypto 1.5.1 MIT).
|
||||||
|
Both client proof and server-final are independently verified. Inputs combine
|
||||||
|
public RFC 7677 nonce/salt/4096-iteration data with SHA-512 and synthetic passwords
|
||||||
|
pencil, 131 A characters and péncil. These SHA-512 transcripts are independently
|
||||||
|
derived vectors, not a claim that RFC 7677 specifies SHA-512 examples.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
python3 -m venv /private/tmp/luma-sha512-review-oracle
|
||||||
|
/private/tmp/luma-sha512-review-oracle/bin/python -m pip install --require-hashes --only-binary=:all: --no-cache-dir -r experiments/0002-xmpp-library/sha512-validation/oracle-requirements.txt
|
||||||
|
/private/tmp/luma-sha512-review-oracle/bin/python experiments/0002-xmpp-library/sha512-validation/oracle.py
|
||||||
|
```
|
||||||
|
|
||||||
|
Python >=3.8; each wheel version/hash is exact. Default oracle mode compares
|
||||||
|
existing fixed fixtures; it does not rewrite them. `--write` is an explicit
|
||||||
|
fixture-generation operation. Scramp is excluded from the app/browser graph.
|
||||||
|
1.4.6 supports the available Python 3.9 host and the documented APIs; no current
|
||||||
|
release or supply-chain/advisory clearance is inferred. Notices are preserved.
|
||||||
|
|
||||||
|
## Public mechanism path
|
||||||
|
|
||||||
|
`Sha512Mechanism` composes Stanza's exported `SCRAM` using its public string
|
||||||
|
constructor with `SCRAM-SHA512`, selecting the existing documented-by-shipped-code
|
||||||
|
`sha512` alias. Its separate Mechanism.name is always **SCRAM-SHA-512**. Internal
|
||||||
|
alias never goes on the wire. Public Factory.disable/register installs that
|
||||||
|
constructor. No Stanza private field is read/changed, no subclass/private-method
|
||||||
|
hook or vendor/bundler crypto patch. Actual cryptographic primitives, PBKDF-style
|
||||||
|
SCRAM composition, SASLprep and verifier stay in unmodified Stanza library code.
|
||||||
|
This is version-specific public API composition, not an upstream endorsement of
|
||||||
|
a future stable alias contract; pins and full vectors are mandatory safeguards.
|
||||||
|
|
||||||
|
The isolated feature handler is copied from reviewed S00-017, narrowed to SHA-512
|
||||||
|
and 88-character base64 verifier, and registers the new mechanism. Install via
|
||||||
|
Client.use on a fresh Client before any negotiation; never install after stock
|
||||||
|
SASL begins. Mechanism priorities filter to SHA-512 even alongside PLUS/256/1/PLAIN
|
||||||
|
offers; unsupported-only offers fail. Complete product fallback policy stays
|
||||||
|
with S00-003. No real channel binding exists; tlsUnique input fails rather than
|
||||||
|
creating PLUS or a fabricated binding. Initial GS2 is n,, and c=biws.
|
||||||
|
|
||||||
|
Guard requires completed proof send before success, strict bounded first/final
|
||||||
|
syntax, then real processSuccess/finalize with both authenticated flags. Missing,
|
||||||
|
forged, malformed, premature/duplicate/racing final and replay fail without auth
|
||||||
|
or restart. Async reset/send failures discard continuation. No derived cache is
|
||||||
|
persisted/emitted and no raw credentials/peer data is logged by authored code.
|
||||||
|
Captured send/transport/disconnect effects are explicit fixtures; actual Client,
|
||||||
|
registry/XML parser, feature dispatcher, mechanism and crypto run.
|
||||||
|
|
||||||
|
## Limits
|
||||||
|
|
||||||
|
Only server-final in success additional-data, no SCRAM extensions/separate final
|
||||||
|
challenge. Limits 4096 decoded bytes and 100000 iterations are fixture policy.
|
||||||
|
Synchronous library PBKDF work can block the UI; no worker/performance acceptance.
|
||||||
|
No constant-time claim: Stanza compares verifier strings. No timeout is added;
|
||||||
|
server silence stays unauthenticated until external reset/cancellation. No
|
||||||
|
arbitrary Unicode/SASLprep conformance certification beyond the fixed case.
|
||||||
|
Native browser engine/RNG execution, WSS/TLS, independent live server/client,
|
||||||
|
parent bounded-ingress integration, complete mechanism policy and release
|
||||||
|
readiness remain outside this experiment. No TLS/certificate/CORS bypass or
|
||||||
|
plaintext downgrade. Browser syntax floors match accepted Vite targets
|
||||||
|
Chrome/Edge 111, Firefox 114, Safari 16.4; API requirements include crypto RNG,
|
||||||
|
fetch and WebSocket. These are build floors, not a certified product matrix.
|
||||||
|
|
||||||
|
Licenses: full locked graph in LICENSE_INVENTORY.md, installed notices including
|
||||||
|
Stanza browser crypto ancestry in THIRD_PARTY_NOTICES.md; oracle notices separate.
|
||||||
|
Vite/build native components are not browser runtime crypto. events maintenance
|
||||||
|
has few contributors per upstream; Stanza crypto is reviewed here with vectors,
|
||||||
|
not newly audited. Dependency/advisory/provenance review remains release work.
|
||||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,10 @@
|
|||||||
|
import client from 'stanza/Client.js';
|
||||||
|
import jxt from 'stanza/jxt/index.js';
|
||||||
|
import platform from 'stanza/platform';
|
||||||
|
import sasl from 'stanza/lib/sasl/index.js';
|
||||||
|
export {default as validatedSasl} from './validated-sasl.mjs';
|
||||||
|
export {Sha512Mechanism} from './sha512-mechanism.mjs';
|
||||||
|
export const Client = client.default;
|
||||||
|
export const parse = jxt.parse;
|
||||||
|
export const {Buffer, createHash, createHmac, name: backend, randomBytes} = platform;
|
||||||
|
export const StockScram = sasl.SCRAM;
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
"""Verify exact installed pins/notices, immutable vendor files and browser graph."""
|
||||||
|
import hashlib
|
||||||
|
import json
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
root = Path(__file__).resolve().parent
|
||||||
|
lock = json.loads((root / 'package-lock.json').read_text())
|
||||||
|
manifest = json.loads((root / 'package.json').read_text())
|
||||||
|
inventory = (root / 'LICENSE_INVENTORY.md').read_text()
|
||||||
|
notices = (root / 'THIRD_PARTY_NOTICES.md').read_text()
|
||||||
|
installed = 0
|
||||||
|
for section in ['dependencies', 'devDependencies']:
|
||||||
|
for name, version in manifest[section].items():
|
||||||
|
assert lock['packages']['node_modules/' + name]['version'] == version
|
||||||
|
for path, entry in lock['packages'].items():
|
||||||
|
if not path:
|
||||||
|
continue
|
||||||
|
name = path.split('node_modules/')[-1]
|
||||||
|
assert entry['resolved'].startswith('https://registry.npmjs.org/')
|
||||||
|
assert entry['integrity'].startswith('sha512-')
|
||||||
|
assert f"| {name} | {entry['version']} | {entry['license']} |" in inventory
|
||||||
|
package = root / path
|
||||||
|
if not package.exists():
|
||||||
|
assert entry.get('optional'), name
|
||||||
|
continue
|
||||||
|
meta = json.loads((package / 'package.json').read_text())
|
||||||
|
assert meta['version'] == entry['version'], name
|
||||||
|
assert meta['license'] == entry['license'], name
|
||||||
|
licenses = [f for f in package.iterdir() if f.is_file() and
|
||||||
|
('license' in f.name.lower() or f.name.lower().startswith(('copying', 'notice')))]
|
||||||
|
if name == '@rolldown/binding-darwin-arm64':
|
||||||
|
assert not licenses
|
||||||
|
licenses = [root / 'node_modules/rolldown/LICENSE',
|
||||||
|
root / 'node_modules/rolldown/THIRD-PARTY-LICENSE']
|
||||||
|
assert licenses, name
|
||||||
|
for file in licenses:
|
||||||
|
assert file.read_text().rstrip() in notices, file
|
||||||
|
installed += 1
|
||||||
|
# Parent is read-only evidence of unmodified pinned candidate, not a dependency.
|
||||||
|
original = root.parent / 'node_modules/stanza'
|
||||||
|
current = root / 'node_modules/stanza'
|
||||||
|
files = [f for f in current.rglob('*') if f.is_file()]
|
||||||
|
assert {f.relative_to(current) for f in files} == {
|
||||||
|
f.relative_to(original) for f in original.rglob('*') if f.is_file()}
|
||||||
|
for file in files:
|
||||||
|
assert file.read_bytes() == (original / file.relative_to(current)).read_bytes(), file
|
||||||
|
modules = json.loads((root / 'dist/modules.json').read_text())
|
||||||
|
assert any(x.endswith('/stanza/platform/browser/crypto/Hmac.js') for x in modules)
|
||||||
|
assert not any('/platform/node/' in x or x.startswith('node:') or 'browser-external' in x for x in modules)
|
||||||
|
print(f"{len(lock['packages']) - 1} locked entries; {installed} installed licenses/pins; "
|
||||||
|
f"{len(files)} unchanged Stanza files; browser backend confirmed")
|
||||||
|
for name in ['package-lock.json', 'sha512-mechanism.mjs', 'validated-sasl.mjs',
|
||||||
|
'test/scramp-vectors.json', 'dist/browser.mjs', 'dist/modules.json']:
|
||||||
|
print(name, hashlib.sha256((root / name).read_bytes()).hexdigest())
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
scramp==1.4.6 --hash=sha256:a0cf9d2b4624b69bac5432dd69fecfc55a542384fe73c3a23ed9b138cda484e1
|
||||||
|
asn1crypto==1.5.1 --hash=sha256:db4e40728b728508912cbb3d44f19ce188f218e9eba635821bb4b68564f8fd67
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
"""Independent existing SCRAM client/server; synthetic public fixture data only."""
|
||||||
|
import base64
|
||||||
|
import json
|
||||||
|
import sys
|
||||||
|
from importlib.metadata import version
|
||||||
|
from pathlib import Path
|
||||||
|
from scramp import ScramClient, ScramMechanism
|
||||||
|
|
||||||
|
assert version("scramp") == "1.4.6"
|
||||||
|
assert version("asn1crypto") == "1.5.1"
|
||||||
|
path = Path(__file__).parent / "test" / "scramp-vectors.json"
|
||||||
|
nonce = "rOprNGfwEbeRWgbNEkqO"
|
||||||
|
server_nonce = "%hvYDpWUa2RaTCAfuxFIlj)hNlF$k0"
|
||||||
|
salt = base64.b64decode("W22ZaJ0SNY7soEsUEjb6gQ==")
|
||||||
|
results = []
|
||||||
|
for password in ["pencil", "A" * 131, "p\u00e9ncil"]:
|
||||||
|
mechanism = ScramMechanism("SCRAM-SHA-512")
|
||||||
|
auth = mechanism.make_auth_info(password, iteration_count=4096, salt=salt)
|
||||||
|
def auth_fn(username):
|
||||||
|
assert username == "user"
|
||||||
|
return auth
|
||||||
|
server = mechanism.make_server(auth_fn, s_nonce=server_nonce)
|
||||||
|
client = ScramClient(["SCRAM-SHA-512"], "user", password, c_nonce=nonce)
|
||||||
|
initial = client.get_client_first()
|
||||||
|
server.set_client_first(initial)
|
||||||
|
first = server.get_server_first()
|
||||||
|
client.set_server_first(first)
|
||||||
|
proof = client.get_client_final()
|
||||||
|
server.set_client_final(proof) # Independent server verifies proof.
|
||||||
|
final = server.get_server_final()
|
||||||
|
client.set_server_final(final) # Independent client verifies server.
|
||||||
|
results.append(dict(mechanism="SCRAM-SHA-512", username="user", password=password,
|
||||||
|
nonce=nonce, initial=initial, first=first, proof=proof, final=final))
|
||||||
|
if sys.argv[1:] == ["--write"]:
|
||||||
|
path.write_text(json.dumps(results, indent=2) + "\n")
|
||||||
|
else:
|
||||||
|
assert not sys.argv[1:]
|
||||||
|
assert json.loads(path.read_text()) == results
|
||||||
|
print("scramp 1.4.6: three fixed SHA-512 client/server exchanges verified")
|
||||||
@@ -0,0 +1,925 @@
|
|||||||
|
{
|
||||||
|
"name": "luma-browser-scram-sha512-experiment",
|
||||||
|
"version": "0.0.0",
|
||||||
|
"lockfileVersion": 3,
|
||||||
|
"requires": true,
|
||||||
|
"packages": {
|
||||||
|
"": {
|
||||||
|
"name": "luma-browser-scram-sha512-experiment",
|
||||||
|
"version": "0.0.0",
|
||||||
|
"dependencies": {
|
||||||
|
"events": "3.3.0",
|
||||||
|
"stanza": "12.22.1"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"vite": "8.3.4"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "24.21.0",
|
||||||
|
"npm": "11.19.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@oxc-project/types": {
|
||||||
|
"version": "0.154.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.154.0.tgz",
|
||||||
|
"integrity": "sha512-KYOUGQFlLWR+s4QMV2Q9ko38TZm7EdO87Qm7rvZLqk/webse/cDjtyxKhg/47l0byCbs/k46LT8qc4PKjAikzA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/oxc-project"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@rolldown/binding-android-arm-eabi": {
|
||||||
|
"version": "1.2.14",
|
||||||
|
"resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm-eabi/-/binding-android-arm-eabi-1.2.14.tgz",
|
||||||
|
"integrity": "sha512-aWR+XKH9+rW7m5fWq/5360J3mHycrx/o6xZM2DQFdJnTMdyP7/WzxPjhtLbVWdKo2y612/4U1bWqb3HQhiF7vg==",
|
||||||
|
"cpu": [
|
||||||
|
"arm"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"android"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": "^20.19.0 || >=22.12.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@rolldown/binding-android-arm64": {
|
||||||
|
"version": "1.2.14",
|
||||||
|
"resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.2.14.tgz",
|
||||||
|
"integrity": "sha512-QiVxHofl6A8lQU7WAtOUY4QMVZXohxwz7WJJoeh8NBEUyKfIkMRKhYyq8az/1RypNiIXzNTtiS6KwHz0erxYRQ==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"android"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": "^20.19.0 || >=22.12.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@rolldown/binding-darwin-arm64": {
|
||||||
|
"version": "1.2.14",
|
||||||
|
"resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.2.14.tgz",
|
||||||
|
"integrity": "sha512-lTQOzHuBIooh3jIr5XINQkjPGEuGy+Dsy0meXDr6Sjty0b99kYgGh4uBC/8mJX1T0kC8d6S0aPP1Y+bHkh6aBw==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"darwin"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": "^20.19.0 || >=22.12.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@rolldown/binding-darwin-x64": {
|
||||||
|
"version": "1.2.14",
|
||||||
|
"resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.2.14.tgz",
|
||||||
|
"integrity": "sha512-hyMKEGatjmBYCs/Q4sbIcL/GqpFEqsX3wc+HYVQ/85A4rxc1o7rPm4DUI4GV4k/h0ITddwFSGpqQunI14GXjTA==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"darwin"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": "^20.19.0 || >=22.12.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@rolldown/binding-freebsd-x64": {
|
||||||
|
"version": "1.2.14",
|
||||||
|
"resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.2.14.tgz",
|
||||||
|
"integrity": "sha512-u371GbXoVXrEgP20PwzoGNOdAOqAMhzo19JzzY+V4YmmnC+DTOoqdbMu+O+54emM59Mik/UdrtWgcuEDKWRPKA==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"freebsd"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": "^20.19.0 || >=22.12.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@rolldown/binding-linux-arm-gnueabihf": {
|
||||||
|
"version": "1.2.14",
|
||||||
|
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.2.14.tgz",
|
||||||
|
"integrity": "sha512-BqwKsdZQf6EqVWr8fv2MrDYzAvOZ7+NYFSJESRGs87fkseEhs0TU9TXgRJTkZoRRkxTizyEg1LKUXfU3/78uqg==",
|
||||||
|
"cpu": [
|
||||||
|
"arm"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": "^20.19.0 || >=22.12.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@rolldown/binding-linux-arm64-gnu": {
|
||||||
|
"version": "1.2.14",
|
||||||
|
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.2.14.tgz",
|
||||||
|
"integrity": "sha512-SD9kY8M009x/7s57Z1dgUVlP5+uYBgKQK0Tkr1WIWngrtUJmRCW1RJFMY7QDla2khXhmg2i5Io7MUjcmZNZ/KQ==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"libc": [
|
||||||
|
"glibc"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": "^20.19.0 || >=22.12.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@rolldown/binding-linux-arm64-musl": {
|
||||||
|
"version": "1.2.14",
|
||||||
|
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.2.14.tgz",
|
||||||
|
"integrity": "sha512-BJ2CL19M6uuWesMgnqJzgxoI+N+eV7b0A9O9vLV42y+gitC41fNa8KElau1tHVzuLWX5GzEuxMPhAO0tQXQMkg==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"libc": [
|
||||||
|
"musl"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": "^20.19.0 || >=22.12.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@rolldown/binding-linux-ppc64-gnu": {
|
||||||
|
"version": "1.2.14",
|
||||||
|
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.2.14.tgz",
|
||||||
|
"integrity": "sha512-4+Kl8daPV8NDHaD7mfCcc1pRM+Qg7ab8DyMm5FG5WoZgv4h2i3KFN1CybYBNbQqmuYm7ftJLvmMI1eHYJ22S/Q==",
|
||||||
|
"cpu": [
|
||||||
|
"ppc64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"libc": [
|
||||||
|
"glibc"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": "^20.19.0 || >=22.12.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@rolldown/binding-linux-s390x-gnu": {
|
||||||
|
"version": "1.2.14",
|
||||||
|
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.2.14.tgz",
|
||||||
|
"integrity": "sha512-V/h8ePq1fKDrmtFMz6xcCN3DKvuvMRrwwD1q0PkBeeo4DpK64biaKvqWGVxqCeYKqeRk7gE7/iKPxLCn08O97A==",
|
||||||
|
"cpu": [
|
||||||
|
"s390x"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"libc": [
|
||||||
|
"glibc"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": "^20.19.0 || >=22.12.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@rolldown/binding-linux-x64-gnu": {
|
||||||
|
"version": "1.2.14",
|
||||||
|
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.2.14.tgz",
|
||||||
|
"integrity": "sha512-WxalXzgrLVpKC93xfpaJ3gCXUWzeCr+FRCbDVD2mUAy4L6Vo3lNVz0epUEUpxPWJkLNYUwM4OSIikv36OJ9mLg==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"libc": [
|
||||||
|
"glibc"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": "^20.19.0 || >=22.12.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@rolldown/binding-linux-x64-musl": {
|
||||||
|
"version": "1.2.14",
|
||||||
|
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.2.14.tgz",
|
||||||
|
"integrity": "sha512-3G3tnAmTpdk9jO3GQtyHXqeFD2SlecfTws/Y3M2mmj9nMBwwuLT4kur8sZKeu7ENUHSpVO3cIsm5A52LEWWwlg==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"libc": [
|
||||||
|
"musl"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": "^20.19.0 || >=22.12.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@rolldown/binding-openharmony-arm64": {
|
||||||
|
"version": "1.2.14",
|
||||||
|
"resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.2.14.tgz",
|
||||||
|
"integrity": "sha512-qyr5XiNGxm2ytx17vvtcJ9nXn6v1gENETi503bJx3OGwZ1tSonj7BosxbMa/xeAfSl5iS9X6KjEWYXLu6v1L7Q==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"openharmony"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": "^20.19.0 || >=22.12.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@rolldown/binding-win32-arm64-msvc": {
|
||||||
|
"version": "1.2.14",
|
||||||
|
"resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.2.14.tgz",
|
||||||
|
"integrity": "sha512-oA4o5o0S2yB199YyYXDzKUv1Z56Rc+mgOjwm7Z/vJ9Z8igiDPYvoOuDyo6g7AK/ZaF09nPhDKVnGBcYHO11GXA==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"win32"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": "^20.19.0 || >=22.12.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@rolldown/binding-win32-x64-msvc": {
|
||||||
|
"version": "1.2.14",
|
||||||
|
"resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.2.14.tgz",
|
||||||
|
"integrity": "sha512-9mE94bf2GZUaR/fUSXCMpBoWkctu421Mi0ORpm4zqfbkjKep7RxzPFa5etcGs5h+2KApoWBOA/hAJa/7saGgEQ==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"win32"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": "^20.19.0 || >=22.12.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@rolldown/pluginutils": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz",
|
||||||
|
"integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/@types/node": {
|
||||||
|
"version": "22.20.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.20.5.tgz",
|
||||||
|
"integrity": "sha512-U2+DNr+wSjpsTS/wZGYHq7GcwfuSmKiKvoPvK22zwTlRhU91yOniN4qRR5KhIjvif7ysw/dz/hKmfDH0Ris4aA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"undici-types": "~6.21.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@types/punycode": {
|
||||||
|
"version": "2.1.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/punycode/-/punycode-2.1.4.tgz",
|
||||||
|
"integrity": "sha512-trzh6NzBnq8yw5e35f8xe8VTYjqM3NE7bohBtvDVf/dtUer3zYTLK1Ka3DG3p7bdtoaOHZucma6FfVKlQ134pQ==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/@types/ws": {
|
||||||
|
"version": "8.18.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.2.tgz",
|
||||||
|
"integrity": "sha512-67MQl+fpWKVTT1NYdnmo3U4sc/xPo/zQBncVnI74qmQa0z/b+1g6iYqNmGCPbxO+zz2aklb08a0oHfegiVd0/w==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/node": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/detect-libc": {
|
||||||
|
"version": "2.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
|
||||||
|
"integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/events": {
|
||||||
|
"version": "3.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz",
|
||||||
|
"integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.8.x"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"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/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/lightningcss": {
|
||||||
|
"version": "1.33.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.33.0.tgz",
|
||||||
|
"integrity": "sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MPL-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"detect-libc": "^2.0.3"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 12.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/parcel"
|
||||||
|
},
|
||||||
|
"optionalDependencies": {
|
||||||
|
"lightningcss-android-arm64": "1.33.0",
|
||||||
|
"lightningcss-darwin-arm64": "1.33.0",
|
||||||
|
"lightningcss-darwin-x64": "1.33.0",
|
||||||
|
"lightningcss-freebsd-x64": "1.33.0",
|
||||||
|
"lightningcss-linux-arm-gnueabihf": "1.33.0",
|
||||||
|
"lightningcss-linux-arm64-gnu": "1.33.0",
|
||||||
|
"lightningcss-linux-arm64-musl": "1.33.0",
|
||||||
|
"lightningcss-linux-x64-gnu": "1.33.0",
|
||||||
|
"lightningcss-linux-x64-musl": "1.33.0",
|
||||||
|
"lightningcss-win32-arm64-msvc": "1.33.0",
|
||||||
|
"lightningcss-win32-x64-msvc": "1.33.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/lightningcss-android-arm64": {
|
||||||
|
"version": "1.33.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.33.0.tgz",
|
||||||
|
"integrity": "sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MPL-2.0",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"android"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 12.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/parcel"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/lightningcss-darwin-arm64": {
|
||||||
|
"version": "1.33.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.33.0.tgz",
|
||||||
|
"integrity": "sha512-Sciaz8eenNTKn9b3t7+xr0ipTp9YxKQY4npwQ3mrRuL0BAVHBLyZxofhaKBAVtzmtRZ/zTyo0/to4B1uWG/Djg==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MPL-2.0",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"darwin"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 12.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/parcel"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/lightningcss-darwin-x64": {
|
||||||
|
"version": "1.33.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.33.0.tgz",
|
||||||
|
"integrity": "sha512-Z5UPAxzrjlWNNyGy6i65cJzzvgJ5D3T6wMvs+gWpY9d7qRhANrxqAp6LhxIgZhWEw18RfJTGcRxjuLIBr+m8XQ==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MPL-2.0",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"darwin"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 12.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/parcel"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/lightningcss-freebsd-x64": {
|
||||||
|
"version": "1.33.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.33.0.tgz",
|
||||||
|
"integrity": "sha512-QQM/Ti/hQajJwCY+RiWuCZ9sdtI/XQk7nDK5vC8kkdwixezOlDgvDx7+RT+QjK6FcFT4MpsuoBnHIo/O3StRRg==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MPL-2.0",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"freebsd"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 12.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/parcel"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/lightningcss-linux-arm-gnueabihf": {
|
||||||
|
"version": "1.33.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.33.0.tgz",
|
||||||
|
"integrity": "sha512-N7FVBe6iS24MlM6R/4RBTxGhQheZGs7tiQ9U32UtF75NzP5Q7xWPRqLBCKxlRQRk3rY1jCIPLzx7WzOhuUIRLQ==",
|
||||||
|
"cpu": [
|
||||||
|
"arm"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MPL-2.0",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 12.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/parcel"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/lightningcss-linux-arm64-gnu": {
|
||||||
|
"version": "1.33.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.33.0.tgz",
|
||||||
|
"integrity": "sha512-j2v/itmy4HlNxlc6voKXYgBqNi0Ng2LShg4z7GufpEgs05P+2suBVyi9I6YHq5uoVFx9ETin3eCEhLVyXGQnKg==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"libc": [
|
||||||
|
"glibc"
|
||||||
|
],
|
||||||
|
"license": "MPL-2.0",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 12.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/parcel"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/lightningcss-linux-arm64-musl": {
|
||||||
|
"version": "1.33.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.33.0.tgz",
|
||||||
|
"integrity": "sha512-yiO5ROMuYQgXbC60yjZU5CYSFZGKXL0HFATXt9mHJn1+zW55oCtMI9NfcVhYLMFDL7gV7oBPon/EmMMGg2OvtQ==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"libc": [
|
||||||
|
"musl"
|
||||||
|
],
|
||||||
|
"license": "MPL-2.0",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 12.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/parcel"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/lightningcss-linux-x64-gnu": {
|
||||||
|
"version": "1.33.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.33.0.tgz",
|
||||||
|
"integrity": "sha512-ar+Ju7LmcN0Jo4FpL4hpFybwNG9/3A/Br5KW2n2jyODg3MEZXaDYADdemoNS+BDNfMgKvylJLj4S5tyRActuAg==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"libc": [
|
||||||
|
"glibc"
|
||||||
|
],
|
||||||
|
"license": "MPL-2.0",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 12.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/parcel"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/lightningcss-linux-x64-musl": {
|
||||||
|
"version": "1.33.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.33.0.tgz",
|
||||||
|
"integrity": "sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"libc": [
|
||||||
|
"musl"
|
||||||
|
],
|
||||||
|
"license": "MPL-2.0",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 12.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/parcel"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/lightningcss-win32-arm64-msvc": {
|
||||||
|
"version": "1.33.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.33.0.tgz",
|
||||||
|
"integrity": "sha512-1K+MPfLSFVpphzpdbfkhlWk6wBrTObBzS2T6db10PNOZgR9GoVsAWzwNyuhUYYbTp23j+4RrncfujZ4uAzXvwA==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MPL-2.0",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"win32"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 12.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/parcel"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/lightningcss-win32-x64-msvc": {
|
||||||
|
"version": "1.33.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.33.0.tgz",
|
||||||
|
"integrity": "sha512-OlEICDx/Xl0FqSp4bry8zFnCvGpig3Gl4gCquvYwHuqJKEC1+n9NgDniFvqHGmMv1ZkqDJrDqKKSykTDX+ehuA==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MPL-2.0",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"win32"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 12.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/parcel"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/nanoid": {
|
||||||
|
"version": "3.3.20",
|
||||||
|
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.20.tgz",
|
||||||
|
"integrity": "sha512-uKdg2G3GNCKQn9byYOpxbGqrT2fGO5KRt5J/8b3pok8rT6qxGWF6hxMyJiEYtAf+FVyYuD9hRaDqX5uPFYJ4ZQ==",
|
||||||
|
"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/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": "4.0.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz",
|
||||||
|
"integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/jonschlinkert"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/postcss": {
|
||||||
|
"version": "8.5.30",
|
||||||
|
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.30.tgz",
|
||||||
|
"integrity": "sha512-zkd+e9S1AQHhmaBKye9zlFHWjOwVgqXl1q3ECYy8561K7qxR2cREf/STLskulBFd78k6AOFeA+ff1TtEzk9bAA==",
|
||||||
|
"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.20",
|
||||||
|
"picocolors": "^1.1.1",
|
||||||
|
"source-map-js": "^1.2.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "^10 || ^12 || >=14"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/punycode": {
|
||||||
|
"version": "2.3.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
|
||||||
|
"integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/rolldown": {
|
||||||
|
"version": "1.2.14",
|
||||||
|
"resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.2.14.tgz",
|
||||||
|
"integrity": "sha512-eTBTTqMR6uHKLgcUqpJP8ntzDI6V4LYA8negAMBEI7gkgVvOdrx6VsvS2Ym+83KhOPdLCHWJu0w7UQFJZhHKNw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@oxc-project/types": "=0.154.0",
|
||||||
|
"@rolldown/pluginutils": "^1.0.0"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"rolldown": "bin/cli.mjs"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "^20.19.0 || >=22.12.0"
|
||||||
|
},
|
||||||
|
"optionalDependencies": {
|
||||||
|
"@rolldown/binding-android-arm-eabi": "1.2.14",
|
||||||
|
"@rolldown/binding-android-arm64": "1.2.14",
|
||||||
|
"@rolldown/binding-darwin-arm64": "1.2.14",
|
||||||
|
"@rolldown/binding-darwin-x64": "1.2.14",
|
||||||
|
"@rolldown/binding-freebsd-x64": "1.2.14",
|
||||||
|
"@rolldown/binding-linux-arm-gnueabihf": "1.2.14",
|
||||||
|
"@rolldown/binding-linux-arm64-gnu": "1.2.14",
|
||||||
|
"@rolldown/binding-linux-arm64-musl": "1.2.14",
|
||||||
|
"@rolldown/binding-linux-ppc64-gnu": "1.2.14",
|
||||||
|
"@rolldown/binding-linux-s390x-gnu": "1.2.14",
|
||||||
|
"@rolldown/binding-linux-x64-gnu": "1.2.14",
|
||||||
|
"@rolldown/binding-linux-x64-musl": "1.2.14",
|
||||||
|
"@rolldown/binding-openharmony-arm64": "1.2.14",
|
||||||
|
"@rolldown/binding-win32-arm64-msvc": "1.2.14",
|
||||||
|
"@rolldown/binding-win32-x64-msvc": "1.2.14"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/sdp": {
|
||||||
|
"version": "3.2.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/sdp/-/sdp-3.2.2.tgz",
|
||||||
|
"integrity": "sha512-xZocWwfyp4hkbN4hLWxMjmv2Q8aNa9MhmOZ7L9aCZPT+dZsgRr6wZRrSYE3HTdyk/2pZKPSgqI7ns7Een1xMSA==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/source-map-js": {
|
||||||
|
"version": "1.2.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.2.tgz",
|
||||||
|
"integrity": "sha512-KGj/8Y43x35aZVDtt+J4mK1hoLGHULMYfSkODJNQjNDC3oW1PqPoxMwo0pLUsWM/UEGzON/NxeHywEfNXNP3Vw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "BSD-3-Clause",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/stanza": {
|
||||||
|
"version": "12.22.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/stanza/-/stanza-12.22.1.tgz",
|
||||||
|
"integrity": "sha512-WwoSl3u5cCEAbed5ao+yt8ByH6AsBJcPDY0gax3JGQkBX/7wEZ2oxu9nMJ7O1tm8slDyH4FBYIpeluHrT+g/LA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/node": "^22.0.0",
|
||||||
|
"@types/punycode": "^2.1.0",
|
||||||
|
"@types/ws": "^8.18.1",
|
||||||
|
"punycode": "^2.1.1",
|
||||||
|
"sdp": "^3.0.2",
|
||||||
|
"ws": "^8.18.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/tinyglobby": {
|
||||||
|
"version": "0.2.17",
|
||||||
|
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz",
|
||||||
|
"integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==",
|
||||||
|
"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/undici-types": {
|
||||||
|
"version": "6.21.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
|
||||||
|
"integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/vite": {
|
||||||
|
"version": "8.3.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/vite/-/vite-8.3.4.tgz",
|
||||||
|
"integrity": "sha512-IftLE8phJhdNRIGNEV4JCCvfNScoZQVfcXaN8TQropWGJtOHcu+iAB9pHcuGvmEb/kddw0LBe/jS8ztdC6xdiQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"lightningcss": "^1.33.0",
|
||||||
|
"picomatch": "^4.0.7",
|
||||||
|
"postcss": "^8.5.29",
|
||||||
|
"rolldown": "~1.2.12",
|
||||||
|
"tinyglobby": "^0.2.17"
|
||||||
|
},
|
||||||
|
"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",
|
||||||
|
"@vitejs/devtools": "^0.7.1",
|
||||||
|
"esbuild": "^0.27.0 || ^0.28.0",
|
||||||
|
"jiti": ">=1.21.0",
|
||||||
|
"less": "^4.0.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
|
||||||
|
},
|
||||||
|
"@vitejs/devtools": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"esbuild": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"jiti": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"less": {
|
||||||
|
"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/ws": {
|
||||||
|
"version": "8.22.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/ws/-/ws-8.22.0.tgz",
|
||||||
|
"integrity": "sha512-Ydggc987+RO0AnWtZ/7Wq9FtNvcrL1b/RO0ud9mWjUPgDrsAAwQSF51sm2hm1XofbU/4jkpGEsLFsZZxU+1DOg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10.0.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"bufferutil": "^4.0.1",
|
||||||
|
"utf-8-validate": ">=5.0.2"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"bufferutil": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"utf-8-validate": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
{
|
||||||
|
"name": "luma-browser-scram-sha512-experiment",
|
||||||
|
"version": "0.0.0",
|
||||||
|
"private": true,
|
||||||
|
"type": "module",
|
||||||
|
"engines": {
|
||||||
|
"node": "24.21.0",
|
||||||
|
"npm": "11.19.0"
|
||||||
|
},
|
||||||
|
"packageManager": "npm@11.19.0",
|
||||||
|
"scripts": {
|
||||||
|
"build": "vite build",
|
||||||
|
"test": "npm run build && node --test test/*.test.mjs"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"stanza": "12.22.1",
|
||||||
|
"events": "3.3.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"vite": "8.3.4"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
import sasl from 'stanza/lib/sasl/index.js';
|
||||||
|
|
||||||
|
// Public constructor selects the existing browser backend's supported hash alias.
|
||||||
|
// The delegate name never goes on the wire. No private field is read or changed.
|
||||||
|
export class Sha512Mechanism {
|
||||||
|
name = 'SCRAM-SHA-512';
|
||||||
|
providesMutualAuthentication = true;
|
||||||
|
#delegate;
|
||||||
|
constructor(name) {
|
||||||
|
if (name !== this.name) throw new Error('Unsupported mechanism');
|
||||||
|
this.#delegate = new sasl.SCRAM('SCRAM-SHA512');
|
||||||
|
}
|
||||||
|
getExpectedCredentials() { return this.#delegate.getExpectedCredentials(); }
|
||||||
|
// Keep derived credentials attempt-local; no cache leaves this boundary.
|
||||||
|
getCacheableCredentials() { return null; }
|
||||||
|
createResponse(credentials) {
|
||||||
|
if (credentials.tlsUnique) throw new Error('Channel binding unavailable');
|
||||||
|
return this.#delegate.createResponse(credentials);
|
||||||
|
}
|
||||||
|
processChallenge(value) { this.#delegate.processChallenge(value); }
|
||||||
|
processSuccess(value) { this.#delegate.processSuccess(value); }
|
||||||
|
finalize() { return this.#delegate.finalize(); }
|
||||||
|
}
|
||||||
|
|
||||||
|
export function registerSha512(client) {
|
||||||
|
client.sasl.disable('SCRAM-SHA-512');
|
||||||
|
client.sasl.register('SCRAM-SHA-512', Sha512Mechanism, 1000);
|
||||||
|
}
|
||||||
@@ -0,0 +1,83 @@
|
|||||||
|
import assert from 'node:assert/strict';
|
||||||
|
import test from 'node:test';
|
||||||
|
import {readFileSync} from 'node:fs';
|
||||||
|
import {createHash as oracleHash, createHmac as oracleHmac} from 'node:crypto';
|
||||||
|
import {Buffer as BrowserBuffer, createHash, createHmac, backend, StockScram, Sha512Mechanism, randomBytes} from '../dist/browser.mjs';
|
||||||
|
const hmacVectors = JSON.parse(readFileSync(new URL('./hmac-vectors.json', import.meta.url)));
|
||||||
|
const scramVectors = JSON.parse(readFileSync(new URL('./scramp-vectors.json', import.meta.url)));
|
||||||
|
|
||||||
|
test('bundle resolves browser backend and contains no external runtime imports', () => {
|
||||||
|
assert.equal(backend, 'browser');
|
||||||
|
const modules = JSON.parse(readFileSync(new URL('../dist/modules.json', import.meta.url)));
|
||||||
|
assert.ok(modules.some(id => id.endsWith('/platform/browser/crypto/Hmac.js')));
|
||||||
|
assert.ok(modules.some(id => id.endsWith('/platform/browser/crypto/SHA-512.js')));
|
||||||
|
assert.ok(!modules.some(id => /platform\/node\/|^node:|browser-external/.test(id)));
|
||||||
|
const bundle = readFileSync(new URL('../dist/browser.mjs', import.meta.url), 'utf8');
|
||||||
|
assert.ok(!/^import\s|\brequire\(/m.test(bundle));
|
||||||
|
});
|
||||||
|
|
||||||
|
for (const vector of hmacVectors) {
|
||||||
|
for (const algorithm of ['sha512', 'SHA512']) {
|
||||||
|
test(`RFC 4231 case ${vector.case}, browser ${algorithm}`, () => {
|
||||||
|
const key = BrowserBuffer.from(vector.key, 'hex');
|
||||||
|
const data = BrowserBuffer.from(vector.data, 'hex');
|
||||||
|
const actual = createHmac(algorithm, key).update(data).digest('hex');
|
||||||
|
assert.equal(actual.slice(0, vector.digest.length), vector.digest);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
test('reproduce browser hyphenated HMAC mismatch, keep Node independent control', () => {
|
||||||
|
const vector = hmacVectors[0];
|
||||||
|
const key = BrowserBuffer.from(vector.key, 'hex');
|
||||||
|
const data = BrowserBuffer.from(vector.data, 'hex');
|
||||||
|
const broken = createHmac('sha-512', key).update(data).digest('hex');
|
||||||
|
assert.notEqual(broken, vector.digest);
|
||||||
|
assert.equal(broken, '9656975ee5de55e75f2976ecce9a04501060b9dc22a6eda2eaef638966280182477fe09f080b2bf564649cad42af8607a2bd8d02979df3a980f15e2326a0a22a');
|
||||||
|
assert.equal(oracleHmac('sha512', Buffer.from(vector.key, 'hex')).update(Buffer.from(vector.data, 'hex')).digest('hex'), vector.digest);
|
||||||
|
});
|
||||||
|
|
||||||
|
const abc = 'ddaf35a193617abacc417349ae20413112e6fa4e89a97ea20a9eeee64b55d39a2192' +
|
||||||
|
'992a274fc1a836ba3c23a3feebbd454d4423643ce80e2a9ac94fa54ca49f';
|
||||||
|
for (const algorithm of ['sha512', 'sha-512', 'SHA512', 'SHA-512']) {
|
||||||
|
test(`fixed SHA512 abc digest, browser ${algorithm}`, () => {
|
||||||
|
assert.equal(createHash(algorithm).update('abc').digest('hex'), abc);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const length of [0, 1, 63, 64, 65, 127, 128, 129, 131, 256]) {
|
||||||
|
test(`browser HMAC alias key length ${length} agrees with Node crypto`, () => {
|
||||||
|
const key = Buffer.alloc(length, 0xab);
|
||||||
|
const data = Buffer.alloc(257, 0xcd);
|
||||||
|
assert.equal(createHmac('sha512', BrowserBuffer.from(key)).update(BrowserBuffer.from(data)).digest('hex'),
|
||||||
|
oracleHmac('sha512', key).update(data).digest('hex'));
|
||||||
|
assert.equal(createHash('sha512').update(BrowserBuffer.from(data)).digest('hex'),
|
||||||
|
oracleHash('sha512').update(data).digest('hex'));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
test('stock browser SHA512 proof mismatch; public alias delegate matches independent vector', () => {
|
||||||
|
const vector = scramVectors[0];
|
||||||
|
const credentials = {username: vector.username, password: vector.password, clientNonce: vector.nonce};
|
||||||
|
const stock = new StockScram('SCRAM-SHA-512');
|
||||||
|
stock.createResponse(credentials);
|
||||||
|
stock.processChallenge(BrowserBuffer.from(vector.first));
|
||||||
|
assert.notEqual(stock.createResponse(credentials).toString(), vector.proof);
|
||||||
|
const selected = new Sha512Mechanism('SCRAM-SHA-512');
|
||||||
|
assert.equal(selected.name, vector.mechanism);
|
||||||
|
assert.equal(selected.createResponse(credentials).toString(), vector.initial);
|
||||||
|
selected.processChallenge(BrowserBuffer.from(vector.first));
|
||||||
|
assert.equal(selected.createResponse(credentials).toString(), vector.proof);
|
||||||
|
selected.processSuccess(BrowserBuffer.from(vector.final));
|
||||||
|
assert.deepEqual(selected.finalize(), {authenticated: true, mutuallyAuthenticated: true});
|
||||||
|
assert.equal(selected.getCacheableCredentials(), null);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('native browser RNG path works without fixed nonce, no invented channel binding', () => {
|
||||||
|
assert.equal(randomBytes(32).length, 32);
|
||||||
|
const selected = new Sha512Mechanism('SCRAM-SHA-512');
|
||||||
|
const initial = selected.createResponse({username: 'user', password: 'pencil'}).toString();
|
||||||
|
assert.match(initial, /^n,,n=user,r=[0-9a-f]{64}$/);
|
||||||
|
assert.throws(() => new Sha512Mechanism('SCRAM-SHA-512-PLUS'));
|
||||||
|
assert.throws(() => new Sha512Mechanism('SCRAM-SHA-512').createResponse({tlsUnique: BrowserBuffer.alloc(32)}));
|
||||||
|
});
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"case": 1,
|
||||||
|
"key": "0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b",
|
||||||
|
"data": "4869205468657265",
|
||||||
|
"digest": "87aa7cdea5ef619d4ff0b4241a1d6cb02379f4e2ce4ec2787ad0b30545e17cdedaa833b7d6b8a702038b274eaea3f4e4be9d914eeb61f1702e696c203a126854"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"case": 2,
|
||||||
|
"key": "4a656665",
|
||||||
|
"data": "7768617420646f2079612077616e7420666f72206e6f7468696e673f",
|
||||||
|
"digest": "164b7a7bfcf819e2e395fbe73b56e0a387bd64222e831fd610270cd7ea2505549758bf75c05a994a6d034f65f8f0e6fdcaeab1a34d4a6b4b636e070a38bce737"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"case": 3,
|
||||||
|
"key": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
|
||||||
|
"data": "dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd",
|
||||||
|
"digest": "fa73b0089d56a284efb0f0756c890be9b1b5dbdd8ee81a3655f83e33b2279d39bf3e848279a722c806b485a47e67c807b946a337bee8942674278859e13292fb"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"case": 4,
|
||||||
|
"key": "0102030405060708090a0b0c0d0e0f10111213141516171819",
|
||||||
|
"data": "cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd",
|
||||||
|
"digest": "b0ba465637458c6990e5a8c5f61d4af7e576d97ff94b872de76f8050361ee3dba91ca5c11aa25eb4d679275cc5788063a5f19741120c4f2de2adebeb10a298dd"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"case": 5,
|
||||||
|
"key": "0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c",
|
||||||
|
"data": "546573742057697468205472756e636174696f6e",
|
||||||
|
"digest": "415fad6271580a531d4179bc891d87a6"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"case": 6,
|
||||||
|
"key": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
|
||||||
|
"data": "54657374205573696e67204c6172676572205468616e20426c6f636b2d53697a65204b6579202d2048617368204b6579204669727374",
|
||||||
|
"digest": "80b24263c7c1a3ebb71493c1dd7be8b49b46d1f41b4aeec1121b013783f8f3526b56d037e05f2598bd0fd2215d6a1e5295e64f73f63f0aec8b915a985d786598"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"case": 7,
|
||||||
|
"key": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
|
||||||
|
"data": "5468697320697320612074657374207573696e672061206c6172676572207468616e20626c6f636b2d73697a65206b657920616e642061206c6172676572207468616e20626c6f636b2d73697a6520646174612e20546865206b6579206e6565647320746f20626520686173686564206265666f7265206265696e6720757365642062792074686520484d414320616c676f726974686d2e",
|
||||||
|
"digest": "e37b6a775dc87dbaa4dfa9f96e5e3ffddebd71f8867289865df5a32d20cdc944b6022cac3c4982b10d5eeb55c3e4de15134676fb6de0446065c97440fa8c6a58"
|
||||||
|
}
|
||||||
|
]
|
||||||
@@ -0,0 +1,302 @@
|
|||||||
|
import assert from 'node:assert/strict';
|
||||||
|
import test from 'node:test';
|
||||||
|
import {readFileSync} from 'node:fs';
|
||||||
|
import {Client, parse, validatedSasl, StockScram} from '../dist/browser.mjs';
|
||||||
|
const vectors = JSON.parse(readFileSync(new URL('./scramp-vectors.json', import.meta.url)));
|
||||||
|
const tick = () => new Promise(resolve => setImmediate(resolve));
|
||||||
|
const deferred = () => {
|
||||||
|
let resolve;
|
||||||
|
const promise = new Promise(r => {resolve = r;});
|
||||||
|
return {promise, resolve};
|
||||||
|
};
|
||||||
|
|
||||||
|
function harness(vector = vectors[1], fixed = true, mechanisms = [vector.mechanism]) {
|
||||||
|
const client = new Client({jid: 'user@example.invalid', autoReconnect: false,
|
||||||
|
credentials: {username: vector.username, password: vector.password, clientNonce: vector.nonce}});
|
||||||
|
const result = {sent: [], success: 0, failed: 0, restart: 0, disconnect: 0, verification: []};
|
||||||
|
// Capture fixture effects; use the real Client, XML registry, feature dispatcher,
|
||||||
|
// mechanism factory and SCRAM crypto. Never connect to a real provider.
|
||||||
|
client.send = async (kind, stanza) => {result.sent.push({kind, stanza});};
|
||||||
|
client.transport = {authenticated: false, restart: () => {result.restart++;}};
|
||||||
|
client.disconnect = async () => {result.disconnect++;};
|
||||||
|
client.on('auth:success', () => {result.success++;});
|
||||||
|
client.on('auth:failed', () => {result.failed++;});
|
||||||
|
if (!fixed) client.sasl.register('SCRAM-SHA-512', StockScram, 1000);
|
||||||
|
if (fixed) {
|
||||||
|
client.use(validatedSasl);
|
||||||
|
const original = client.sasl.createMechanism.bind(client.sasl);
|
||||||
|
client.sasl.createMechanism = names => {
|
||||||
|
const mech = original(names);
|
||||||
|
if (mech) for (const method of ['processSuccess', 'finalize']) {
|
||||||
|
const fn = mech[method].bind(mech);
|
||||||
|
mech[method] = (...args) => {result.verification.push(method); return fn(...args);};
|
||||||
|
}
|
||||||
|
return mech;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
const receive = async (type, value) => {
|
||||||
|
const body = value === undefined ? '' : Buffer.from(value).toString('base64');
|
||||||
|
const xml = parse(`<${type} xmlns="urn:ietf:params:xml:ns:xmpp-sasl">${body}</${type}>`);
|
||||||
|
const stanza = client.stanzas.import(xml);
|
||||||
|
assert.equal(stanza.type, type);
|
||||||
|
client.emit('sasl', stanza);
|
||||||
|
await tick();
|
||||||
|
};
|
||||||
|
const start = async () => {
|
||||||
|
client.emit('features', {sasl: {mechanisms}});
|
||||||
|
await tick();
|
||||||
|
};
|
||||||
|
return {client, result, receive, start};
|
||||||
|
}
|
||||||
|
function rejected(h) {
|
||||||
|
assert.equal(h.result.success, 0);
|
||||||
|
assert.equal(h.result.restart, 0);
|
||||||
|
assert.equal(h.result.failed, 1);
|
||||||
|
assert.equal(h.result.disconnect, 1);
|
||||||
|
assert.equal(h.client.features.negotiated.sasl, false);
|
||||||
|
assert.equal(h.client.transport.authenticated, false);
|
||||||
|
assert.equal(h.client.listenerCount('sasl'), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const vector of vectors) {
|
||||||
|
test(`${vector.mechanism}: independent Scramp proof and verified final through feature dispatcher`, async () => {
|
||||||
|
const h = harness(vector);
|
||||||
|
await h.start();
|
||||||
|
assert.equal(h.result.sent[0].stanza.mechanism, vector.mechanism);
|
||||||
|
assert.equal(h.result.sent[0].stanza.value.toString(), `n,,n=user,r=${vector.nonce}`);
|
||||||
|
await h.receive('challenge', vector.first);
|
||||||
|
assert.equal(h.result.sent[1].stanza.value.toString(), vector.proof);
|
||||||
|
assert.equal(h.result.success, 0);
|
||||||
|
assert.equal(h.result.restart, 0);
|
||||||
|
assert.equal(h.client.transport.authenticated, false);
|
||||||
|
await h.receive('success', vector.final);
|
||||||
|
assert.deepEqual(h.result.verification, ['processSuccess', 'finalize']);
|
||||||
|
assert.equal(h.result.success, 1);
|
||||||
|
assert.equal(h.result.restart, 1);
|
||||||
|
assert.equal(h.result.disconnect, 0);
|
||||||
|
assert.equal(h.client.features.negotiated.sasl, true);
|
||||||
|
assert.equal(h.client.transport.authenticated, true);
|
||||||
|
await h.receive('success', vector.final);
|
||||||
|
assert.equal(h.result.success, 1);
|
||||||
|
assert.equal(h.result.restart, 1);
|
||||||
|
});
|
||||||
|
for (const [label, final] of [
|
||||||
|
['missing', undefined],
|
||||||
|
['forged', 'v=' + (vector.final[2] === 'A' ? 'B' : 'A') + vector.final.slice(3)],
|
||||||
|
['error', 'e=invalid-proof'],
|
||||||
|
['conflicting error', `${vector.final},e=invalid-proof`],
|
||||||
|
['duplicate verifier', `${vector.final},${vector.final}`],
|
||||||
|
['bad base64', 'v=***'],
|
||||||
|
['oversized', 'v=' + 'A'.repeat(4096)]
|
||||||
|
]) {
|
||||||
|
test(`${vector.mechanism}: reject ${label} server-final after proof`, async () => {
|
||||||
|
const h = harness(vector);
|
||||||
|
await h.start();
|
||||||
|
await h.receive('challenge', vector.first);
|
||||||
|
await h.receive('success', final);
|
||||||
|
rejected(h);
|
||||||
|
if (label === 'forged') assert.deepEqual(h.result.verification, ['processSuccess', 'finalize']);
|
||||||
|
await h.receive('success', vector.final);
|
||||||
|
rejected(h);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
test(`${vector.mechanism}: reject valid-looking final before proof`, async () => {
|
||||||
|
const h = harness(vector);
|
||||||
|
await h.start();
|
||||||
|
await h.receive('success', vector.final);
|
||||||
|
rejected(h);
|
||||||
|
assert.deepEqual(h.result.verification, []);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const [label, final] of [['missing', undefined], ['forged', 'v=AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=']]) {
|
||||||
|
test(`stock 12.22.1 reproduces ${label} premature acceptance`, async () => {
|
||||||
|
const h = harness(vectors[1], false);
|
||||||
|
await h.start();
|
||||||
|
await h.receive('success', final);
|
||||||
|
assert.equal(h.result.success, 1);
|
||||||
|
assert.equal(h.result.restart, 1);
|
||||||
|
assert.equal(h.client.transport.authenticated, true);
|
||||||
|
});
|
||||||
|
test(`replacement rejects original ${label} premature defect`, async () => {
|
||||||
|
const h = harness();
|
||||||
|
await h.start();
|
||||||
|
await h.receive('success', final);
|
||||||
|
rejected(h);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const [label, first] of [
|
||||||
|
['wrong nonce', vectors[1].first.replace('r=rOpr', 'r=wrong')],
|
||||||
|
['unextended nonce', `r=${vectors[1].nonce},s=W22ZaJ0SNY7soEsUEjb6gQ==,i=4096`],
|
||||||
|
['iteration overflow', vectors[1].first.replace('i=4096', 'i=100001')],
|
||||||
|
['invalid iterations', vectors[1].first.replace('i=4096', 'i=NaN')],
|
||||||
|
['duplicate attribute', vectors[1].first + ',i=4096'],
|
||||||
|
['mandatory extension', 'm=required,' + vectors[1].first]
|
||||||
|
]) {
|
||||||
|
test(`reject ${label} before library proof work`, async () => {
|
||||||
|
const h = harness();
|
||||||
|
await h.start();
|
||||||
|
await h.receive('challenge', first);
|
||||||
|
rejected(h);
|
||||||
|
assert.equal(h.result.sent.length, 1);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
test('reject success while proof send is pending; no late revival', async () => {
|
||||||
|
const h = harness();
|
||||||
|
await h.start();
|
||||||
|
const pending = deferred();
|
||||||
|
h.client.send = () => pending.promise;
|
||||||
|
await h.receive('challenge', vectors[1].first);
|
||||||
|
await h.receive('success', vectors[1].final);
|
||||||
|
rejected(h);
|
||||||
|
pending.resolve();
|
||||||
|
await tick();
|
||||||
|
await h.receive('success', vectors[1].final);
|
||||||
|
rejected(h);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('duplicate challenge while sending proof fails closed', async () => {
|
||||||
|
const h = harness();
|
||||||
|
await h.start();
|
||||||
|
const pending = deferred();
|
||||||
|
h.client.send = () => pending.promise;
|
||||||
|
await h.receive('challenge', vectors[1].first);
|
||||||
|
await h.receive('challenge', vectors[1].first);
|
||||||
|
rejected(h);
|
||||||
|
pending.resolve();
|
||||||
|
await tick();
|
||||||
|
rejected(h);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('reset while awaiting credentials prevents late auth and listener leak', async () => {
|
||||||
|
const h = harness();
|
||||||
|
const pending = deferred();
|
||||||
|
h.client.getCredentials = () => pending.promise;
|
||||||
|
await h.start();
|
||||||
|
h.client.emit('--reset-stream-features');
|
||||||
|
pending.resolve({username: 'user', password: 'pencil', clientNonce: vectors[1].nonce});
|
||||||
|
await tick();
|
||||||
|
await h.receive('success', vectors[1].final);
|
||||||
|
assert.equal(h.result.sent.length, 0);
|
||||||
|
assert.equal(h.result.success, 0);
|
||||||
|
assert.equal(h.result.restart, 0);
|
||||||
|
assert.equal(h.client.listenerCount('sasl'), 0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('reset after proof, new negotiation succeeds without old handlers', async () => {
|
||||||
|
const h = harness();
|
||||||
|
await h.start();
|
||||||
|
await h.receive('challenge', vectors[1].first);
|
||||||
|
h.client.emit('--reset-stream-features');
|
||||||
|
await h.receive('success', vectors[1].final);
|
||||||
|
assert.equal(h.result.success, 0);
|
||||||
|
assert.equal(h.client.listenerCount('sasl'), 0);
|
||||||
|
await h.start();
|
||||||
|
assert.equal(h.client.listenerCount('sasl'), 1);
|
||||||
|
await h.receive('challenge', vectors[1].first);
|
||||||
|
await h.receive('success', vectors[1].final);
|
||||||
|
assert.equal(h.result.success, 1);
|
||||||
|
assert.equal(h.result.restart, 1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('fixture policy filters PLUS and plaintext; strongest available non-PLUS selected', async () => {
|
||||||
|
const h = harness(vectors[1], true, ['PLAIN', 'SCRAM-SHA-1', 'SCRAM-SHA-512-PLUS', 'SCRAM-SHA-256', 'SCRAM-SHA-512']);
|
||||||
|
await h.start();
|
||||||
|
assert.equal(h.result.sent[0].stanza.mechanism, 'SCRAM-SHA-512');
|
||||||
|
});
|
||||||
|
for (const names of [['PLAIN'], ['SCRAM-SHA-512-PLUS'], []]) {
|
||||||
|
test(`no allowed mechanism (${names.join(',')}) disconnects`, async () => {
|
||||||
|
const h = harness(vectors[1], true, names);
|
||||||
|
await h.start();
|
||||||
|
rejected(h);
|
||||||
|
assert.equal(h.result.sent.length, 0);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
for (const type of ['failure', 'abort']) {
|
||||||
|
test(`${type} is terminal; valid final cannot revive it`, async () => {
|
||||||
|
const h = harness();
|
||||||
|
await h.start();
|
||||||
|
await h.receive(type);
|
||||||
|
await h.receive('success', vectors[1].final);
|
||||||
|
rejected(h);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
test('success during pending initial credentials fails closed', async () => {
|
||||||
|
const h = harness();
|
||||||
|
const pending = deferred();
|
||||||
|
h.client.getCredentials = () => pending.promise;
|
||||||
|
await h.start();
|
||||||
|
await h.receive('success', vectors[1].final);
|
||||||
|
rejected(h);
|
||||||
|
pending.resolve({username: 'user', password: 'pencil', clientNonce: vectors[1].nonce});
|
||||||
|
await tick();
|
||||||
|
assert.equal(h.result.sent.length, 0);
|
||||||
|
rejected(h);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('reset during proof send cancels continuation', async () => {
|
||||||
|
const h = harness();
|
||||||
|
await h.start();
|
||||||
|
const pending = deferred();
|
||||||
|
h.client.send = () => pending.promise;
|
||||||
|
await h.receive('challenge', vectors[1].first);
|
||||||
|
h.client.emit('--reset-stream-features');
|
||||||
|
pending.resolve();
|
||||||
|
await tick();
|
||||||
|
await h.receive('success', vectors[1].final);
|
||||||
|
assert.equal(h.result.success, 0);
|
||||||
|
assert.equal(h.result.restart, 0);
|
||||||
|
assert.equal(h.client.listenerCount('sasl'), 0);
|
||||||
|
});
|
||||||
|
|
||||||
|
for (const stage of ['credentials', 'initial send', 'proof send']) {
|
||||||
|
test(`${stage} rejection disconnects without success`, async () => {
|
||||||
|
const h = harness();
|
||||||
|
const reject = async () => {throw new Error('synthetic fixture failure');};
|
||||||
|
if (stage === 'credentials') h.client.getCredentials = reject;
|
||||||
|
if (stage === 'initial send') h.client.send = reject;
|
||||||
|
await h.start();
|
||||||
|
if (stage === 'proof send') {
|
||||||
|
h.client.send = reject;
|
||||||
|
await h.receive('challenge', vectors[1].first);
|
||||||
|
}
|
||||||
|
rejected(h);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
test('repeat challenge after completed proof is rejected', async () => {
|
||||||
|
const h = harness();
|
||||||
|
await h.start();
|
||||||
|
await h.receive('challenge', vectors[1].first);
|
||||||
|
await h.receive('challenge', vectors[1].first);
|
||||||
|
rejected(h);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
test('synthetic binding input fails without auth or plaintext fallback', async () => {
|
||||||
|
const h = harness();
|
||||||
|
h.client.config.credentials.tlsUnique = Buffer.alloc(32);
|
||||||
|
await h.start();
|
||||||
|
rejected(h);
|
||||||
|
assert.equal(h.result.sent.length, 0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('server silence after proof never authenticates; reset discards attempt', async () => {
|
||||||
|
const h = harness();
|
||||||
|
let cacheEvents = 0;
|
||||||
|
h.client.on('credentials:update', () => {cacheEvents++;});
|
||||||
|
await h.start();
|
||||||
|
await h.receive('challenge', vectors[0].first);
|
||||||
|
assert.equal(h.result.success, 0);
|
||||||
|
assert.equal(h.result.restart, 0);
|
||||||
|
assert.equal(h.client.transport.authenticated, false);
|
||||||
|
assert.equal(cacheEvents, 0);
|
||||||
|
assert.equal(h.client.config.credentials.saltedPassword, undefined);
|
||||||
|
h.client.emit('--reset-stream-features');
|
||||||
|
await h.receive('success', vectors[0].final);
|
||||||
|
assert.equal(h.result.success, 0);
|
||||||
|
assert.equal(h.result.restart, 0);
|
||||||
|
});
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"mechanism": "SCRAM-SHA-512",
|
||||||
|
"username": "user",
|
||||||
|
"password": "pencil",
|
||||||
|
"nonce": "rOprNGfwEbeRWgbNEkqO",
|
||||||
|
"initial": "n,,n=user,r=rOprNGfwEbeRWgbNEkqO",
|
||||||
|
"first": "r=rOprNGfwEbeRWgbNEkqO%hvYDpWUa2RaTCAfuxFIlj)hNlF$k0,s=W22ZaJ0SNY7soEsUEjb6gQ==,i=4096",
|
||||||
|
"proof": "c=biws,r=rOprNGfwEbeRWgbNEkqO%hvYDpWUa2RaTCAfuxFIlj)hNlF$k0,p=gMGXRcevScNtxZ6/8lQYpGtnsNAc3mGcmNomv+xnoOMw+3R2xNJdMNnzMlTN8PPC6wdp6dybEmDYXYTxwnYPJQ==",
|
||||||
|
"final": "v=ZQnYEgWQMFmmsM8aQMF0nDDCy/AgCzkwk8CmMZYcMg0vSVlKDanekLtifDSeVGT4+5ZxXnJq199RVG2rR7N7Zw=="
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"mechanism": "SCRAM-SHA-512",
|
||||||
|
"username": "user",
|
||||||
|
"password": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
|
||||||
|
"nonce": "rOprNGfwEbeRWgbNEkqO",
|
||||||
|
"initial": "n,,n=user,r=rOprNGfwEbeRWgbNEkqO",
|
||||||
|
"first": "r=rOprNGfwEbeRWgbNEkqO%hvYDpWUa2RaTCAfuxFIlj)hNlF$k0,s=W22ZaJ0SNY7soEsUEjb6gQ==,i=4096",
|
||||||
|
"proof": "c=biws,r=rOprNGfwEbeRWgbNEkqO%hvYDpWUa2RaTCAfuxFIlj)hNlF$k0,p=FQ+Ds6nMR5kU+jnkwyhJGBK8lA2hTFKTqEBVSUCvSgmexvd4GGT4uAqD3iACaCkLYP0Bc6JHwnHMWkpGNOX2rg==",
|
||||||
|
"final": "v=dXX1mz64UYbnX15NloHSoWfqoMVVvFg23i8KXwHigIVa+U6w+eJBBwhNg47qeIqkRLhhVnRnvcVvQMXpqav+4A=="
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"mechanism": "SCRAM-SHA-512",
|
||||||
|
"username": "user",
|
||||||
|
"password": "p\u00e9ncil",
|
||||||
|
"nonce": "rOprNGfwEbeRWgbNEkqO",
|
||||||
|
"initial": "n,,n=user,r=rOprNGfwEbeRWgbNEkqO",
|
||||||
|
"first": "r=rOprNGfwEbeRWgbNEkqO%hvYDpWUa2RaTCAfuxFIlj)hNlF$k0,s=W22ZaJ0SNY7soEsUEjb6gQ==,i=4096",
|
||||||
|
"proof": "c=biws,r=rOprNGfwEbeRWgbNEkqO%hvYDpWUa2RaTCAfuxFIlj)hNlF$k0,p=Jwk58tl/01ikMIUvnr0MXiTPWZJOiqtTyIAKx2A7IL85TXsL9S/fvBEIYvd/a0QIAQrNp47BSvResR5QB63TSg==",
|
||||||
|
"final": "v=QwFhP0JOInTkJ/uTsZ2DRt00V0dQtkZKotpq815cU8X2SpF/5qbOd8sd6TCj8NjX/GPke8Xj4yg6whLHu7KNlw=="
|
||||||
|
}
|
||||||
|
]
|
||||||
@@ -0,0 +1,111 @@
|
|||||||
|
// Isolated S00-019 copy of the reviewed S00-017 ordering guard.
|
||||||
|
import {registerSha512} from './sha512-mechanism.mjs';
|
||||||
|
// No production transport, credential persistence, or channel binding is provided.
|
||||||
|
const SUPPORTED = new Set(['SCRAM-SHA-512']);
|
||||||
|
const BASE64 = /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/;
|
||||||
|
|
||||||
|
function text(value) {
|
||||||
|
// SASL's parsed additional-data is a Buffer, not raw XML/base64 text.
|
||||||
|
if (!value || typeof value.toString !== 'function' || value.length > 4096) {
|
||||||
|
throw new Error('Invalid SASL data');
|
||||||
|
}
|
||||||
|
const result = value.toString('utf8');
|
||||||
|
if (!/^[\x21-\x7e]*$/.test(result)) throw new Error('Invalid SASL data');
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
function firstMessage(value, nonce) {
|
||||||
|
// Deliberately narrow, no extensions. Bound work before calling library crypto.
|
||||||
|
const match = /^r=([\x21-\x2b\x2d-\x7e]+),s=([A-Za-z0-9+/=]+),i=([1-9][0-9]*)$/.exec(text(value));
|
||||||
|
if (!match || !BASE64.test(match[2]) || !match[1].startsWith(nonce) ||
|
||||||
|
match[1].length <= nonce.length || Number(match[3]) > 100000) {
|
||||||
|
throw new Error('Invalid SCRAM server-first');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function finalMessage(value, name) {
|
||||||
|
const final = text(value);
|
||||||
|
if (name !== 'SCRAM-SHA-512') throw new Error('Unsupported mechanism');
|
||||||
|
const length = 88;
|
||||||
|
if (!final.startsWith('v=') || final.length !== length + 2 || !BASE64.test(final.slice(2))) {
|
||||||
|
throw new Error('Invalid SCRAM server-final');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function validatedSasl(client) {
|
||||||
|
registerSha512(client);
|
||||||
|
let active;
|
||||||
|
// Public plugin + named feature registration replaces the stock handler before use.
|
||||||
|
client.registerFeature('sasl', 100, async (features, done) => {
|
||||||
|
if (active) active();
|
||||||
|
const names = features.sasl.mechanisms.filter(name => SUPPORTED.has(name));
|
||||||
|
const mech = client.sasl.createMechanism(names);
|
||||||
|
let phase = 'starting';
|
||||||
|
let credentials;
|
||||||
|
let nonce;
|
||||||
|
const cleanup = () => {
|
||||||
|
client.off('sasl', handler);
|
||||||
|
client.off('--reset-stream-features', reset);
|
||||||
|
if (active === reset) active = undefined;
|
||||||
|
};
|
||||||
|
const reset = () => {
|
||||||
|
phase = 'closed';
|
||||||
|
cleanup();
|
||||||
|
client.features.negotiated.sasl = false;
|
||||||
|
};
|
||||||
|
const fail = () => {
|
||||||
|
if (phase === 'closed') return;
|
||||||
|
phase = 'closed';
|
||||||
|
cleanup();
|
||||||
|
client.features.negotiated.sasl = false;
|
||||||
|
if (client.transport) client.transport.authenticated = false;
|
||||||
|
client.emit('auth:failed');
|
||||||
|
done('disconnect', 'SCRAM validation failed'); // Never log peer data/credentials.
|
||||||
|
};
|
||||||
|
const handler = async sasl => {
|
||||||
|
if (phase === 'closed') return;
|
||||||
|
try {
|
||||||
|
if (sasl.type === 'challenge' && phase === 'server-first') {
|
||||||
|
phase = 'sending-proof'; // Reject duplicate challenges and racing success.
|
||||||
|
firstMessage(sasl.value, nonce);
|
||||||
|
mech.processChallenge(sasl.value);
|
||||||
|
const response = mech.createResponse(credentials);
|
||||||
|
await client.send('sasl', {type: 'response', value: response});
|
||||||
|
if (phase !== 'closed') phase = 'server-final';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (sasl.type !== 'success' || phase !== 'server-final') return fail();
|
||||||
|
finalMessage(sasl.value, mech.name);
|
||||||
|
mech.processSuccess(sasl.value);
|
||||||
|
const result = mech.finalize();
|
||||||
|
if (!result.authenticated || !result.mutuallyAuthenticated) return fail();
|
||||||
|
phase = 'closed';
|
||||||
|
cleanup();
|
||||||
|
client.features.negotiated.sasl = true;
|
||||||
|
if (client.transport) client.transport.authenticated = true;
|
||||||
|
client.emit('auth:success', client.config.credentials);
|
||||||
|
done('restart');
|
||||||
|
} catch {
|
||||||
|
fail();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
active = reset;
|
||||||
|
client.features.negotiated.sasl = false;
|
||||||
|
client.on('sasl', handler);
|
||||||
|
client.once('--reset-stream-features', reset);
|
||||||
|
try {
|
||||||
|
if (!mech || !SUPPORTED.has(mech.name)) return fail();
|
||||||
|
credentials = await client.getCredentials();
|
||||||
|
if (phase === 'closed') return;
|
||||||
|
// Restrict this experiment to non-PLUS; never invent TLS binding bytes.
|
||||||
|
if (credentials.tlsUnique) return fail();
|
||||||
|
const response = mech.createResponse(credentials);
|
||||||
|
nonce = /,r=([^,]+)$/.exec(response.toString('utf8'))?.[1];
|
||||||
|
if (!nonce) return fail();
|
||||||
|
await client.send('sasl', {type: 'auth', mechanism: mech.name, value: response});
|
||||||
|
if (phase !== 'closed') phase = 'server-first';
|
||||||
|
} catch {
|
||||||
|
fail();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
import {defineConfig} from 'vite';
|
||||||
|
import {fileURLToPath} from 'node:url';
|
||||||
|
export default defineConfig({
|
||||||
|
resolve: {alias: {events: fileURLToPath(new URL('./node_modules/events/events.js', import.meta.url))}},
|
||||||
|
build: {
|
||||||
|
target: ['chrome111', 'edge111', 'firefox114', 'safari16.4'],
|
||||||
|
minify: false,
|
||||||
|
lib: {entry: 'browser-entry.mjs', formats: ['es'], fileName: () => 'browser.mjs'},
|
||||||
|
rollupOptions: {plugins: [{
|
||||||
|
name: 'backend-evidence',
|
||||||
|
generateBundle() {
|
||||||
|
const modules = [...this.getModuleIds()].map(id => id.replace(process.cwd(), '.')).sort();
|
||||||
|
if (!modules.some(id => id.endsWith('/stanza/platform/browser/crypto/Hmac.js'))) {
|
||||||
|
throw new Error('Browser HMAC missing');
|
||||||
|
}
|
||||||
|
if (modules.some(id => /stanza\/platform\/node\/|^node:|browser-external/.test(id))) {
|
||||||
|
throw new Error('Unexpected Node runtime backend');
|
||||||
|
}
|
||||||
|
this.emitFile({type: 'asset', fileName: 'modules.json', source: JSON.stringify(modules, null, 2) + '\n'});
|
||||||
|
}
|
||||||
|
}]}
|
||||||
|
}
|
||||||
|
});
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
# Матрица задач
|
# Матрица задач
|
||||||
|
|
||||||
Generated; state revision **48**. Canonical status: state/tasks.json.
|
Generated; state revision **50**. Canonical status: state/tasks.json.
|
||||||
|
|
||||||
| ID | Этап | Оператор | Reviewer | Статус | Owner | Зависимости | Задача |
|
| ID | Этап | Оператор | Reviewer | Статус | Owner | Зависимости | Задача |
|
||||||
| --- | --- | --- | --- | --- | --- | --- | --- |
|
| --- | --- | --- | --- | --- | --- | --- | --- |
|
||||||
@@ -21,7 +21,7 @@ Generated; state revision **48**. Canonical status: state/tasks.json.
|
|||||||
| [S00-016](tasks/S00-016.md) | S00 | ci-cd | qa | done | auto-ci-cd-351a0cbbabef | S00-001, S00-011, S00-013, S00-014 | Restore reviewer model availability after provider capacity failure |
|
| [S00-016](tasks/S00-016.md) | S00 | ci-cd | qa | done | auto-ci-cd-351a0cbbabef | S00-001, S00-011, S00-013, S00-014 | Restore reviewer model availability after provider capacity failure |
|
||||||
| [S00-017](tasks/S00-017.md) | S00 | protocol | security | done | auto-protocol-599343cd9e3f | S00-001, S00-002 | Establish fail-closed SCRAM server-final validation |
|
| [S00-017](tasks/S00-017.md) | S00 | protocol | security | done | auto-protocol-599343cd9e3f | S00-001, S00-002 | Establish fail-closed SCRAM server-final validation |
|
||||||
| [S00-018](tasks/S00-018.md) | S00 | protocol | security | done | auto-protocol-89fe6563092c | S00-001, S00-002, S00-017 | Validate bounded standalone XML ingress for the XMPP candidate |
|
| [S00-018](tasks/S00-018.md) | S00 | protocol | security | done | auto-protocol-89fe6563092c | S00-001, S00-002, S00-017 | Validate bounded standalone XML ingress for the XMPP candidate |
|
||||||
| [S00-019](tasks/S00-019.md) | S00 | crypto | security | todo | | S00-001, S00-002, S00-017, S00-018 | Validate browser-compatible SCRAM-SHA-512 through a public mechanism API |
|
| [S00-019](tasks/S00-019.md) | S00 | crypto | security | review | auto-crypto-c68801170fa4 | S00-001, S00-002, S00-017, S00-018 | Validate browser-compatible SCRAM-SHA-512 through a public mechanism API |
|
||||||
| [S00-010](tasks/S00-010.md) | S00 | qa | orchestrator | todo | | S00-001, S00-002, S00-003, S00-004, S00-005, S00-006, S00-007, S00-008, S00-009 | Gate: Профиль и проверка стека |
|
| [S00-010](tasks/S00-010.md) | S00 | qa | orchestrator | todo | | S00-001, S00-002, S00-003, S00-004, S00-005, S00-006, S00-007, S00-008, S00-009 | Gate: Профиль и проверка стека |
|
||||||
| [S01-001](tasks/S01-001.md) | S01 | orchestrator | security | todo | | S00-010 | Описать identity/model contracts |
|
| [S01-001](tasks/S01-001.md) | S01 | orchestrator | security | todo | | S00-010 | Описать identity/model contracts |
|
||||||
| [S01-002](tasks/S01-002.md) | S01 | orchestrator | security | todo | | S00-010, S01-001 | Описать async account context |
|
| [S01-002](tasks/S01-002.md) | S01 | orchestrator | security | todo | | S00-010, S01-001 | Описать async account context |
|
||||||
|
|||||||
@@ -23,7 +23,7 @@
|
|||||||
- [S00-016](../tasks/S00-016.md): Restore reviewer model availability after provider capacity failure — ci-cd; done.
|
- [S00-016](../tasks/S00-016.md): Restore reviewer model availability after provider capacity failure — ci-cd; done.
|
||||||
- [S00-017](../tasks/S00-017.md): Establish fail-closed SCRAM server-final validation — protocol; done.
|
- [S00-017](../tasks/S00-017.md): Establish fail-closed SCRAM server-final validation — protocol; done.
|
||||||
- [S00-018](../tasks/S00-018.md): Validate bounded standalone XML ingress for the XMPP candidate — protocol; done.
|
- [S00-018](../tasks/S00-018.md): Validate bounded standalone XML ingress for the XMPP candidate — protocol; done.
|
||||||
- [S00-019](../tasks/S00-019.md): Validate browser-compatible SCRAM-SHA-512 through a public mechanism API — crypto; todo.
|
- [S00-019](../tasks/S00-019.md): Validate browser-compatible SCRAM-SHA-512 through a public mechanism API — crypto; review.
|
||||||
- [S00-010](../tasks/S00-010.md): Gate: Профиль и проверка стека — qa; todo.
|
- [S00-010](../tasks/S00-010.md): Gate: Профиль и проверка стека — qa; todo.
|
||||||
|
|
||||||
## Граница этапа
|
## Граница этапа
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ S00-015,S00,ci-cd,qa,done,auto-ci-cd-84e209103f24,"S00-001, S00-011, S00-013, S0
|
|||||||
S00-016,S00,ci-cd,qa,done,auto-ci-cd-351a0cbbabef,"S00-001, S00-011, S00-013, S00-014",Restore reviewer model availability after provider capacity failure
|
S00-016,S00,ci-cd,qa,done,auto-ci-cd-351a0cbbabef,"S00-001, S00-011, S00-013, S00-014",Restore reviewer model availability after provider capacity failure
|
||||||
S00-017,S00,protocol,security,done,auto-protocol-599343cd9e3f,"S00-001, S00-002",Establish fail-closed SCRAM server-final validation
|
S00-017,S00,protocol,security,done,auto-protocol-599343cd9e3f,"S00-001, S00-002",Establish fail-closed SCRAM server-final validation
|
||||||
S00-018,S00,protocol,security,done,auto-protocol-89fe6563092c,"S00-001, S00-002, S00-017",Validate bounded standalone XML ingress for the XMPP candidate
|
S00-018,S00,protocol,security,done,auto-protocol-89fe6563092c,"S00-001, S00-002, S00-017",Validate bounded standalone XML ingress for the XMPP candidate
|
||||||
S00-019,S00,crypto,security,todo,,"S00-001, S00-002, S00-017, S00-018",Validate browser-compatible SCRAM-SHA-512 through a public mechanism API
|
S00-019,S00,crypto,security,review,auto-crypto-c68801170fa4,"S00-001, S00-002, S00-017, S00-018",Validate browser-compatible SCRAM-SHA-512 through a public mechanism API
|
||||||
S00-010,S00,qa,orchestrator,todo,,"S00-001, S00-002, S00-003, S00-004, S00-005, S00-006, S00-007, S00-008, S00-009",Gate: Профиль и проверка стека
|
S00-010,S00,qa,orchestrator,todo,,"S00-001, S00-002, S00-003, S00-004, S00-005, S00-006, S00-007, S00-008, S00-009",Gate: Профиль и проверка стека
|
||||||
S01-001,S01,orchestrator,security,todo,,S00-010,Описать identity/model contracts
|
S01-001,S01,orchestrator,security,todo,,S00-010,Описать identity/model contracts
|
||||||
S01-002,S01,orchestrator,security,todo,,"S00-010, S01-001",Описать async account context
|
S01-002,S01,orchestrator,security,todo,,"S00-010, S01-001",Описать async account context
|
||||||
|
|||||||
|
@@ -0,0 +1,4 @@
|
|||||||
|
Validate browser SCRAM-SHA-512 through public mechanism composition
|
||||||
|
|
||||||
|
Changes: Add an isolated pinned mechanism wrapper using Stanza's public constructor and factory APIs, preserve the SCRAM-SHA-512 wire name and fail-closed final guard, and document the proposed ADR, browser bundle evidence, independent Scramp vectors, notices and limits. Leave parent selection, dependency experiments, specs and shared contracts unchanged.
|
||||||
|
Validation: Fresh-cache npm ci and Vite browser build pass; 87 focused tests pass, including RFC HMAC vectors, original defect reproduction, independent full SHA-512 exchanges and rejected invalid finals/order/reset paths. Hashed Scramp oracle fixture comparison passes. Dependency check verifies 49 locked entries, 25 installed pins/licenses and 340 unmodified Stanza files. make check passes 72 tooling tests. Native browsers, WSS/live server interoperability and production readiness are untested; independent security review remains pending.
|
||||||
@@ -0,0 +1,157 @@
|
|||||||
|
# S00-019 executor evidence
|
||||||
|
|
||||||
|
Agent: auto-crypto-c68801170fa4 (crypto), 2026-10-15.
|
||||||
|
Result: isolated SCRAM-SHA-512 experiment and proposed ADR; security review pending.
|
||||||
|
|
||||||
|
## Input/output and scope
|
||||||
|
|
||||||
|
Read master/startup/common/crypto/protocol/workflow/testing/Git instructions;
|
||||||
|
assigned resume/show/context and required blocker; accepted scope/stack/SASL/
|
||||||
|
parser ADRs, direct prerequisite evidence/reviews/handoffs, original S00-003
|
||||||
|
blocked evidence/handoff and exact ACC-004/NET-003/NET-004. No earlier S00-019
|
||||||
|
handoff existed. No new shared S01 contract is needed for this isolated experiment.
|
||||||
|
|
||||||
|
Only source changes: experiments/0002-xmpp-library/sha512-validation/** and
|
||||||
|
docs/decisions/0002-xmpp-sha512-validation.md. Evidence/handoff/commit message
|
||||||
|
are workflow metadata. Initial Git status showed only coordinator state claim.
|
||||||
|
Original candidate, accepted S00-017/018 source, specs, planning definitions,
|
||||||
|
shared interfaces and application modules unchanged. No Git mutations, other
|
||||||
|
task selection, agent spawn, secrets/real accounts or TLS/CORS bypass.
|
||||||
|
|
||||||
|
Public Mechanism wrapper composes exported SCRAM constructor with SHA512 alias;
|
||||||
|
wire name stays SCRAM-SHA-512. Factory.disable/register and Client.use/
|
||||||
|
registerFeature are public shipped APIs. No library private field/method is
|
||||||
|
accessed/mutated; no vendor/prototype/crypto import patch. All primitives,
|
||||||
|
SCRAM derivation, proof and verifier stay in existing unmodified Stanza. This
|
||||||
|
is pinned version-specific alias composition, not upstream endorsement of a
|
||||||
|
nonstandard wire name or future API stability. Reviewer must assess it explicitly.
|
||||||
|
|
||||||
|
Guard is an isolated copy of accepted S00-017 narrowed to SHA-512/88-byte-base64
|
||||||
|
verifier text length and new registration. Actual processSuccess/finalize both
|
||||||
|
flags precede authenticated state/success/restart. No cache persistence/emission;
|
||||||
|
no invented channel binding/plaintext fallback. Non-PLUS-only fixture profile.
|
||||||
|
|
||||||
|
## Actual commands and exit codes
|
||||||
|
|
||||||
|
All npm/Node commands use
|
||||||
|
PATH=/private/tmp/luma-stack-runtime/node-v24.21.0-darwin-arm64/bin:$PATH.
|
||||||
|
Reused accepted runtime; no new runtime download/checksum verification claimed.
|
||||||
|
Source inspections/rg/web reads supplied exact declarations and primary docs.
|
||||||
|
|
||||||
|
- python3 scripts/tasks.py resume --agent auto-crypto-c68801170fa4; show S00-019;
|
||||||
|
packet S00-019 --agent auto-crypto-c68801170fa4: each exit 0. Assignment,
|
||||||
|
dependencies done, scope and exact requirements confirmed; no repeat claim.
|
||||||
|
- npm install --prefix experiments/0002-xmpp-library/sha512-validation
|
||||||
|
--ignore-scripts --no-audit --no-fund --cache /private/tmp/luma-sha512-install
|
||||||
|
--fetch-retries=0 --fetch-timeout=15000: exit 0, initially 24 packages;
|
||||||
|
exact events 3.3.0 added to manifest and same command exit 0, one added.
|
||||||
|
- npm view events@3.3.0 version license engines repository --json
|
||||||
|
--cache /private/tmp/luma-sha512-install --strict-ssl=true: exit 0, MIT,
|
||||||
|
Node >=0.8.x; shipped README/public API/browser constraints inspected before use.
|
||||||
|
- npm run build --prefix experiments/0002-xmpp-library/sha512-validation:
|
||||||
|
corrected build exit 0, 166 modules, self-contained ESM 637.01 kB / gzip
|
||||||
|
128.84 kB; module graph 8.04 kB. Browser HMAC/SHA512 resolved, no Node backend,
|
||||||
|
builtin or browser external. Output ignored; repeatable via locked build.
|
||||||
|
- python3 -m venv /private/tmp/luma-sha512-oracle: exit 0. Its Python is 3.9.6.
|
||||||
|
- /private/tmp/luma-sha512-oracle/bin/python -m pip install --require-hashes
|
||||||
|
--only-binary=:all: --no-cache-dir -r
|
||||||
|
experiments/0002-xmpp-library/sha512-validation/oracle-requirements.txt:
|
||||||
|
exit 0, exact Scramp 1.4.6/asn1crypto 1.5.1 wheels. No scripts/system install.
|
||||||
|
- /private/tmp/luma-sha512-oracle/bin/python
|
||||||
|
experiments/0002-xmpp-library/sha512-validation/oracle.py --write: exit 0,
|
||||||
|
three independent client/server verified fixed SHA-512 transcripts generated.
|
||||||
|
Same oracle.py without --write: exit 0, fixed fixture comparison passed.
|
||||||
|
- Python urllib canonical RFC 4231 .txt retrieval/vector extraction: exit 0;
|
||||||
|
all seven HMAC SHA512 expected outputs copied from RFC, not generated by target.
|
||||||
|
- npm test --prefix experiments/0002-xmpp-library/sha512-validation:
|
||||||
|
exit 0 after harness correction, 87 tests passed, zero fail/skip/cancel.
|
||||||
|
- npm ci --prefix experiments/0002-xmpp-library/sha512-validation
|
||||||
|
--ignore-scripts --no-audit --no-fund
|
||||||
|
--cache /private/tmp/luma-sha512-clean-c68801170fa4
|
||||||
|
--fetch-retries=0 --fetch-timeout=15000: exit 0, new empty cache, 25 packages.
|
||||||
|
Strict HTTPS/engine settings retained; lifecycle scripts disabled.
|
||||||
|
- npm test after clean install, output /private/tmp/luma-sha512-final-tests.log:
|
||||||
|
exit 0, rebuilt and 87 passed, duration 3049.997417ms. Added exact original
|
||||||
|
broken-digest assertion afterward, reran npm test with output
|
||||||
|
/private/tmp/luma-sha512-submit-tests.log: exit 0, 87 passed. No implementation
|
||||||
|
changes after clean install; final source/test suite now fixed.
|
||||||
|
- python3 experiments/0002-xmpp-library/sha512-validation/check-dependencies.py:
|
||||||
|
exit 0, 49 locked entries, 25 installed exact pins/licenses, all 340 Stanza
|
||||||
|
files byte-match original candidate, browser graph verified. Repeated after
|
||||||
|
final test assertion; exit 0 and artifact/source hashes unchanged.
|
||||||
|
- Corrected npm notice collector and independent Python oracle notice collector:
|
||||||
|
exit 0; full installed texts including Stanza NOTICE ancestry and rolldown
|
||||||
|
third-party licenses preserved. Scramp MIT-0 and asn1crypto MIT preserved.
|
||||||
|
- make check > /private/tmp/luma-S00-019-check.log 2>&1: exit 0,
|
||||||
|
284 tasks / 22 stages / 221 requirements, 72 tooling tests, 19.252s.
|
||||||
|
Foundation integrity only; does not prove messenger readiness.
|
||||||
|
- git diff --check; git status --short --untracked-files=all;
|
||||||
|
git rev-parse --show-toplevel: exit 0, project is independent Git root,
|
||||||
|
scoped source plus allowed state metadata. Authored untracked files separately
|
||||||
|
checked for final newline/trailing whitespace before submit.
|
||||||
|
|
||||||
|
Exploratory/development failures are not passed checks. Initial guessed
|
||||||
|
SCRAM.js/browser/Hmac.js paths were absent (cat exit 1), located actual index.js
|
||||||
|
and browser/crypto files. rg /private/tmp hit one unrelated permission-denied
|
||||||
|
socket directory, found accepted runtime; no required artifact inaccessible.
|
||||||
|
First build exit 1: evidence writer attempted nonexistent dist/modules.json;
|
||||||
|
changed to public bundler emitFile. It also exposed unresolved events builtin;
|
||||||
|
resolved with pinned existing browser polyfill, not a crypto backend patch.
|
||||||
|
Initial integration run exit 1: 51/53 passed, two stock baseline cases lacked
|
||||||
|
SHA512 factory registration (stock Client doesn't register it by default);
|
||||||
|
corrected fixtures to register original SCRAM through public API, then reproduced
|
||||||
|
premature acceptance. Initial npm license collector asserted missing optional
|
||||||
|
native binding license, inner Python failed but following oracle collector made
|
||||||
|
combined shell exit 0; corrected collector preserves owning rolldown notices.
|
||||||
|
Some primary registry/GitHub pages failed web retrieval; normal HTTPS registry
|
||||||
|
metadata/local pinned source supplied exact facts. No unresolved blocker.
|
||||||
|
|
||||||
|
## Demonstrated checks and backend separation
|
||||||
|
|
||||||
|
32 backend checks plus 55 feature-dispatcher cases = 87. Seven RFC 4231 cases
|
||||||
|
including short/25-byte/131-byte keys and truncation match sha512/SHA512. Fixed
|
||||||
|
SHA512 abc matches four hash spellings. Additional 0/1/63/64/65/127/128/129/131/256
|
||||||
|
key lengths match independent Node crypto. Exact original sha-512 broken digest
|
||||||
|
and wrong stock browser SCRAM proof reproduce. Selected mechanism matches three
|
||||||
|
Scramp fixed full exchanges including long and Unicode synthetic passwords.
|
||||||
|
Scramp server verifies proof and Scramp client verifies server-final independently.
|
||||||
|
|
||||||
|
All integration imports are from the Vite browser artifact. Backend name and
|
||||||
|
module graph confirm browser code; artifact has no external runtime imports or
|
||||||
|
require. Node native crypto is test-only independent primitive oracle. Browser
|
||||||
|
RNG path calls host getRandomValues. XML registry/parser and feature dispatcher
|
||||||
|
are real. Only send/transport/disconnect effects are explicit fixtures. Public
|
||||||
|
verification-method tracing calls real delegate methods and records order.
|
||||||
|
|
||||||
|
Missing/forged/premature/error/conflicting/duplicate/bad-base64/oversized finals,
|
||||||
|
invalid first/nonce/iteration, duplicate challenges, pending sends/credentials,
|
||||||
|
reset, send errors and replay fail without authentication/restart. Correct wire
|
||||||
|
name and mixed-offer SHA512 selection pass. PLUS/plaintext/empty-only offers
|
||||||
|
fail; synthetic tlsUnique fails. Silence never authenticates; reset cancels it.
|
||||||
|
No derived cache is persisted/emitted.
|
||||||
|
|
||||||
|
## SHA256 artifacts
|
||||||
|
|
||||||
|
- package-lock.json: e0e129e46d3f8f86f39895a9c5fb73de761eae59108122d3684a35ab60bce399
|
||||||
|
- sha512-mechanism.mjs: f8afc29ff467c947942495eca59ec9078e32f00448b7838da39e819b8360aaeb
|
||||||
|
- validated-sasl.mjs: 7d2ba7c4e7033e844a6604ec899604cb9542ab57a55ea892c1e2ac474885c7d9
|
||||||
|
- test/scramp-vectors.json: fbf8c17b3e45c0a9a88bf6f01ba63ff91b6e51a22097af6ce2f6e3de898b8321
|
||||||
|
- dist/browser.mjs: 0b4fb1ef7684d7eab5cb1cd2c6078cdb84bff55f5cb3986e1e79a1d7418cf72a
|
||||||
|
- dist/modules.json: c07df6bb81f9adafe4b3d5df71454f282bd115f369005b91f46ddb0c003119e6
|
||||||
|
|
||||||
|
## Limits and next input
|
||||||
|
|
||||||
|
Browser-built backend executes in Node with host native web APIs. No actual
|
||||||
|
Chrome/Firefox/Safari run, WSS/TLS/server/live independent client, parent parser
|
||||||
|
integration, complete fallback policy, worker/performance, constant-time,
|
||||||
|
exhaustive SASLprep, advisory/provenance or release readiness result claimed.
|
||||||
|
No new primitive audit: existing library code is vetted here by source/API and
|
||||||
|
known/interoperability vectors, not certified audited. String verifier equality,
|
||||||
|
synchronous derivation, success-only final layout, no extensions, fixture byte/
|
||||||
|
iteration ceilings and no timeout remain explicit limits. See README/ADR.
|
||||||
|
|
||||||
|
Independent security reviewer must inspect actual diff/message, public alias
|
||||||
|
composition, graph/pins/notices, rerun README clean install/build/tests, oracle
|
||||||
|
fixture comparison and vendor checks, assess limits, then accept/reject ADR via
|
||||||
|
separate workflow. This executor submits for review and stops; coordinator alone
|
||||||
|
commits/launches fresh review or parent continuation. No done/acceptance claimed.
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
# S00-019 handoff for independent security review
|
||||||
|
|
||||||
|
Executor: auto-crypto-c68801170fa4 (crypto), 2026-10-15.
|
||||||
|
Outcome: isolated pinned browser-compatible SCRAM-SHA-512 experiment and proposed
|
||||||
|
docs/decisions/0002-xmpp-sha512-validation.md. No blocker; acceptance pending.
|
||||||
|
|
||||||
|
Public Mechanism composition uses exported SCRAM('SCRAM-SHA512') constructor to
|
||||||
|
select existing browser HMAC alias while wrapper wire name stays SCRAM-SHA-512.
|
||||||
|
Factory.disable/register and fresh Client.use/registerFeature only; no library
|
||||||
|
private-field/vendor/primitive patch. S00-017 guard copied/narrowed inside scope;
|
||||||
|
accepted parent/dependency sources remain unchanged.
|
||||||
|
|
||||||
|
Clean-cache npm ci and actual browser bundle/build + 87 tests passed. Seven RFC
|
||||||
|
HMAC cases and key-length boundaries pass, exact old hyphenated HMAC/full proof
|
||||||
|
defects reproduce, three fixed independent Scramp client/server SHA512 exchanges
|
||||||
|
match, final/order/error/reset/replay cases reject before auth/restart. 49 locked
|
||||||
|
entries/25 installed pins/notices and 340 unchanged Stanza files verified.
|
||||||
|
make check: exit 0, 72 tests. Detailed commands/hashes/development failures:
|
||||||
|
state/evidence/S00-019-auto-crypto-c68801170fa4.md.
|
||||||
|
|
||||||
|
Reviewer: inspect executor diff/message and public constructor alias composition
|
||||||
|
explicitly. Follow experiment README with accepted Node 24.21.0/npm 11.19.0 and
|
||||||
|
new cache, run npm test and check-dependencies.py; independently recreate hashed
|
||||||
|
Python oracle environment and run oracle.py (default compares fixed fixtures).
|
||||||
|
Parent installed pinned candidate is read-only vendor comparison input. Review
|
||||||
|
licenses including Stanza browser ancestry, build binding owner notices and
|
||||||
|
separate MIT-0/MIT oracle, actual feature dispatcher and both verifier flags.
|
||||||
|
ADR must remain proposed until your separate acceptance decision.
|
||||||
|
|
||||||
|
Limits: browser bundle executes in Node, no native-browser/WSS/live-server/client
|
||||||
|
run or parent parser integration; no full fallback policy/production selection.
|
||||||
|
No invented binding/plaintext downgrade. Synchronous derivation, string equality
|
||||||
|
verifier, no timeout, success-only final, no extensions, local bounds and limited
|
||||||
|
Unicode vector remain. No audit/provenance/release clearance. Coordinator owns
|
||||||
|
commit and next fresh process; executor stops after submit, does not continue
|
||||||
|
S00-003 or mark done.
|
||||||
+31
-4
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"schema_version": 1,
|
"schema_version": 1,
|
||||||
"revision": 48,
|
"revision": 50,
|
||||||
"tasks": {
|
"tasks": {
|
||||||
"S00-001": {
|
"S00-001": {
|
||||||
"status": "done",
|
"status": "done",
|
||||||
@@ -1249,10 +1249,14 @@
|
|||||||
"completed_at": "2026-10-14T19:35:55+00:00"
|
"completed_at": "2026-10-14T19:35:55+00:00"
|
||||||
},
|
},
|
||||||
"S00-019": {
|
"S00-019": {
|
||||||
"status": "todo",
|
"status": "review",
|
||||||
"owner": null,
|
"owner": "auto-crypto-c68801170fa4",
|
||||||
"external_wait": false,
|
"external_wait": false,
|
||||||
"reason": null
|
"reason": null,
|
||||||
|
"started_at": "2026-10-14T19:38:43+00:00",
|
||||||
|
"updated_at": "2026-10-14T19:48:39+00:00",
|
||||||
|
"evidence": "state/evidence/S00-019-auto-crypto-c68801170fa4.md",
|
||||||
|
"handoff": "state/handoffs/S00-019.md"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"events": [
|
"events": [
|
||||||
@@ -1878,6 +1882,29 @@
|
|||||||
"from": "in_progress",
|
"from": "in_progress",
|
||||||
"to": "blocked",
|
"to": "blocked",
|
||||||
"reason": "Stanza 12.22.1 browser HMAC sha-512 fails RFC 4231 Test Case 1: wrong 64-byte block selection. SCRAM-SHA-512 derives that hyphenated algorithm; private algorithm mutation/vendor patches are unsupported. Parent cannot attest browser strongest-mechanism compatibility from Node-only success."
|
"reason": "Stanza 12.22.1 browser HMAC sha-512 fails RFC 4231 Test Case 1: wrong 64-byte block selection. SCRAM-SHA-512 derives that hyphenated algorithm; private algorithm mutation/vendor patches are unsupported. Parent cannot attest browser strongest-mechanism compatibility from Node-only success."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"revision": 49,
|
||||||
|
"at": "2026-10-14T19:38:43+00:00",
|
||||||
|
"action": "claim",
|
||||||
|
"task": "S00-019",
|
||||||
|
"agent": "auto-crypto-c68801170fa4",
|
||||||
|
"role": "crypto",
|
||||||
|
"from": "todo",
|
||||||
|
"to": "in_progress",
|
||||||
|
"reason": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"revision": 50,
|
||||||
|
"at": "2026-10-14T19:48:39+00:00",
|
||||||
|
"action": "submit",
|
||||||
|
"task": "S00-019",
|
||||||
|
"agent": "auto-crypto-c68801170fa4",
|
||||||
|
"role": null,
|
||||||
|
"from": "in_progress",
|
||||||
|
"to": "review",
|
||||||
|
"reason": null,
|
||||||
|
"evidence": "state/evidence/S00-019-auto-crypto-c68801170fa4.md"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"retired_agents": [
|
"retired_agents": [
|
||||||
|
|||||||
Reference in new issue
Block a user